Fix dangling SingleImplementations left after class unloading
Test: make test-art-host, manual using sample code
bug: 73143991
Change-Id: I4d56b39c69d4ed60266a8b90b9e9d18fba7b8227
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 579e554..bd9b64d 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;
}
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
@@ -242,10 +243,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>
@@ -280,8 +282,9 @@
return (GetAccessFlags() & mask) == mask;
}
+ template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
bool IsAbstract() {
- return (GetAccessFlags() & kAccAbstract) != 0;
+ return (GetAccessFlags<kReadBarrierOption>() & kAccAbstract) != 0;
}
bool IsSynthetic() {
@@ -496,6 +499,7 @@
return DataOffset(kRuntimePointerSize);
}
+ template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
ALWAYS_INLINE bool HasSingleImplementation() REQUIRES_SHARED(Locks::mutator_lock_);
ALWAYS_INLINE void SetHasSingleImplementation(bool single_impl) {
@@ -513,12 +517,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 bf0d3ad..3c1ffe0 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1162,6 +1162,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_)
@@ -2146,12 +2165,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);
@@ -2166,6 +2187,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;
}
@@ -8891,7 +8918,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 8d6b3d2..d05e78f 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 52e9f82..3e90fe2 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",