diff options
author | 2018-02-01 19:09:59 +0300 | |
---|---|---|
committer | 2018-03-13 17:01:21 +0300 | |
commit | be4c2bd892bd167a50b4dfa7133e70a809197698 (patch) | |
tree | 150898533382fb98c160e265a5f31f66ded754f0 | |
parent | bfce631960ae2ab381180beb55cf34ab3b445aaa (diff) |
Fix dangling SingleImplementations left after class unloading
Test: make test-art-host, manual using sample code
bug: 73143991
Change-Id: I4d56b39c69d4ed60266a8b90b9e9d18fba7b8227
-rw-r--r-- | runtime/art_method-inl.h | 5 | ||||
-rw-r--r-- | runtime/art_method.cc | 7 | ||||
-rw-r--r-- | runtime/art_method.h | 17 | ||||
-rw-r--r-- | runtime/cha.cc | 101 | ||||
-rw-r--r-- | runtime/cha.h | 11 | ||||
-rw-r--r-- | runtime/class_linker.cc | 34 | ||||
-rw-r--r-- | runtime/class_linker.h | 2 | ||||
-rw-r--r-- | runtime/class_table-inl.h | 8 | ||||
-rw-r--r-- | runtime/class_table.h | 4 | ||||
-rw-r--r-- | runtime/mirror/class-inl.h | 19 | ||||
-rw-r--r-- | runtime/mirror/class.h | 6 | ||||
-rw-r--r-- | runtime/mirror/iftable.h | 5 | ||||
-rw-r--r-- | test/616-cha-unloading/cha_unload.cc | 57 | ||||
-rw-r--r-- | test/616-cha-unloading/expected.txt | 4 | ||||
-rw-r--r-- | test/616-cha-unloading/info.txt | 1 | ||||
-rw-r--r-- | test/616-cha-unloading/run | 18 | ||||
-rw-r--r-- | test/616-cha-unloading/src-ex/AbstractCHATester.java | 19 | ||||
-rw-r--r-- | test/616-cha-unloading/src-ex/ConcreteCHATester.java | 19 | ||||
-rw-r--r-- | test/616-cha-unloading/src/AbstractCHATester.java | 19 | ||||
-rw-r--r-- | test/616-cha-unloading/src/Main.java | 121 | ||||
-rw-r--r-- | test/Android.bp | 1 |
21 files changed, 453 insertions, 25 deletions
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 8bf91d9da1..1565644380 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -374,13 +374,14 @@ inline ObjPtr<mirror::Class> ArtMethod::ResolveReturnType() { 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 bbc60072b6..f3c495957f 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -88,13 +88,18 @@ ArtMethod* ArtMethod::GetNonObsoleteMethod() { } } +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 579e554901..bd9b64df36 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -172,8 +172,9 @@ class ArtMethod FINAL { 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 @@ class ArtMethod FINAL { } // 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 @@ class ArtMethod FINAL { 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 @@ class ArtMethod FINAL { 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 @@ class ArtMethod FINAL { 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 a53d7e5b25..f2e6a7314e 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::RemoveDependentsWithMethodHeaders( } } +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 40999dd15b..d1a1b7cfae 100644 --- a/runtime/cha.h +++ b/runtime/cha.h @@ -110,6 +110,17 @@ class ClassHierarchyAnalysis { 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 bf0d3adf0f..3c1ffe0d50 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1162,6 +1162,25 @@ static bool FlattenPathClassLoader(ObjPtr<mirror::ClassLoader> class_loader, 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 @@ ClassLinker::~ClassLinker() { 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 @@ void ClassLinker::DeleteClassLoader(Thread* self, const ClassLoaderData& data) { // 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 @@ void ClassLinker::CleanupClassLoaders() { } } 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 8d6b3d245c..d05e78fb40 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -752,7 +752,7 @@ class ClassLinker { 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 718e93a97d..c59e2e881d 100644 --- a/runtime/class_table-inl.h +++ b/runtime/class_table-inl.h @@ -60,12 +60,12 @@ void ClassTable::VisitRoots(const Visitor& visitor) { } } -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 @@ bool ClassTable::Visit(Visitor& visitor) { 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 52e9f82396..3e90fe2768 100644 --- a/runtime/class_table.h +++ b/runtime/class_table.h @@ -190,11 +190,11 @@ class ClassTable { 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 ee7d217e8d..f63f105c3a 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -304,20 +304,25 @@ inline bool Class::HasVTable() { 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 @@ inline IfTable* Class::GetIfTable() { 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 ea065676a0..51d1376a3c 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -808,8 +808,12 @@ class MANAGED Class FINAL : public Object { 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 @@ class MANAGED Class FINAL : public Object { 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 296c163ef7..d72c7866c5 100644 --- a/runtime/mirror/iftable.h +++ b/runtime/mirror/iftable.h @@ -25,8 +25,11 @@ namespace mirror { 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 0000000000..46ceac640b --- /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 0000000000..a71b724876 --- /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 0000000000..563380b6b2 --- /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 0000000000..d8b4f0d26c --- /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 0000000000..e11094584a --- /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 0000000000..ee2be9c3f5 --- /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 0000000000..e11094584a --- /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 0000000000..b633a0c22e --- /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 17a50421a8..0c1edcaab8 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -429,6 +429,7 @@ cc_defaults { "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", |