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);
+}