Fix dangling SingleImplementations left after class unloading

Test: make test-art-host, manual using sample code

bug: 73143991

(cherry picked from commit be4c2bd892bd167a50b4dfa7133e70a809197698)

Change-Id: I5f4d726334a9ea306d93b967966c58111fd34fd1
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 8bf91d9..1565644 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -374,13 +374,14 @@
   return ResolveClassFromTypeIndex(GetReturnTypeIndex());
 }
 
+template <ReadBarrierOption kReadBarrierOption>
 inline bool ArtMethod::HasSingleImplementation() {
-  if (IsFinal() || GetDeclaringClass()->IsFinal()) {
+  if (IsFinal<kReadBarrierOption>() || GetDeclaringClass<kReadBarrierOption>()->IsFinal()) {
     // We don't set kAccSingleImplementation for these cases since intrinsic
     // can use the flag also.
     return true;
   }
-  return (GetAccessFlags() & kAccSingleImplementation) != 0;
+  return (GetAccessFlags<kReadBarrierOption>() & kAccSingleImplementation) != 0;
 }
 
 inline bool ArtMethod::IsHiddenIntrinsic(uint32_t ordinal) {
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index bbc6007..f3c4959 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -88,13 +88,18 @@
   }
 }
 
+template <ReadBarrierOption kReadBarrierOption>
 ArtMethod* ArtMethod::GetSingleImplementation(PointerSize pointer_size) {
-  if (!IsAbstract()) {
+  if (!IsAbstract<kReadBarrierOption>()) {
     // A non-abstract's single implementation is itself.
     return this;
   }
   return reinterpret_cast<ArtMethod*>(GetDataPtrSize(pointer_size));
 }
+template ArtMethod* ArtMethod::GetSingleImplementation<ReadBarrierOption::kWithReadBarrier>(
+    PointerSize pointer_size);
+template ArtMethod* ArtMethod::GetSingleImplementation<ReadBarrierOption::kWithoutReadBarrier>(
+    PointerSize pointer_size);
 
 ArtMethod* ArtMethod::FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa,
                                           jobject jlr_method) {
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 5d9b729..3c6f230 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -172,8 +172,9 @@
     return (GetAccessFlags() & synchonized) != 0;
   }
 
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   bool IsFinal() {
-    return (GetAccessFlags() & kAccFinal) != 0;
+    return (GetAccessFlags<kReadBarrierOption>() & kAccFinal) != 0;
   }
 
   bool IsIntrinsic() {
@@ -241,10 +242,11 @@
   }
 
   // This is set by the class linker.
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   bool IsDefault() {
     static_assert((kAccDefault & (kAccIntrinsic | kAccIntrinsicBits)) == 0,
                   "kAccDefault conflicts with intrinsic modifier");
-    return (GetAccessFlags() & kAccDefault) != 0;
+    return (GetAccessFlags<kReadBarrierOption>() & kAccDefault) != 0;
   }
 
   template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
@@ -279,8 +281,9 @@
     return (GetAccessFlags() & mask) == mask;
   }
 
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   bool IsAbstract() {
-    return (GetAccessFlags() & kAccAbstract) != 0;
+    return (GetAccessFlags<kReadBarrierOption>() & kAccAbstract) != 0;
   }
 
   bool IsSynthetic() {
@@ -494,6 +497,7 @@
     return DataOffset(kRuntimePointerSize);
   }
 
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ALWAYS_INLINE bool HasSingleImplementation() REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE void SetHasSingleImplementation(bool single_impl) {
@@ -511,12 +515,15 @@
   ArtMethod* GetCanonicalMethod(PointerSize pointer_size = kRuntimePointerSize)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ArtMethod* GetSingleImplementation(PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ALWAYS_INLINE void SetSingleImplementation(ArtMethod* method, PointerSize pointer_size) {
-    DCHECK(!IsNative());
-    DCHECK(IsAbstract());  // Non-abstract method's single implementation is just itself.
+    DCHECK(!IsNative<kReadBarrierOption>());
+    // Non-abstract method's single implementation is just itself.
+    DCHECK(IsAbstract<kReadBarrierOption>());
     SetDataPtrSize(method, pointer_size);
   }
 
diff --git a/runtime/cha.cc b/runtime/cha.cc
index a53d7e5..f2e6a73 100644
--- a/runtime/cha.cc
+++ b/runtime/cha.cc
@@ -21,6 +21,7 @@
 #include "jit/jit.h"
 #include "jit/jit_code_cache.h"
 #include "linear_alloc.h"
+#include "mirror/class_loader.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
@@ -77,6 +78,106 @@
   }
 }
 
