Fix dangling SingleImplementations left after class unloading
Test: make test-art-host, manual using sample code
bug: 73143991
Change-Id: I4d56b39c69d4ed60266a8b90b9e9d18fba7b8227
diff --git a/test/616-cha-unloading/src/AbstractCHATester.java b/test/616-cha-unloading/src/AbstractCHATester.java
new file mode 100644
index 0000000..e110945
--- /dev/null
+++ b/test/616-cha-unloading/src/AbstractCHATester.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+public abstract class AbstractCHATester {
+ public abstract void lonelyMethod();
+}
diff --git a/test/616-cha-unloading/src/Main.java b/test/616-cha-unloading/src/Main.java
new file mode 100644
index 0000000..b633a0c
--- /dev/null
+++ b/test/616-cha-unloading/src/Main.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class Main {
+ static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/616-cha-unloading-ex.jar";
+ static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path");
+ static Constructor<? extends ClassLoader> sConstructor;
+
+ private static class CHAUnloaderRetType {
+ private CHAUnloaderRetType(WeakReference<ClassLoader> cl,
+ AbstractCHATester obj,
+ long methodPtr) {
+ this.cl = cl;
+ this.obj = obj;
+ this.methodPtr = methodPtr;
+ }
+ public WeakReference<ClassLoader> cl;
+ public AbstractCHATester obj;
+ public long methodPtr;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ Class<ClassLoader> pathClassLoader = (Class<ClassLoader>) Class.forName("dalvik.system.PathClassLoader");
+ sConstructor =
+ pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class);
+
+ testUnload();
+ }
+
+ private static void testUnload() throws Exception {
+ // Load a concrete class, then unload it. Get a deleted ArtMethod to test if it'll be inlined.
+ CHAUnloaderRetType result = doUnloadLoader();
+ WeakReference<ClassLoader> loader = result.cl;
+ long methodPtr = result.methodPtr;
+ // Check that the classloader is indeed unloaded.
+ System.out.println(loader.get());
+
+ // Reuse the linear alloc so old pointers so it becomes invalid.
+ boolean ret = tryReuseArenaOfMethod(methodPtr, 10);
+ // Check that we indeed reused it.
+ System.out.println(ret);
+
+ // Try to JIT-compile under dangerous conditions.
+ ensureJitCompiled(Main.class, "targetMethodForJit");
+ System.out.println("Done");
+ }
+
+ private static void doUnloading() {
+ // Do multiple GCs to prevent rare flakiness if some other thread is keeping the
+ // classloader live.
+ for (int i = 0; i < 5; ++i) {
+ Runtime.getRuntime().gc();
+ }
+ }
+
+ private static CHAUnloaderRetType setupLoader()
+ throws Exception {
+ ClassLoader loader = sConstructor.newInstance(
+ DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
+ Class<?> concreteCHATester = loader.loadClass("ConcreteCHATester");
+
+ // Preemptively compile methods to prevent delayed JIT tasks from blocking the unloading.
+ ensureJitCompiled(concreteCHATester, "<init>");
+ ensureJitCompiled(concreteCHATester, "lonelyMethod");
+
+ Object obj = concreteCHATester.newInstance();
+ Method lonelyMethod = concreteCHATester.getDeclaredMethod("lonelyMethod");
+
+ // Get a pointer to a region that shall be not used after the unloading.
+ long artMethod = getArtMethod(lonelyMethod);
+
+ AbstractCHATester ret = null;
+ return new CHAUnloaderRetType(new WeakReference(loader), ret, artMethod);
+ }
+
+ private static CHAUnloaderRetType targetMethodForJit(int mode)
+ throws Exception {
+ CHAUnloaderRetType ret = new CHAUnloaderRetType(null, null, 0);
+ if (mode == 0) {
+ ret = setupLoader();
+ } else if (mode == 1) {
+ // This branch is not supposed to be executed. It shall trigger "lonelyMethod" inlining
+ // during jit compilation of "targetMethodForJit".
+ ret = setupLoader();
+ AbstractCHATester obj = ret.obj;
+ obj.lonelyMethod();
+ }
+ return ret;
+ }
+
+ private static CHAUnloaderRetType doUnloadLoader()
+ throws Exception {
+ CHAUnloaderRetType result = targetMethodForJit(0);
+ doUnloading();
+ return result;
+ }
+
+ private static native void ensureJitCompiled(Class<?> itf, String method_name);
+ private static native long getArtMethod(Object javaMethod);
+ private static native boolean tryReuseArenaOfMethod(long artMethod, int tries_count);
+}