[Ravenwood] Support NativeAllocationRegistry

We don't track native allocation size, but we still need to
release native allocations.

Bug: 337110712
Test: atest RavenwoodBivalentTest
Test: atest RavenwoodBivalentTest_device
Test: ./ravenwood/scripts/run-ravenwood-tests.sh
Change-Id: Ia50c963731a26fc951a8040cbf353a5c56505b6a
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 74382a6..3ab0934 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -270,6 +270,7 @@
     ],
     jni_libs: [
         "libandroid_runtime",
+        "libravenwood_runtime",
     ],
 }
 
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 3337419..e06f400 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -155,6 +155,31 @@
     visibility: ["//frameworks/base"],
 }
 
+cc_library_shared {
+    name: "libravenwood_runtime",
+    host_supported: true,
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+
+    srcs: [
+        "runtime-helper-src/jni/*.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnativehelper",
+        "libutils",
+        "libcutils",
+    ],
+    visibility: ["//frameworks/base"],
+}
+
 // For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/.
 // The "test" just shows the available stats filenames.
 sh_test_host {
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/bivalenttest/Android.bp
index a6b6ed9..2d94894 100644
--- a/ravenwood/bivalenttest/Android.bp
+++ b/ravenwood/bivalenttest/Android.bp
@@ -45,7 +45,6 @@
     jni_libs: [
         "libravenwoodbivalenttest_jni",
     ],
-    sdk_version: "test_current",
     auto_gen_config: true,
 }
 
diff --git a/ravenwood/bivalenttest/AndroidTest.xml b/ravenwood/bivalenttest/AndroidTest.xml
index ac4695b..9e5dd11 100644
--- a/ravenwood/bivalenttest/AndroidTest.xml
+++ b/ravenwood/bivalenttest/AndroidTest.xml
@@ -25,5 +25,8 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.ravenwood.bivalenttest" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+
+        <!-- Need to use MODULE_LIBRARIES APIs, so no hidden API check. -->
+        <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
index 83f756e..956d79c 100644
--- a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
+++ b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
@@ -15,19 +15,70 @@
  */
 
 #include <nativehelper/JNIHelp.h>
+#include <atomic>
 #include "jni.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
 
+// JNI methods for RavenwoodJniTest
+
 static jint add(JNIEnv* env, jclass clazz, jint a, jint b) {
     return a + b;
 }
 
-static const JNINativeMethod sMethods[] =
+static const JNINativeMethod sMethods_JniTest[] =
 {
     { "add", "(II)I", (void*)add },
 };
 