+void ClassHierarchyAnalysis::ResetSingleImplementationInHierarchy(ObjPtr<mirror::Class> klass,
+                                                                  const LinearAlloc* alloc,
+                                                                  const PointerSize pointer_size)
+                                                                  const {
+  // Presumably called from some sort of class visitor, no null pointers expected.
+  DCHECK(klass != nullptr);
+  DCHECK(alloc != nullptr);
+
+  // Skip interfaces since they cannot provide SingleImplementations to work with.
+  if (klass->IsInterface()) {
+    return;
+  }
+
+  // This method is called while visiting classes in the class table of a class loader.
+  // That means, some 'klass'es can belong to other classloaders. Argument 'alloc'
+  // allows to explicitly indicate a classloader, which is going to be deleted.
+  // Filter out classes, that do not belong to it.
+  if (!alloc->ContainsUnsafe(klass->GetMethodsPtr())) {
+    return;
+  }
+
+  // CHA analysis is only applied to resolved classes.
+  if (!klass->IsResolved()) {
+    return;
+  }
+
+  ObjPtr<mirror::Class> super = klass->GetSuperClass<kDefaultVerifyFlags, kWithoutReadBarrier>();
+
+  // Skip Object class and primitive classes.
+  if (super == nullptr) {
+    return;
+  }
+
+  // The class is going to be deleted. Iterate over the virtual methods of its superclasses to see
+  // if they have SingleImplementations methods defined by 'klass'.
+  // Skip all virtual methods that do not override methods from super class since they cannot be
+  // SingleImplementations for anything.
+  int32_t vtbl_size = super->GetVTableLength<kDefaultVerifyFlags, kWithoutReadBarrier>();
+  ObjPtr<mirror::ClassLoader> loader =
+      klass->GetClassLoader<kDefaultVerifyFlags, kWithoutReadBarrier>();
+  for (int vtbl_index = 0; vtbl_index < vtbl_size; ++vtbl_index) {
+    ArtMethod* method =
+        klass->GetVTableEntry<kDefaultVerifyFlags, kWithoutReadBarrier>(vtbl_index, pointer_size);
+    if (!alloc->ContainsUnsafe(method)) {
+      continue;
+    }
+
+    // Find all occurrences of virtual methods in parents' SingleImplementations fields
+    // and reset them.
+    // No need to reset SingleImplementations for the method itself (it will be cleared anyways),
+    // so start with a superclass and move up looking into a corresponding vtbl slot.
+    for (ObjPtr<mirror::Class> super_it = super;
+         super_it != nullptr &&
+             super_it->GetVTableLength<kDefaultVerifyFlags, kWithoutReadBarrier>() > vtbl_index;
+         super_it = super_it->GetSuperClass<kDefaultVerifyFlags, kWithoutReadBarrier>()) {
+      // Skip superclasses that are also going to be unloaded.
+      ObjPtr<mirror::ClassLoader> super_loader = super_it->
+          GetClassLoader<kDefaultVerifyFlags, kWithoutReadBarrier>();
+      if (super_loader == loader) {
+        continue;
+      }
+
+      ArtMethod* super_method = super_it->
+          GetVTableEntry<kDefaultVerifyFlags, kWithoutReadBarrier>(vtbl_index, pointer_size);
+      if (super_method->IsAbstract<kWithoutReadBarrier>() &&
+          super_method->HasSingleImplementation<kWithoutReadBarrier>() &&
+          super_method->GetSingleImplementation<kWithoutReadBarrier>(pointer_size) == method) {
+        // Do like there was no single implementation defined previously
+        // for this method of the superclass.
+        super_method->SetSingleImplementation<kWithoutReadBarrier>(nullptr, pointer_size);
+      } else {
+        // No related SingleImplementations could possibly be found any further.
+        DCHECK(!super_method->HasSingleImplementation<kWithoutReadBarrier>());
+        break;
+      }
+    }
+  }
+
+  // Check all possible interface methods too.
+  ObjPtr<mirror::IfTable> iftable = klass->GetIfTable<kDefaultVerifyFlags, kWithoutReadBarrier>();
+  const size_t ifcount = klass->GetIfTableCount<kDefaultVerifyFlags, kWithoutReadBarrier>();
+  for (size_t i = 0; i < ifcount; ++i) {
+    ObjPtr<mirror::Class> interface =
+        iftable->GetInterface<kDefaultVerifyFlags, kWithoutReadBarrier>(i);
+    for (size_t j = 0,
+         count = iftable->GetMethodArrayCount<kDefaultVerifyFlags, kWithoutReadBarrier>(i);
+         j < count;
+         ++j) {
+      ArtMethod* method = interface->GetVirtualMethod(j, pointer_size);
+      if (method->HasSingleImplementation<kWithoutReadBarrier>() &&
+          alloc->ContainsUnsafe(
+              method->GetSingleImplementation<kWithoutReadBarrier>(pointer_size)) &&
+          !method->IsDefault<kWithoutReadBarrier>()) {
+        // Do like there was no single implementation defined previously for this method.
+        method->SetSingleImplementation<kWithoutReadBarrier>(nullptr, pointer_size);
+      }
+    }
+  }
+}
+
 // This stack visitor walks the stack and for compiled code with certain method
 // headers, sets the should_deoptimize flag on stack to 1.
 // TODO: also set the register value to 1 when should_deoptimize is allocated in
