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/cha_unload.cc b/test/616-cha-unloading/cha_unload.cc
new file mode 100644
index 0000000..46ceac6
--- /dev/null
+++ b/test/616-cha-unloading/cha_unload.cc
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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 "jni.h"
+
+#include <iostream>
+
+#include "art_method.h"
+#include "jit/jit.h"
+#include "linear_alloc.h"
+#include "nativehelper/ScopedUtfChars.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+
+namespace art {
+namespace {
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getArtMethod(JNIEnv* env,
+ jclass,
+ jobject java_method) {
+ ScopedObjectAccess soa(env);
+ ArtMethod* method = ArtMethod::FromReflectedMethod(soa, java_method);
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(method));
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_tryReuseArenaOfMethod(JNIEnv*,
+ jclass,
+ jlong art_method,
+ jint tries_count) {
+ // Create a new allocation and use it to request a specified amount of arenas.
+ // Hopefully one of them is a reused one, the one that covers the art_method pointer.
+ std::unique_ptr<LinearAlloc> alloc(Runtime::Current()->CreateLinearAlloc());
+ for (int i = static_cast<int>(tries_count); i > 0; --i) {
+ // Ask for a byte - it's sufficient to get an arena and not have issues with size.
+ alloc->Alloc(Thread::Current(), 1);
+ }
+ bool retval = alloc->Contains(reinterpret_cast<void*>(static_cast<uintptr_t>(art_method)));
+
+ return retval;
+}
+
+} // namespace
+} // namespace art
diff --git a/test/616-cha-unloading/expected.txt b/test/616-cha-unloading/expected.txt
new file mode 100644
index 0000000..a71b724
--- /dev/null
+++ b/test/616-cha-unloading/expected.txt
@@ -0,0 +1,4 @@
+JNI_OnLoad called
+null
+true
+Done
diff --git a/test/616-cha-unloading/info.txt b/test/616-cha-unloading/info.txt
new file mode 100644
index 0000000..563380b
--- /dev/null
+++ b/test/616-cha-unloading/info.txt
@@ -0,0 +1 @@
+Test that class unloading updated single implementations.
diff --git a/test/616-cha-unloading/run b/test/616-cha-unloading/run
new file mode 100644
index 0000000..d8b4f0d
--- /dev/null
+++ b/test/616-cha-unloading/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 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.
+
+# Run without an app image to prevent the classes to be loaded at startup.
+exec ${RUN} "${@}" --no-app-image
diff --git a/test/616-cha-unloading/src-ex/AbstractCHATester.java b/test/616-cha-unloading/src-ex/AbstractCHATester.java
new file mode 100644
index 0000000..e110945
--- /dev/null
+++ b/test/616-cha-unloading/src-ex/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-ex/ConcreteCHATester.java b/test/616-cha-unloading/src-ex/ConcreteCHATester.java
new file mode 100644
index 0000000..ee2be9c
--- /dev/null
+++ b/test/616-cha-unloading/src-ex/ConcreteCHATester.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 class ConcreteCHATester extends AbstractCHATester {
+ public void lonelyMethod() {}
+}
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);
+}
diff --git a/test/Android.bp b/test/Android.bp
index 17a5042..0c1edca 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -429,6 +429,7 @@
"596-app-images/app_images.cc",
"596-monitor-inflation/monitor_inflation.cc",
"597-deopt-new-string/deopt.cc",
+ "616-cha-unloading/cha_unload.cc",
"626-const-class-linking/clear_dex_cache_types.cc",
"642-fp-callees/fp_callees.cc",
"647-jni-get-field-id/get_field_id.cc",