+// JNI methods for RavenwoodNativeAllocationRegistryTest
+std::atomic<int> numTotalAlloc = 0;
+
+class NarTestData {
+public:
+    NarTestData(jint v): value(v) {
+        numTotalAlloc++;
+    }
+
+    ~NarTestData() {
+        value = -1;
+        numTotalAlloc--;
+    }
+
+    volatile jint value;
+};
+
+static jlong NarTestData_nMalloc(JNIEnv* env, jclass clazz, jint value) {
+    NarTestData* p = new NarTestData(value);
+    return reinterpret_cast<jlong>(p);
+}
+
+static jint NarTestData_nGet(JNIEnv* env, jclass clazz, jlong ptr) {
+    NarTestData* p = reinterpret_cast<NarTestData*>(ptr);
+    return p->value;
+}
+
+static void NarTestData_free(jlong ptr) {
+    NarTestData* p = reinterpret_cast<NarTestData*>(ptr);
+    delete p;
+}
+
+static jlong NarTestData_nGetNativeFinalizer(JNIEnv* env, jclass clazz) {
+    return reinterpret_cast<jlong>(NarTestData_free);
+}
+
+static jint NarTestData_nGetTotalAlloc(JNIEnv* env, jclass clazz) {
+    return numTotalAlloc;
+}
+
+static const JNINativeMethod sMethods_NarTestData[] =
+{
+    { "nMalloc", "(I)J", (void*)NarTestData_nMalloc },
+    { "nGet", "(J)I", (void*)NarTestData_nGet },
+    { "nGetNativeFinalizer", "()J", (void*)NarTestData_nGetNativeFinalizer },
+    { "nGetTotalAlloc", "()I", (void*)NarTestData_nGetTotalAlloc },
+};
+
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
 {
     JNIEnv* env = NULL;
@@ -43,7 +94,13 @@
 
     int res = jniRegisterNativeMethods(env,
             "com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest",
-            sMethods, NELEM(sMethods));
+            sMethods_JniTest, NELEM(sMethods_JniTest));
+    if (res < 0) {
+        return res;
+    }
+    res = jniRegisterNativeMethods(env,
+            "com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest$Data",
+            sMethods_NarTestData, NELEM(sMethods_NarTestData));
     if (res < 0) {
         return res;
     }
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
index 59467e9..0cc2adc 100644
--- a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
@@ -29,6 +29,10 @@
 @RunWith(AndroidJUnit4.class)
 public final class RavenwoodJniTest {
     static {
+        initializeJni();
+    }
+
+    public static void initializeJni() {
         RavenwoodUtils.loadJniLibrary("ravenwoodbivalenttest_jni");
     }
 
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
new file mode 100644
index 0000000..415b467
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodNativeAllocationRegistryTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import libcore.util.NativeAllocationRegistry;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodNativeAllocationRegistryTest {
+    private static final String TAG = RavenwoodNativeAllocationRegistryTest.class.getSimpleName();
+    static {
+        RavenwoodJniTest.initializeJni();
+    }
+
+    @Rule
+    public final RavenwoodRule mRavenwoodRule = new RavenwoodRule();
+
+    private static class Data {
+        private final long mNativePtr;
+
+        private static native long nMalloc(int value);
+        private static native int nGet(long ptr);
+        private static native long nGetNativeFinalizer();
+
+        public static native int nGetTotalAlloc();
+
+        public int get() {
+            return nGet(mNativePtr);
+        }
+
+        private static class NarHolder {
+            public static final NativeAllocationRegistry sRegistry =
+                    NativeAllocationRegistry.createMalloced(
+                            Data.class.getClassLoader(), nGetNativeFinalizer());
+        }
+
+        public Data(int value) {
+            mNativePtr = nMalloc(value);
+            NarHolder.sRegistry.registerNativeAllocation(this, mNativePtr);
+        }
+    }
+
+    @Test
+    public void testNativeAllocationRegistry() {
+
+        final long timeoutTime = mRavenwoodRule.realCurrentTimeMillis() + 10_000;
+
+        final int startAlloc = Data.nGetTotalAlloc();
+
+        int totalAlloc = 0;
+
+        // Keep allocation new objects, until some get released.
+
+        while (true) {
+            for (int i = 0; i < 1000; i++) {
+                totalAlloc++;
+                Data d = new Data(i);
+                assertEquals(i, d.get());
+            }
+            System.gc();
+
+            final int currentAlloc = Data.nGetTotalAlloc() - startAlloc;
+            Log.i(TAG, "# of currently allocated objects=" + currentAlloc);
+
+            if (currentAlloc < totalAlloc) {
+                break; // Good, some objects have been released;
+            }
+            if (mRavenwoodRule.realCurrentTimeMillis() > timeoutTime) {
+                fail("No objects have been released before timeout");
+            }
+        }
+    }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 21d8019..9d12f85 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -426,4 +426,15 @@
             return ENABLE_OPTIONAL_VALIDATION;
         }
     }
+
+    /**
+     * Returns the "real" result from {@link System#currentTimeMillis()}.
+     *
+     * Currently, it's the same thing as calling {@link System#currentTimeMillis()},
+     * but this one is guaranteeed to return the real value, even when Ravenwood supports
+     * injecting a time to{@link System#currentTimeMillis()}.
+     */
+    public long realCurrentTimeMillis() {
+        return System.currentTimeMillis();
+    }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
index 37ceac6..99ab327 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java
@@ -26,6 +26,15 @@
     private RavenwoodUtils() {
     }
 