diff --git a/runtime/cha.h b/runtime/cha.h
index 40999dd..d1a1b7c 100644
--- a/runtime/cha.h
+++ b/runtime/cha.h
@@ -110,6 +110,17 @@
       const std::unordered_set<OatQuickMethodHeader*>& method_headers)
       REQUIRES(Locks::cha_lock_);
 
+  // If a given class belongs to a linear allocation that is about to be deleted, in all its
+  // superclasses and superinterfaces reset SingleImplementation fields of their methods
+  // that might be affected by the deletion.
+  // The method is intended to be called during GC before ReclaimPhase, since it gets info from
+  // Java objects that are going to be collected.
+  // For the same reason it's important to access objects without read barrier to not revive them.
+  void ResetSingleImplementationInHierarchy(ObjPtr<mirror::Class> klass,
+                                            const LinearAlloc* alloc,
+                                            PointerSize pointer_size)
+      const REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Update CHA info for methods that `klass` overrides, after loading `klass`.
   void UpdateAfterLoadingOf(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 3f84ffd..bd8da61 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1169,6 +1169,25 @@
   return true;
 }
 
+class CHAOnDeleteUpdateClassVisitor {
+ public:
+  explicit CHAOnDeleteUpdateClassVisitor(LinearAlloc* alloc)
+      : allocator_(alloc), cha_(Runtime::Current()->GetClassLinker()->GetClassHierarchyAnalysis()),
+        pointer_size_(Runtime::Current()->GetClassLinker()->GetImagePointerSize()),
+        self_(Thread::Current()) {}
+
+  bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+    // This class is going to be unloaded. Tell CHA about it.
+    cha_->ResetSingleImplementationInHierarchy(klass, allocator_, pointer_size_);
+    return true;
+  }
+ private:
+  const LinearAlloc* allocator_;
+  const ClassHierarchyAnalysis* cha_;
+  const PointerSize pointer_size_;
+  const Thread* self_;
+};
+
 class VerifyDeclaringClassVisitor : public ArtMethodVisitor {
  public:
   VerifyDeclaringClassVisitor() REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_)
@@ -2168,12 +2187,14 @@
   mirror::EmulatedStackFrame::ResetClass();
   Thread* const self = Thread::Current();
   for (const ClassLoaderData& data : class_loaders_) {
-    DeleteClassLoader(self, data);
+    // CHA unloading analysis is not needed. No negative consequences are expected because
+    // all the classloaders are deleted at the same time.
+    DeleteClassLoader(self, data, false /*cleanup_cha*/);
   }
   class_loaders_.clear();
 }
 
-void ClassLinker::DeleteClassLoader(Thread* self, const ClassLoaderData& data) {
+void ClassLinker::DeleteClassLoader(Thread* self, const ClassLoaderData& data, bool cleanup_cha) {
   Runtime* const runtime = Runtime::Current();
   JavaVMExt* const vm = runtime->GetJavaVM();
   vm->DeleteWeakGlobalRef(self, data.weak_root);
@@ -2188,6 +2209,12 @@
     // If we don't have a JIT, we need to manually remove the CHA dependencies manually.
     cha_->RemoveDependenciesForLinearAlloc(data.allocator);
   }
+  // Cleanup references to single implementation ArtMethods that will be deleted.
+  if (cleanup_cha) {
+    CHAOnDeleteUpdateClassVisitor visitor(data.allocator);
+    data.class_table->Visit<CHAOnDeleteUpdateClassVisitor, kWithoutReadBarrier>(visitor);
+  }
+
   delete data.allocator;
   delete data.class_table;
 }
@@ -8952,7 +8979,8 @@
     }
   }
   for (ClassLoaderData& data : to_delete) {
-    DeleteClassLoader(self, data);
+    // CHA unloading analysis and SingleImplementaion cleanups are required.
+    DeleteClassLoader(self, data, true /*cleanup_cha*/);
   }
 }
 
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 712e3ae..8ab3f72 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -752,7 +752,7 @@
       REQUIRES(!Locks::dex_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void DeleteClassLoader(Thread* self, const ClassLoaderData& data)
+  void DeleteClassLoader(Thread* self, const ClassLoaderData& data, bool cleanup_cha)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   void VisitClassesInternal(ClassVisitor* visitor)
diff --git a/runtime/class_table-inl.h b/runtime/class_table-inl.h
index 718e93a..c59e2e8 100644
--- a/runtime/class_table-inl.h
+++ b/runtime/class_table-inl.h
@@ -60,12 +60,12 @@
   }
 }
 