+    private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
+
+    // LibcoreRavenwoodUtils calls it with reflections.
+    public static void loadRavenwoodNativeRuntime() {
+        if (RavenwoodRule.isOnRavenwood()) {
+            RavenwoodUtils.loadJniLibrary(RAVENWOOD_NATIVE_RUNTIME_NAME);
+        }
+    }
+
     /**
      * Load a JNI library respecting {@code java.library.path}
      * (which reflects {@code LD_LIBRARY_PATH}).
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index e951351b..773a89a 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -39,4 +39,8 @@
     public static void validate(Statement base, Description description,
             boolean enableOptionalValidation) {
     }
+
+    public static long realCurrentTimeMillis() {
+        return System.currentTimeMillis();
+    }
 }
diff --git a/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp b/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp
new file mode 100644
index 0000000..8e3a21d
--- /dev/null
+++ b/ravenwood/runtime-helper-src/jni/ravenwood_runtime.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+
+typedef void (*FreeFunction)(void*);
+
+static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*,
+                                                       jclass,
+                                                       jlong freeFunction,
+                                                       jlong ptr) {
+    void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
+    FreeFunction nativeFreeFunction
+        = reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
+    nativeFreeFunction(nativePtr);
+}
+
+static const JNINativeMethod sMethods_NAR[] =
+{
+    { "applyFreeFunction", "(JJ)V", (void*)NativeAllocationRegistry_applyFreeFunction },
+};
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+        ALOGE("GetEnv failed!");
+        return result;
+    }
+    ALOG_ASSERT(env, "Could not retrieve the env!");
+
+    ALOGI("%s: JNI_OnLoad", __FILE__);
+
+    // Initialize the Ravenwood version of NativeAllocationRegistry.
+    // We don't use this JNI on the device side, but if we ever have to do, skip this part.
+#ifndef __ANDROID__
+    int res = jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry",
+            sMethods_NAR, NELEM(sMethods_NAR));
+    if (res < 0) {
+        return res;
+    }
+#endif
+
+    return JNI_VERSION_1_4;
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java
new file mode 100644
index 0000000..839b62a
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/ravenwood/LibcoreRavenwoodUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore.ravenwood;
+
+public class LibcoreRavenwoodUtils {
+    private LibcoreRavenwoodUtils() {
+    }
+
+    public static void loadRavenwoodNativeRuntime() {
+        // TODO Stop using reflections.
+        // We need to call RavenwoodUtils.loadRavenwoodNativeRuntime(), but due to the build
+        // structure complexity, we can't refer to to this method directly from here,
+        // so let's use reflections for now...
+        try {
+            final var clazz = Class.forName("android.platform.test.ravenwood.RavenwoodUtils");
+            final var method = clazz.getMethod("loadRavenwoodNativeRuntime");
+            method.invoke(null);
+        } catch (Throwable th) {
+            throw new IllegalStateException("Failed to load Ravenwood native runtime", th);
+        }
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
new file mode 100644
index 0000000..93861e8
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package libcore.util;
+
+import libcore.ravenwood.LibcoreRavenwoodUtils;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Reference;
+
+/**
+ * Re-implementation of ART's NativeAllocationRegistry for Ravenwood.
+ * - We don't track the native allocation size on Ravenwood.
+ * - sun.misc.Cleaner isn't available on the desktop JVM, so we use java.lang.ref.Cleaner.
+ *   (Should ART switch to java.lang.ref.Cleaner?)
+ */
+public class NativeAllocationRegistry {
+    static {
+        // Initialize the JNI method.
+        LibcoreRavenwoodUtils.loadRavenwoodNativeRuntime();
+    }
+
+    private final long mFreeFunction;
+    private static final Cleaner sCleaner = Cleaner.create();
+
+    public static NativeAllocationRegistry createNonmalloced(
+            ClassLoader classLoader, long freeFunction, long size) {
+        return new NativeAllocationRegistry(classLoader, freeFunction, size, false);
+    }
+
+    public static NativeAllocationRegistry createMalloced(
+            ClassLoader classLoader, long freeFunction, long size) {
+        return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
+    }
+
+    public static NativeAllocationRegistry createMalloced(
+            ClassLoader classLoader, long freeFunction) {
+        return new NativeAllocationRegistry(classLoader, freeFunction, 0, true);
+    }
+
+    public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
+        this(classLoader, freeFunction, size, size == 0);
+    }
+
+    private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
+            boolean mallocAllocation) {
+        if (size < 0) {
+            throw new IllegalArgumentException("Invalid native allocation size: " + size);
+        }
+        mFreeFunction = freeFunction;
+    }
+
+    public Runnable registerNativeAllocation(Object referent, long nativePtr) {
+        if (referent == null) {
+            throw new IllegalArgumentException("referent is null");
+        }
+        if (nativePtr == 0) {
+            throw new IllegalArgumentException("nativePtr is null");
+        }
+
+        final Runnable releaser = () -> {
+            applyFreeFunction(mFreeFunction, nativePtr);
+        };
+        sCleaner.register(referent, releaser);
+
+        // Ensure that cleaner doesn't get invoked before we enable it.
+        Reference.reachabilityFence(referent);
+        return releaser;
+    }
+
+    /**
+     * Calls {@code freeFunction}({@code nativePtr}).
+     */
+    public static native void applyFreeFunction(long freeFunction, long nativePtr);
+}
+