-template <typename Visitor>
+template <typename Visitor, ReadBarrierOption kReadBarrierOption>
 bool ClassTable::Visit(Visitor& visitor) {
   ReaderMutexLock mu(Thread::Current(), lock_);
   for (ClassSet& class_set : classes_) {
     for (TableSlot& table_slot : class_set) {
-      if (!visitor(table_slot.Read())) {
+      if (!visitor(table_slot.Read<kReadBarrierOption>())) {
         return false;
       }
     }
@@ -73,12 +73,12 @@
   return true;
 }
 
-template <typename Visitor>
+template <typename Visitor, ReadBarrierOption kReadBarrierOption>
 bool ClassTable::Visit(const Visitor& visitor) {
   ReaderMutexLock mu(Thread::Current(), lock_);
   for (ClassSet& class_set : classes_) {
     for (TableSlot& table_slot : class_set) {
-      if (!visitor(table_slot.Read())) {
+      if (!visitor(table_slot.Read<kReadBarrierOption>())) {
         return false;
       }
     }
diff --git a/runtime/class_table.h b/runtime/class_table.h
index 48129b1..19c29b5 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -190,11 +190,11 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Stops visit if the visitor returns false.
-  template <typename Visitor>
+  template <typename Visitor, ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   bool Visit(Visitor& visitor)
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  template <typename Visitor>
+  template <typename Visitor, ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   bool Visit(const Visitor& visitor)
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index ee7d217..f63f105 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -304,20 +304,25 @@
   return GetVTable() != nullptr || ShouldHaveEmbeddedVTable();
 }
 
+  template<VerifyObjectFlags kVerifyFlags,
+           ReadBarrierOption kReadBarrierOption>
 inline int32_t Class::GetVTableLength() {
-  if (ShouldHaveEmbeddedVTable()) {
+  if (ShouldHaveEmbeddedVTable<kVerifyFlags, kReadBarrierOption>()) {
     return GetEmbeddedVTableLength();
   }
-  return GetVTable() != nullptr ? GetVTable()->GetLength() : 0;
+  return GetVTable<kVerifyFlags, kReadBarrierOption>() != nullptr ?
+      GetVTable<kVerifyFlags, kReadBarrierOption>()->GetLength() : 0;
 }
 
+  template<VerifyObjectFlags kVerifyFlags,
+           ReadBarrierOption kReadBarrierOption>
 inline ArtMethod* Class::GetVTableEntry(uint32_t i, PointerSize pointer_size) {
-  if (ShouldHaveEmbeddedVTable()) {
+  if (ShouldHaveEmbeddedVTable<kVerifyFlags, kReadBarrierOption>()) {
     return GetEmbeddedVTableEntry(i, pointer_size);
   }
-  auto* vtable = GetVTable();
+  auto* vtable = GetVTable<kVerifyFlags, kReadBarrierOption>();
   DCHECK(vtable != nullptr);
-  return vtable->GetElementPtrSize<ArtMethod*>(i, pointer_size);
+  return vtable->template GetElementPtrSize<ArtMethod*, kVerifyFlags, kReadBarrierOption>(i, pointer_size);
 }
 
 inline int32_t Class::GetEmbeddedVTableLength() {
@@ -627,8 +632,10 @@
   return ret.Ptr();
 }
 
+template<VerifyObjectFlags kVerifyFlags,
+         ReadBarrierOption kReadBarrierOption>
 inline int32_t Class::GetIfTableCount() {
-  return GetIfTable()->Count();
+  return GetIfTable<kVerifyFlags, kReadBarrierOption>()->Count();
 }
 
 inline void Class::SetIfTable(ObjPtr<IfTable> new_iftable) {
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index ea06567..51d1376 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -808,8 +808,12 @@
 
   static MemberOffset EmbeddedVTableEntryOffset(uint32_t i, PointerSize pointer_size);
 
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   int32_t GetVTableLength() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ArtMethod* GetVTableEntry(uint32_t i, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -941,6 +945,8 @@
     return (GetAccessFlags() & kAccRecursivelyInitialized) != 0;
   }
 
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ALWAYS_INLINE int32_t GetIfTableCount() REQUIRES_SHARED(Locks::mutator_lock_);
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
diff --git a/runtime/mirror/iftable.h b/runtime/mirror/iftable.h
index 296c163..d72c786 100644
--- a/runtime/mirror/iftable.h
+++ b/runtime/mirror/iftable.h
@@ -25,8 +25,11 @@
 
 class MANAGED IfTable FINAL : public ObjectArray<Object> {
  public:
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ALWAYS_INLINE Class* GetInterface(int32_t i) REQUIRES_SHARED(Locks::mutator_lock_) {
-    Class* interface = GetWithoutChecks((i * kMax) + kInterface)->AsClass();
+    Class* interface =
+        GetWithoutChecks<kVerifyFlags, kReadBarrierOption>((i * kMax) + kInterface)->AsClass();
     DCHECK(interface != nullptr);
     return interface;
   }
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",