diff options
| author | 2017-01-05 18:16:14 +0000 | |
|---|---|---|
| committer | 2017-01-05 18:16:15 +0000 | |
| commit | 16722603e0f0ef286085fbe9b2cbe9ccad86bfef (patch) | |
| tree | 2dbee5e5b5a5b068c0e16f1602608388555a51b8 | |
| parent | f67dadb5550ee2bd9db0b7b0b75d8c44ddf170d2 (diff) | |
| parent | dba61481035b7944173181ec9ee02aea41dd0e29 (diff) | |
Merge "Revert "Revert "Revert "Revert "Basic obsolete methods support"""""
42 files changed, 1296 insertions, 54 deletions
diff --git a/compiler/image_writer.h b/compiler/image_writer.h index c5374838f6..cc7df1ce21 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -27,6 +27,7 @@ #include <string> #include <ostream> +#include "art_method.h" #include "base/bit_utils.h" #include "base/dchecked_vector.h" #include "base/enums.h" diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index ef03bb3dd4..96976d9bce 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -134,8 +134,7 @@ inline ArtMethod* ArtMethod::GetDexCacheResolvedMethod(uint16_t method_index, // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here // without accessing the DexCache and we don't want to do that in release build. DCHECK_LT(method_index, - GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass() - ->GetDexCache()->NumResolvedMethods()); + GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods()); ArtMethod* method = mirror::DexCache::GetElementPtrSize(GetDexCacheResolvedMethods(pointer_size), method_index, pointer_size); @@ -154,8 +153,7 @@ inline void ArtMethod::SetDexCacheResolvedMethod(uint16_t method_index, // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here // without accessing the DexCache and we don't want to do that in release build. DCHECK_LT(method_index, - GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass() - ->GetDexCache()->NumResolvedMethods()); + GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods()); DCHECK(new_method == nullptr || new_method->GetDeclaringClass() != nullptr); mirror::DexCache::SetElementPtrSize(GetDexCacheResolvedMethods(pointer_size), method_index, @@ -186,8 +184,7 @@ template <bool kWithCheck> inline mirror::Class* ArtMethod::GetDexCacheResolvedType(dex::TypeIndex type_index, PointerSize pointer_size) { if (kWithCheck) { - mirror::DexCache* dex_cache = - GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass()->GetDexCache(); + mirror::DexCache* dex_cache = GetInterfaceMethodIfProxy(pointer_size)->GetDexCache(); if (UNLIKELY(type_index.index_ >= dex_cache->NumResolvedTypes())) { ThrowArrayIndexOutOfBoundsException(type_index.index_, dex_cache->NumResolvedTypes()); return nullptr; @@ -333,7 +330,7 @@ inline const char* ArtMethod::GetName() { } inline const DexFile::CodeItem* ArtMethod::GetCodeItem() { - return GetDeclaringClass()->GetDexFile().GetCodeItem(GetCodeItemOffset()); + return GetDexFile()->GetCodeItem(GetCodeItemOffset()); } inline bool ArtMethod::IsResolvedTypeIdx(dex::TypeIndex type_idx, PointerSize pointer_size) { @@ -398,11 +395,11 @@ inline mirror::ClassLoader* ArtMethod::GetClassLoader() { } inline mirror::DexCache* ArtMethod::GetDexCache() { - DCHECK(!IsProxyMethod()); - if (UNLIKELY(IsObsolete())) { - return GetObsoleteDexCache(); - } else { + if (LIKELY(!IsObsolete())) { return GetDeclaringClass()->GetDexCache(); + } else { + DCHECK(!IsProxyMethod()); + return GetObsoleteDexCache(); } } diff --git a/runtime/art_method.h b/runtime/art_method.h index 3bc6f5d4fd..963a541741 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -27,6 +27,7 @@ #include "invoke_type.h" #include "method_reference.h" #include "modifiers.h" +#include "mirror/dex_cache.h" #include "mirror/object.h" #include "obj_ptr.h" #include "read_barrier_option.h" @@ -220,6 +221,12 @@ class ArtMethod FINAL { return !IsIntrinsic() && (GetAccessFlags() & kAccObsoleteMethod) != 0; } + void SetIsObsolete() { + // TODO We should really support redefining intrinsic if possible. + DCHECK(!IsIntrinsic()); + SetAccessFlags(GetAccessFlags() | kAccObsoleteMethod); + } + template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier> bool IsNative() { return (GetAccessFlags<kReadBarrierOption>() & kAccNative) != 0; @@ -326,6 +333,7 @@ class ArtMethod FINAL { ALWAYS_INLINE ArtMethod* GetDexCacheResolvedMethod(uint16_t method_index, PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); + ALWAYS_INLINE void SetDexCacheResolvedMethod(uint16_t method_index, ArtMethod* new_method, PointerSize pointer_size) diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h index cdcc84ddc3..5fc5f1a2f5 100644 --- a/runtime/class_linker-inl.h +++ b/runtime/class_linker-inl.h @@ -189,20 +189,15 @@ inline ArtField* ClassLinker::GetResolvedField(uint32_t field_idx, return dex_cache->GetResolvedField(field_idx, image_pointer_size_); } -inline ArtField* ClassLinker::GetResolvedField(uint32_t field_idx, - ObjPtr<mirror::Class> field_declaring_class) { - return GetResolvedField(field_idx, MakeObjPtr(field_declaring_class->GetDexCache())); -} - inline ArtField* ClassLinker::ResolveField(uint32_t field_idx, ArtMethod* referrer, bool is_static) { Thread::PoisonObjectPointersIfDebug(); ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass(); - ArtField* resolved_field = GetResolvedField(field_idx, declaring_class); + ArtField* resolved_field = GetResolvedField(field_idx, referrer->GetDexCache()); if (UNLIKELY(resolved_field == nullptr)) { StackHandleScope<2> hs(Thread::Current()); - Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache())); + Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache())); Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader())); const DexFile& dex_file = *dex_cache->GetDexFile(); resolved_field = ResolveField(dex_file, field_idx, dex_cache, class_loader, is_static); diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 9b25303b65..6ef882a66a 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -327,8 +327,6 @@ class ClassLinker { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_); - ArtField* GetResolvedField(uint32_t field_idx, ObjPtr<mirror::Class> field_declaring_class) - REQUIRES_SHARED(Locks::mutator_lock_); ArtField* GetResolvedField(uint32_t field_idx, ObjPtr<mirror::DexCache> dex_cache) REQUIRES_SHARED(Locks::mutator_lock_); ArtField* ResolveField(uint32_t field_idx, ArtMethod* referrer, bool is_static) diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h index f6eeffca73..14c9c21356 100644 --- a/runtime/entrypoints/entrypoint_utils-inl.h +++ b/runtime/entrypoints/entrypoint_utils-inl.h @@ -563,7 +563,7 @@ inline ArtMethod* FindMethodFromCode(uint32_t method_idx, HandleWrapperObjPtr<mirror::Object> h_this(hs2.NewHandleWrapper(this_object)); Handle<mirror::Class> h_referring_class(hs2.NewHandle(referrer->GetDeclaringClass())); const dex::TypeIndex method_type_idx = - h_referring_class->GetDexFile().GetMethodId(method_idx).class_idx_; + referrer->GetDexFile()->GetMethodId(method_idx).class_idx_; mirror::Class* method_reference_class = class_linker->ResolveType(method_type_idx, referrer); if (UNLIKELY(method_reference_class == nullptr)) { // Bad type idx. @@ -673,8 +673,7 @@ inline ArtField* FindFieldFast(uint32_t field_idx, ArtMethod* referrer, FindFiel size_t expected_size) { ScopedAssertNoThreadSuspension ants(__FUNCTION__); ArtField* resolved_field = - referrer->GetDeclaringClass()->GetDexCache()->GetResolvedField(field_idx, - kRuntimePointerSize); + referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize); if (UNLIKELY(resolved_field == nullptr)) { return nullptr; } @@ -733,7 +732,7 @@ inline ArtMethod* FindMethodFast(uint32_t method_idx, } mirror::Class* referring_class = referrer->GetDeclaringClass(); ArtMethod* resolved_method = - referring_class->GetDexCache()->GetResolvedMethod(method_idx, kRuntimePointerSize); + referrer->GetDexCache()->GetResolvedMethod(method_idx, kRuntimePointerSize); if (UNLIKELY(resolved_method == nullptr)) { return nullptr; } @@ -759,9 +758,9 @@ inline ArtMethod* FindMethodFast(uint32_t method_idx, } else if (type == kSuper) { // TODO This lookup is rather slow. dex::TypeIndex method_type_idx = - referring_class->GetDexFile().GetMethodId(method_idx).class_idx_; + referrer->GetDexFile()->GetMethodId(method_idx).class_idx_; mirror::Class* method_reference_class = - referring_class->GetDexCache()->GetResolvedType(method_type_idx); + referrer->GetDexCache()->GetResolvedType(method_type_idx); if (method_reference_class == nullptr) { // Need to do full type resolution... return nullptr; diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 03ef962f05..4ea1130947 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -557,8 +557,10 @@ void Instrumentation::RemoveListener(InstrumentationListener* listener, uint32_t } Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const { - if (interpreter_stubs_installed_) { + if (interpreter_stubs_installed_ && interpret_only_) { return InstrumentationLevel::kInstrumentWithInterpreter; + } else if (interpreter_stubs_installed_) { + return InstrumentationLevel::kInstrumentWithInterpreterAndJit; } else if (entry_exit_stubs_installed_) { return InstrumentationLevel::kInstrumentWithInstrumentationStubs; } else { @@ -566,6 +568,14 @@ Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentation } } +bool Instrumentation::RequiresInstrumentationInstallation(InstrumentationLevel new_level) const { + // We need to reinstall instrumentation if we go to a different level or if the current level is + // kInstrumentWithInterpreterAndJit since that level does not force all code to always use the + // interpreter and so we might have started running optimized code again. + return new_level == InstrumentationLevel::kInstrumentWithInterpreterAndJit || + GetCurrentInstrumentationLevel() != new_level; +} + void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desired_level) { // Store the instrumentation level for this key or remove it. if (desired_level == InstrumentationLevel::kInstrumentNothing) { @@ -585,8 +595,7 @@ void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desir interpret_only_ = (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) || forced_interpret_only_; - InstrumentationLevel current_level = GetCurrentInstrumentationLevel(); - if (requested_level == current_level) { + if (!RequiresInstrumentationInstallation(requested_level)) { // We're already set. return; } @@ -595,7 +604,7 @@ void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desir Locks::mutator_lock_->AssertExclusiveHeld(self); Locks::thread_list_lock_->AssertNotHeld(self); if (requested_level > InstrumentationLevel::kInstrumentNothing) { - if (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) { + if (requested_level >= InstrumentationLevel::kInstrumentWithInterpreterAndJit) { interpreter_stubs_installed_ = true; entry_exit_stubs_installed_ = true; } else { @@ -842,7 +851,8 @@ void Instrumentation::EnableDeoptimization() { void Instrumentation::DisableDeoptimization(const char* key) { CHECK_EQ(deoptimization_enabled_, true); // If we deoptimized everything, undo it. - if (interpreter_stubs_installed_) { + InstrumentationLevel level = GetCurrentInstrumentationLevel(); + if (level == InstrumentationLevel::kInstrumentWithInterpreter) { UndeoptimizeEverything(key); } // Undeoptimized selected methods. @@ -869,6 +879,14 @@ bool Instrumentation::ShouldNotifyMethodEnterExitEvents() const { return !deoptimization_enabled_ && !interpreter_stubs_installed_; } +// TODO we don't check deoptimization_enabled_ because currently there isn't really any support for +// multiple users of instrumentation. Since this is just a temporary state anyway pending work to +// ensure that the current_method doesn't get kept across suspend points this should be okay. +// TODO Remove once b/33630159 is resolved. +void Instrumentation::ReJitEverything(const char* key) { + ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreterAndJit); +} + void Instrumentation::DeoptimizeEverything(const char* key) { CHECK(deoptimization_enabled_); ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreter); diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 1e5fcf2c04..05c0aaa081 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -133,6 +133,9 @@ class Instrumentation { enum class InstrumentationLevel { kInstrumentNothing, // execute without instrumentation kInstrumentWithInstrumentationStubs, // execute with instrumentation entry/exit stubs + kInstrumentWithInterpreterAndJit, // execute with interpreter initially and later the JIT + // (if it is enabled). This level is special in that it + // always requires re-instrumentation. kInstrumentWithInterpreter // execute with interpreter }; @@ -163,6 +166,13 @@ class Instrumentation { } bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_); + // Executes everything with the interpreter/jit (if available). + void ReJitEverything(const char* key) + REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) + REQUIRES(!Locks::thread_list_lock_, + !Locks::classlinker_classes_lock_, + !deoptimized_methods_lock_); + // Executes everything with interpreter. void DeoptimizeEverything(const char* key) REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) @@ -432,9 +442,13 @@ class Instrumentation { return alloc_entrypoints_instrumented_; } - private: InstrumentationLevel GetCurrentInstrumentationLevel() const; + private: + // Returns true if moving to the given instrumentation level requires the installation of stubs. + // False otherwise. + bool RequiresInstrumentationInstallation(InstrumentationLevel new_level) const; + // Does the job of installing or removing instrumentation code within methods. // In order to support multiple clients using instrumentation at the same time, // the caller must pass a unique key (a string) identifying it so we remind which diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index 423f0543d5..b599949af4 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -251,17 +251,16 @@ static inline ObjPtr<mirror::String> ResolveString(Thread* self, } } ArtMethod* method = shadow_frame.GetMethod(); - ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass(); // MethodVerifier refuses methods with string_idx out of bounds. DCHECK_LT(string_idx.index_ % mirror::DexCache::kDexCacheStringCacheSize, - declaring_class->GetDexFile().NumStringIds()); + method->GetDexFile()->NumStringIds()); ObjPtr<mirror::String> string_ptr = - mirror::StringDexCachePair::Lookup(declaring_class->GetDexCache()->GetStrings(), + mirror::StringDexCachePair::Lookup(method->GetDexCache()->GetStrings(), string_idx.index_, mirror::DexCache::kDexCacheStringCacheSize).Read(); if (UNLIKELY(string_ptr == nullptr)) { StackHandleScope<1> hs(self); - Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache())); + Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache())); string_ptr = Runtime::Current()->GetClassLinker()->ResolveString(*method->GetDexFile(), string_idx, dex_cache); diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index f43e30dd6f..6336cddc07 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -594,6 +594,9 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions."; return nullptr; } + DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsDebuggable()) + << "Should not be using cha on debuggable apps/runs!"; + for (ArtMethod* single_impl : cha_single_implementation_list) { Runtime::Current()->GetClassHierarchyAnalysis()->AddDependency( single_impl, method, method_header); @@ -645,6 +648,69 @@ size_t JitCodeCache::CodeCacheSize() { return CodeCacheSizeLocked(); } +// This notifies the code cache that the given method has been redefined and that it should remove +// any cached information it has on the method. All threads must be suspended before calling this +// method. The compiled code for the method (if there is any) must not be in any threads call stack. +void JitCodeCache::NotifyMethodRedefined(ArtMethod* method) { + MutexLock mu(Thread::Current(), lock_); + if (method->IsNative()) { + return; + } + ProfilingInfo* info = method->GetProfilingInfo(kRuntimePointerSize); + if (info != nullptr) { + auto profile = std::find(profiling_infos_.begin(), profiling_infos_.end(), info); + DCHECK(profile != profiling_infos_.end()); + profiling_infos_.erase(profile); + } + method->SetProfilingInfo(nullptr); + ScopedCodeCacheWrite ccw(code_map_.get()); + for (auto code_iter = method_code_map_.begin(); + code_iter != method_code_map_.end(); + ++code_iter) { + if (code_iter->second == method) { + FreeCode(code_iter->first); + method_code_map_.erase(code_iter); + } + } + auto code_map = osr_code_map_.find(method); + if (code_map != osr_code_map_.end()) { + osr_code_map_.erase(code_map); + } +} + +// This invalidates old_method. Once this function returns one can no longer use old_method to +// execute code unless it is fixed up. This fixup will happen later in the process of installing a +// class redefinition. +// TODO We should add some info to ArtMethod to note that 'old_method' has been invalidated and +// shouldn't be used since it is no longer logically in the jit code cache. +// TODO We should add DCHECKS that validate that the JIT is paused when this method is entered. +void JitCodeCache::MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method) { + MutexLock mu(Thread::Current(), lock_); + // Update ProfilingInfo to the new one and remove it from the old_method. + if (old_method->GetProfilingInfo(kRuntimePointerSize) != nullptr) { + DCHECK_EQ(old_method->GetProfilingInfo(kRuntimePointerSize)->GetMethod(), old_method); + ProfilingInfo* info = old_method->GetProfilingInfo(kRuntimePointerSize); + old_method->SetProfilingInfo(nullptr); + // Since the JIT should be paused and all threads suspended by the time this is called these + // checks should always pass. + DCHECK(!info->IsInUseByCompiler()); + new_method->SetProfilingInfo(info); + info->method_ = new_method; + } + // Update method_code_map_ to point to the new method. + for (auto& it : method_code_map_) { + if (it.second == old_method) { + it.second = new_method; + } + } + // Update osr_code_map_ to point to the new method. + auto code_map = osr_code_map_.find(old_method); + if (code_map != osr_code_map_.end()) { + osr_code_map_.Put(new_method, code_map->second); + osr_code_map_.erase(old_method); + } +} + size_t JitCodeCache::CodeCacheSizeLocked() { return used_memory_for_code_; } diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index d97742d00b..b5e31769ab 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -75,6 +75,10 @@ class JitCodeCache { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_); + void NotifyMethodRedefined(ArtMethod* method) + REQUIRES(Locks::mutator_lock_) + REQUIRES(!lock_); + // Notify to the code cache that the compiler wants to use the // profiling info of `method` to drive optimizations, // and therefore ensure the returned profiling info object is not @@ -219,6 +223,11 @@ class JitCodeCache { void DisallowInlineCacheAccess() REQUIRES(!lock_); void BroadcastForInlineCacheAccess() REQUIRES(!lock_); + // Notify the code cache that the method at the pointer 'old_method' is being moved to the pointer + // 'new_method' since it is being made obsolete. + void MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method) + REQUIRES(!lock_) REQUIRES(Locks::mutator_lock_); + private: // Take ownership of maps. JitCodeCache(MemMap* code_map, diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h index 9902bb584f..9fbf2e3afa 100644 --- a/runtime/jit/profiling_info.h +++ b/runtime/jit/profiling_info.h @@ -128,7 +128,9 @@ class ProfilingInfo { const uint32_t number_of_inline_caches_; // Method this profiling info is for. - ArtMethod* const method_; + // Not 'const' as JVMTI introduces obsolete methods that we implement by creating new ArtMethods. + // See JitCodeCache::MoveObsoleteMethod. + ArtMethod* method_; // Whether the ArtMethod is currently being compiled. This flag // is implicitly guarded by the JIT code cache lock. diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h index ec265e5ab3..6f88cc5df4 100644 --- a/runtime/mirror/dex_cache.h +++ b/runtime/mirror/dex_cache.h @@ -19,7 +19,6 @@ #include "array.h" #include "art_field.h" -#include "art_method.h" #include "class.h" #include "dex_file_types.h" #include "object.h" @@ -27,6 +26,7 @@ namespace art { +class ArtMethod; struct DexCacheOffsets; class DexFile; class ImageWriter; diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 68815e7de0..57cc938ec7 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -40,6 +40,8 @@ #include "events-inl.h" #include "gc/allocation_listener.h" #include "instrumentation.h" +#include "jit/jit.h" +#include "jit/jit_code_cache.h" #include "jni_env_ext-inl.h" #include "jvmti_allocator.h" #include "mirror/class.h" @@ -53,6 +55,143 @@ namespace openjdkjvmti { using android::base::StringPrintf; +// This visitor walks thread stacks and allocates and sets up the obsolete methods. It also does +// some basic sanity checks that the obsolete method is sane. +class ObsoleteMethodStackVisitor : public art::StackVisitor { + protected: + ObsoleteMethodStackVisitor( + art::Thread* thread, + art::LinearAlloc* allocator, + const std::unordered_set<art::ArtMethod*>& obsoleted_methods, + /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps, + /*out*/bool* success, + /*out*/std::string* error_msg) + : StackVisitor(thread, + /*context*/nullptr, + StackVisitor::StackWalkKind::kIncludeInlinedFrames), + allocator_(allocator), + obsoleted_methods_(obsoleted_methods), + obsolete_maps_(obsolete_maps), + success_(success), + is_runtime_frame_(false), + error_msg_(error_msg) { + *success_ = true; + } + + ~ObsoleteMethodStackVisitor() OVERRIDE {} + + public: + // Returns true if we successfully installed obsolete methods on this thread, filling + // obsolete_maps_ with the translations if needed. Returns false and fills error_msg if we fail. + // The stack is cleaned up when we fail. + static bool UpdateObsoleteFrames( + art::Thread* thread, + art::LinearAlloc* allocator, + const std::unordered_set<art::ArtMethod*>& obsoleted_methods, + /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps, + /*out*/std::string* error_msg) REQUIRES(art::Locks::mutator_lock_) { + bool success = true; + ObsoleteMethodStackVisitor visitor(thread, + allocator, + obsoleted_methods, + obsolete_maps, + &success, + error_msg); + visitor.WalkStack(); + if (!success) { + RestoreFrames(thread, *obsolete_maps, error_msg); + return false; + } else { + return true; + } + } + + static void RestoreFrames( + art::Thread* thread ATTRIBUTE_UNUSED, + const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsolete_maps ATTRIBUTE_UNUSED, + std::string* error_msg) + REQUIRES(art::Locks::mutator_lock_) { + LOG(FATAL) << "Restoring stack frames is not yet supported. Error was: " << *error_msg; + } + + bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + art::ArtMethod* old_method = GetMethod(); + // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt + // works through runtime methods. + bool prev_was_runtime_frame_ = is_runtime_frame_; + is_runtime_frame_ = old_method->IsRuntimeMethod(); + if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) { + // The check below works since when we deoptimize we set shadow frames for all frames until a + // native/runtime transition and for those set the return PC to a function that will complete + // the deoptimization. This does leave us with the unfortunate side-effect that frames just + // below runtime frames cannot be deoptimized at the moment. + // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt + // works through runtime methods. + // TODO b/33616143 + if (!IsShadowFrame() && prev_was_runtime_frame_) { + *error_msg_ = StringPrintf("Deoptimization failed due to runtime method in stack."); + *success_ = false; + return false; + } + // We cannot ensure that the right dex file is used in inlined frames so we don't support + // redefining them. + DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition"; + // TODO We should really support intrinsic obsolete methods. + // TODO We should really support redefining intrinsics. + // We don't support intrinsics so check for them here. + DCHECK(!old_method->IsIntrinsic()); + art::ArtMethod* new_obsolete_method = nullptr; + auto obsolete_method_pair = obsolete_maps_->find(old_method); + if (obsolete_method_pair == obsolete_maps_->end()) { + // Create a new Obsolete Method and put it in the list. + art::Runtime* runtime = art::Runtime::Current(); + art::ClassLinker* cl = runtime->GetClassLinker(); + auto ptr_size = cl->GetImagePointerSize(); + const size_t method_size = art::ArtMethod::Size(ptr_size); + auto* method_storage = allocator_->Alloc(GetThread(), method_size); + if (method_storage == nullptr) { + *success_ = false; + *error_msg_ = StringPrintf("Unable to allocate storage for obsolete version of '%s'", + old_method->PrettyMethod().c_str()); + return false; + } + new_obsolete_method = new (method_storage) art::ArtMethod(); + new_obsolete_method->CopyFrom(old_method, ptr_size); + DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass()); + new_obsolete_method->SetIsObsolete(); + obsolete_maps_->insert({old_method, new_obsolete_method}); + // Update JIT Data structures to point to the new method. + art::jit::Jit* jit = art::Runtime::Current()->GetJit(); + if (jit != nullptr) { + // Notify the JIT we are making this obsolete method. It will update the jit's internal + // structures to keep track of the new obsolete method. + jit->GetCodeCache()->MoveObsoleteMethod(old_method, new_obsolete_method); + } + } else { + new_obsolete_method = obsolete_method_pair->second; + } + DCHECK(new_obsolete_method != nullptr); + SetMethod(new_obsolete_method); + } + return true; + } + + private: + // The linear allocator we should use to make new methods. + art::LinearAlloc* allocator_; + // The set of all methods which could be obsoleted. + const std::unordered_set<art::ArtMethod*>& obsoleted_methods_; + // A map from the original to the newly allocated obsolete method for frames on this thread. The + // values in this map must be added to the obsolete_methods_ (and obsolete_dex_caches_) fields of + // the redefined classes ClassExt by the caller. + std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps_; + bool* success_; + // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt + // works through runtime methods. + bool is_runtime_frame_; + std::string* error_msg_; +}; + // Moves dex data to an anonymous, read-only mmap'd region. std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location, jint data_len, @@ -76,6 +215,8 @@ std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& orig return map; } +// TODO This should handle doing multiple classes at once so we need to do less cleanup when things +// go wrong. jvmtiError Redefiner::RedefineClass(ArtJvmTiEnv* env, art::Runtime* runtime, art::Thread* self, @@ -116,6 +257,9 @@ jvmtiError Redefiner::RedefineClass(ArtJvmTiEnv* env, *error_msg = os.str(); return ERR(INVALID_CLASS_FORMAT); } + // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we + // are going to redefine. + art::jit::ScopedJitSuspend suspend_jit; // Get shared mutator lock. art::ScopedObjectAccess soa(self); art::StackHandleScope<1> hs(self); @@ -296,6 +440,107 @@ bool Redefiner::FinishRemainingAllocations( return true; } +struct CallbackCtx { + Redefiner* const r; + art::LinearAlloc* allocator; + std::unordered_map<art::ArtMethod*, art::ArtMethod*> obsolete_map; + std::unordered_set<art::ArtMethod*> obsolete_methods; + bool success; + std::string* error_msg; + + CallbackCtx(Redefiner* self, art::LinearAlloc* alloc, std::string* error) + : r(self), allocator(alloc), success(true), error_msg(error) {} +}; + +void DoRestoreObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS { + CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata); + ObsoleteMethodStackVisitor::RestoreFrames(t, data->obsolete_map, data->error_msg); +} + +void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS { + CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata); + if (data->success) { + // Don't do anything if we already failed once. + data->success = ObsoleteMethodStackVisitor::UpdateObsoleteFrames(t, + data->allocator, + data->obsolete_methods, + &data->obsolete_map, + data->error_msg); + } +} + +// This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is +// updated so they will be run. +bool Redefiner::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) { + art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking"); + art::mirror::ClassExt* ext = art_klass->GetExtData(); + CHECK(ext->GetObsoleteMethods() != nullptr); + CallbackCtx ctx(this, art_klass->GetClassLoader()->GetAllocator(), error_msg_); + // Add all the declared methods to the map + for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) { + ctx.obsolete_methods.insert(&m); + } + for (art::ArtMethod* old_method : ctx.obsolete_methods) { + if (old_method->IsIntrinsic()) { + *error_msg_ = StringPrintf("Method '%s' is intrinsic and cannot be made obsolete!", + old_method->PrettyMethod().c_str()); + return false; + } + } + { + art::MutexLock mu(self_, *art::Locks::thread_list_lock_); + art::ThreadList* list = art::Runtime::Current()->GetThreadList(); + list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx)); + if (!ctx.success) { + list->ForEach(DoRestoreObsoleteMethodsCallback, static_cast<void*>(&ctx)); + return false; + } + } + FillObsoleteMethodMap(art_klass, ctx.obsolete_map); + return true; +} + +// Fills the obsolete method map in the art_klass's extData. This is so obsolete methods are able to +// figure out their DexCaches. +void Redefiner::FillObsoleteMethodMap( + art::mirror::Class* art_klass, + const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) { + int32_t index = 0; + art::mirror::ClassExt* ext_data = art_klass->GetExtData(); + art::mirror::PointerArray* obsolete_methods = ext_data->GetObsoleteMethods(); + art::mirror::ObjectArray<art::mirror::DexCache>* obsolete_dex_caches = + ext_data->GetObsoleteDexCaches(); + int32_t num_method_slots = obsolete_methods->GetLength(); + // Find the first empty index. + for (; index < num_method_slots; index++) { + if (obsolete_methods->GetElementPtrSize<art::ArtMethod*>( + index, art::kRuntimePointerSize) == nullptr) { + break; + } + } + // Make sure we have enough space. + CHECK_GT(num_method_slots, static_cast<int32_t>(obsoletes.size() + index)); + CHECK(obsolete_dex_caches->Get(index) == nullptr); + // Fill in the map. + for (auto& obs : obsoletes) { + obsolete_methods->SetElementPtrSize(index, obs.second, art::kRuntimePointerSize); + obsolete_dex_caches->Set(index, art_klass->GetDexCache()); + index++; + } +} + +// TODO It should be possible to only deoptimize the specific obsolete methods. +// TODO ReJitEverything can (sort of) fail. In certain cases it will skip deoptimizing some frames. +// If one of these frames is an obsolete method we have a problem. b/33616143 +// TODO This shouldn't be necessary once we can ensure that the current method is not kept in +// registers across suspend points. +// TODO Pending b/33630159 +void Redefiner::EnsureObsoleteMethodsAreDeoptimized() { + art::ScopedAssertNoThreadSuspension nts("Deoptimizing everything!"); + art::instrumentation::Instrumentation* i = runtime_->GetInstrumentation(); + i->ReJitEverything("libOpenJkdJvmti - Class Redefinition"); +} + jvmtiError Redefiner::Run() { art::StackHandleScope<5> hs(self_); // TODO We might want to have a global lock (or one based on the class being redefined at least) @@ -338,6 +583,11 @@ jvmtiError Redefiner::Run() { self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative); runtime_->GetThreadList()->SuspendAll( "Final installation of redefined Class!", /*long_suspend*/true); + // TODO We need to invalidate all breakpoints in the redefined class with the debugger. + // TODO We need to deal with any instrumentation/debugger deoptimized_methods_. + // TODO We need to update all debugger MethodIDs so they note the method they point to is + // obsolete or implement some other well defined semantics. + // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods. // TODO Might want to move this into a different type. // Now we reach the part where we must do active cleanup if something fails. // TODO We should really Retry if this fails instead of simply aborting. @@ -345,7 +595,8 @@ jvmtiError Redefiner::Run() { art::ObjPtr<art::mirror::LongArray> original_dex_file_cookie(nullptr); if (!UpdateJavaDexFile(java_dex_file.Get(), new_dex_file_cookie.Get(), - &original_dex_file_cookie)) { + &original_dex_file_cookie) || + !FindAndAllocateObsoleteMethods(art_class.Get())) { // Release suspendAll runtime_->GetThreadList()->ResumeAll(); // Get back shared mutator lock as expected for return. @@ -361,23 +612,25 @@ jvmtiError Redefiner::Run() { self_->TransitionFromSuspendedToRunnable(); return result_; } - // Update the ClassObjects Keep the old DexCache (and other stuff) around so we can restore - // functions/fields. - // Verify the new Class. - // Failure then undo updates to class - // Do stack walks and allocate obsolete methods - // Shrink the obsolete method maps if possible? - // TODO find appropriate class loader. Allocate new dex files array. Pause all java treads. - // Replace dex files array. Do stack scan + allocate obsoletes. Remove array if possible. - // TODO We might want to ensure that all threads are stopped for this! - // AddDexToClassPath(); - // TODO - // Release suspendAll + // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have + // pointers to their ArtMethod's stashed in registers that they then use to attempt to hit the + // DexCache. + // TODO This can fail (leave some methods optimized) near runtime methods (including + // quick-to-interpreter transition function). + // TODO We probably don't need this at all once we have a way to ensure that the + // current_art_method is never stashed in a (physical) register by the JIT and lost to the + // stack-walker. + EnsureObsoleteMethodsAreDeoptimized(); + // TODO Verify the new Class. + // TODO Failure then undo updates to class + // TODO Shrink the obsolete method maps if possible? + // TODO find appropriate class loader. // TODO Put this into a scoped thing. runtime_->GetThreadList()->ResumeAll(); // Get back shared mutator lock as expected for return. self_->TransitionFromSuspendedToRunnable(); - // TODO Do this at a more reasonable place. + // TODO Do the dex_file_ release at a more reasonable place. This works but it muddles who really + // owns the DexFile. dex_file_.release(); return OK; } @@ -420,19 +673,24 @@ bool Redefiner::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, } const art::DexFile::ProtoId* proto_id = dex_file_->FindProtoId(method_return_idx, new_type_list); - CHECK(proto_id != nullptr || old_type_list == nullptr); // TODO Return false, cleanup. + CHECK(proto_id != nullptr || old_type_list == nullptr); const art::DexFile::MethodId* method_id = dex_file_->FindMethodId(declaring_class_id, *new_name_id, *proto_id); - CHECK(method_id != nullptr); // TODO Return false, cleanup. + CHECK(method_id != nullptr); uint32_t dex_method_idx = dex_file_->GetIndexForMethodId(*method_id); method.SetDexMethodIndex(dex_method_idx); linker->SetEntryPointsToInterpreter(&method); method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx)); method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size); method.SetDexCacheResolvedTypes(new_dex_cache->GetResolvedTypes(), image_pointer_size); + // Notify the jit that this method is redefined. + art::jit::Jit* jit = runtime_->GetJit(); + if (jit != nullptr) { + jit->GetCodeCache()->NotifyMethodRedefined(&method); + } } return true; } diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 73cfc2b69b..9d23ce445f 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -64,6 +64,8 @@ namespace openjdkjvmti { // Class that can redefine a single class's methods. +// TODO We should really make this be driven by an outside class so we can do multiple classes at +// the same time and have less required cleanup. class Redefiner { public: // Redefine the given class with the given dex data. Note this function does not take ownership of @@ -124,6 +126,14 @@ class Redefiner { // in the future. For now we will just take the memory hit. bool EnsureClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_); + // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have + // pointers to their ArtMethods stashed in registers that they then use to attempt to hit the + // DexCache. + void EnsureObsoleteMethodsAreDeoptimized() + REQUIRES(art::Locks::mutator_lock_) + REQUIRES(!art::Locks::thread_list_lock_, + !art::Locks::classlinker_classes_lock_); + art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_); // This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath. @@ -170,6 +180,13 @@ class Redefiner { bool UpdateClass(art::ObjPtr<art::mirror::Class> mclass, art::ObjPtr<art::mirror::DexCache> new_dex_cache) REQUIRES(art::Locks::mutator_lock_); + + bool FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) + REQUIRES(art::Locks::mutator_lock_); + + void FillObsoleteMethodMap(art::mirror::Class* art_klass, + const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) + REQUIRES(art::Locks::mutator_lock_); }; } // namespace openjdkjvmti diff --git a/runtime/stack.cc b/runtime/stack.cc index 3fed7c9458..f9efc0b88f 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -618,6 +618,17 @@ std::string StackVisitor::DescribeLocation() const { return result; } +void StackVisitor::SetMethod(ArtMethod* method) { + DCHECK(GetMethod() != nullptr); + if (cur_shadow_frame_ != nullptr) { + cur_shadow_frame_->SetMethod(method); + } else { + DCHECK(cur_quick_frame_ != nullptr); + CHECK(!IsInInlinedFrame()) << "We do not support setting inlined method's ArtMethod!"; + *cur_quick_frame_ = method; + } +} + static void AssertPcIsWithinQuickCode(ArtMethod* method, uintptr_t pc) REQUIRES_SHARED(Locks::mutator_lock_) { if (method->IsNative() || method->IsRuntimeMethod() || method->IsProxyMethod()) { diff --git a/runtime/stack.h b/runtime/stack.h index b1e99e5fd0..9dceb2931d 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -327,6 +327,12 @@ class ShadowFrame { } } + void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_) { + DCHECK(method != nullptr); + DCHECK(method_ != nullptr); + method_ = method; + } + ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(method_ != nullptr); return method_; @@ -610,6 +616,10 @@ class StackVisitor { ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_); + // Sets this stack frame's method pointer. This requires a full lock of the MutatorLock. This + // doesn't work with inlined methods. + void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_); + ArtMethod* GetOuterMethod() const { return *GetCurrentQuickFrame(); } diff --git a/test/914-hello-obsolescence/build b/test/914-hello-obsolescence/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/914-hello-obsolescence/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-build "$@" --experimental agents diff --git a/test/914-hello-obsolescence/expected.txt b/test/914-hello-obsolescence/expected.txt new file mode 100644 index 0000000000..83efda144d --- /dev/null +++ b/test/914-hello-obsolescence/expected.txt @@ -0,0 +1,9 @@ +hello +Not doing anything here +goodbye +hello +transforming calling function +goodbye +Hello - Transformed +Not doing anything here +Goodbye - Transformed diff --git a/test/914-hello-obsolescence/info.txt b/test/914-hello-obsolescence/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/914-hello-obsolescence/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/914-hello-obsolescence/run b/test/914-hello-obsolescence/run new file mode 100755 index 0000000000..b2f0b049a2 --- /dev/null +++ b/test/914-hello-obsolescence/run @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Copyright 2016 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. + +plugin=libopenjdkjvmtid.so +agent=libtiagentd.so +lib=tiagentd +if [[ "$@" == *"-O"* ]]; then + agent=libtiagent.so + plugin=libopenjdkjvmti.so + lib=tiagent +fi + +if [[ "$@" == *"--jvm"* ]]; then + arg="jvm" +else + arg="art" + if [[ "$@" != *"--debuggable"* ]]; then + other_args=" -Xcompiler-option --debuggable " + else + other_args="" + fi +fi + + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --runtime-option -agentpath:${agent}=914-hello-obsolescence,${arg} \ + --android-runtime-option -Xplugin:${plugin} \ + --android-runtime-option -Xfully-deoptable \ + ${other_args} \ + --args ${lib} diff --git a/test/914-hello-obsolescence/src/Main.java b/test/914-hello-obsolescence/src/Main.java new file mode 100644 index 0000000000..46266efb28 --- /dev/null +++ b/test/914-hello-obsolescence/src/Main.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 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.util.Base64; + +public class Main { + // class Transform { + // public void sayHi(Runnable r) { + // System.out.println("Hello - Transformed"); + // r.run(); + // System.out.println("Goodbye - Transformed"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" + + "KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAeAQATSGVsbG8gLSBU" + + "cmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABVHb29kYnllIC0gVHJhbnNmb3JtZWQBAAlUcmFu" + + "c2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZh" + + "L2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZh" + + "L2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAAAAACAAAA" + + "CQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQABAA0ADgABAAsAAAA7AAIA" + + "AgAAABeyAAISA7YABCu5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4ABQAWAAYA" + + "AQAPAAAAAgAQ"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQAYeAMMXgYWxoeSHAS9EWKCCtVRSAGpqZVQAwAAcAAAAHhWNBIAAAAAAAAAALACAAAR" + + "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAMAgAARAEAAKIB" + + "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAAB3AgAAfAIA" + + "AIUCAACKAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" + + "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" + + "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAJ8CAAAAAAAAAQABAAEAAACRAgAABAAAAHAQ" + + "AwAAAA4ABAACAAIAAACWAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAwBiAAAAGwEBAAAAbiAC" + + "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" + + "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" + + "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" + + "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00" + + "LjEzAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAEBAICABMQCAQHc" + + "AgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEQAAAHAAAAACAAAABwAAALQAAAADAAAAAwAAANAAAAAE" + + "AAAAAQAAAPQAAAAFAAAABQAAAPwAAAAGAAAAAQAAACQBAAABIAAAAgAAAEQBAAABEAAAAgAAAJQB" + + "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA="); + + public static void main(String[] args) { + System.loadLibrary(args[1]); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + t.sayHi(() -> { + System.out.println("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + }); + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); +} diff --git a/test/914-hello-obsolescence/src/Transform.java b/test/914-hello-obsolescence/src/Transform.java new file mode 100644 index 0000000000..8cda6cdf53 --- /dev/null +++ b/test/914-hello-obsolescence/src/Transform.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 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. + */ + +class Transform { + public void sayHi(Runnable r) { + // Use lower 'h' to make sure the string will have a different string id + // than the transformation (the transformation code is the same except + // the actual printed String, which was making the test inacurately passing + // in JIT mode when loading the string from the dex cache, as the string ids + // of the two different strings were the same). + // We know the string ids will be different because lexicographically: + // "Hello" < "LTransform;" < "hello". + System.out.println("hello"); + r.run(); + System.out.println("goodbye"); + } +} diff --git a/test/915-obsolete-2/build b/test/915-obsolete-2/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/915-obsolete-2/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-build "$@" --experimental agents diff --git a/test/915-obsolete-2/expected.txt b/test/915-obsolete-2/expected.txt new file mode 100644 index 0000000000..04aff3a6dc --- /dev/null +++ b/test/915-obsolete-2/expected.txt @@ -0,0 +1,21 @@ +Pre Start private method call +hello - private +Post Start private method call +Not doing anything here +Pre Finish private method call +goodbye - private +Post Finish private method call +Pre Start private method call +hello - private +Post Start private method call +transforming calling function +Pre Finish private method call +Goodbye - private - Transformed +Post Finish private method call +Pre Start private method call - Transformed +Hello - private - Transformed +Post Start private method call - Transformed +Not doing anything here +Pre Finish private method call - Transformed +Goodbye - private - Transformed +Post Finish private method call - Transformed diff --git a/test/915-obsolete-2/info.txt b/test/915-obsolete-2/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/915-obsolete-2/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/915-obsolete-2/run b/test/915-obsolete-2/run new file mode 100755 index 0000000000..bfe227fe2a --- /dev/null +++ b/test/915-obsolete-2/run @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Copyright 2016 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. + +plugin=libopenjdkjvmtid.so +agent=libtiagentd.so +lib=tiagentd +if [[ "$@" == *"-O"* ]]; then + agent=libtiagent.so + plugin=libopenjdkjvmti.so + lib=tiagent +fi + +if [[ "$@" == *"--jvm"* ]]; then + arg="jvm" +else + arg="art" + if [[ "$@" != *"--debuggable"* ]]; then + other_args=" -Xcompiler-option --debuggable " + else + other_args="" + fi +fi + + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --runtime-option -agentpath:${agent}=915-obsolete-2,${arg} \ + --android-runtime-option -Xplugin:${plugin} \ + --android-runtime-option -Xfully-deoptable \ + ${other_args} \ + --args ${lib} diff --git a/test/915-obsolete-2/src/Main.java b/test/915-obsolete-2/src/Main.java new file mode 100644 index 0000000000..bbeb726858 --- /dev/null +++ b/test/915-obsolete-2/src/Main.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 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.util.Base64; + +public class Main { + // class Transform { + // private void Start() { + // System.out.println("Hello - private - Transformed"); + // } + // + // private void Finish() { + // System.out.println("Goodbye - private - Transformed"); + // } + // + // public void sayHi(Runnable r) { + // System.out.println("Pre Start private method call - Transformed"); + // Start(); + // System.out.println("Post Start private method call - Transformed"); + // r.run(); + // System.out.println("Pre Finish private method call - Transformed"); + // Finish(); + // System.out.println("Post Finish private method call - Transformed"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAMgoADgAZCQAaABsIABwKAB0AHggAHwgAIAoADQAhCAAiCwAjACQIACUKAA0AJggA" + + "JwcAKAcAKQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVTdGFydAEA" + + "BkZpbmlzaAEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAApTb3VyY2VGaWxlAQAO" + + "VHJhbnNmb3JtLmphdmEMAA8AEAcAKgwAKwAsAQAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3Jt" + + "ZWQHAC0MAC4ALwEAH0dvb2RieWUgLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQBACtQcmUgU3RhcnQg" + + "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAATABABACxQb3N0IFN0YXJ0IHByaXZh" + + "dGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAcAMAwAMQAQAQAsUHJlIEZpbmlzaCBwcml2YXRl" + + "IG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQMABQAEAEALVBvc3QgRmluaXNoIHByaXZhdGUgbWV0" + + "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAEACVRyYW5zZm9ybQEAEGphdmEvbGFuZy9PYmplY3QBABBq" + + "YXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9Q" + + "cmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABJqYXZhL2xhbmcv" + + "UnVubmFibGUBAANydW4AIAANAA4AAAAAAAQAAAAPABAAAQARAAAAHQABAAEAAAAFKrcAAbEAAAAB" + + "ABIAAAAGAAEAAAABAAIAEwAQAAEAEQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEAEgAAAAoAAgAA" + + "AAMACAAEAAIAFAAQAAEAEQAAACUAAgABAAAACbIAAhIFtgAEsQAAAAEAEgAAAAoAAgAAAAcACAAI" + + "AAEAFQAWAAEAEQAAAGMAAgACAAAAL7IAAhIGtgAEKrcAB7IAAhIItgAEK7kACQEAsgACEgq2AAQq" + + "twALsgACEgy2AASxAAAAAQASAAAAIgAIAAAACwAIAAwADAANABQADgAaAA8AIgAQACYAEQAuABIA" + + "AQAXAAAAAgAY"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCM0QYTJmX+NsZXkImojgSkJtXyuew3oaXcBAAAcAAAAHhWNBIAAAAAAAAAADwEAAAX" + + "AAAAcAAAAAcAAADMAAAAAwAAAOgAAAABAAAADAEAAAcAAAAUAQAAAQAAAEwBAABwAwAAbAEAAD4C" + + "AABGAgAATgIAAG8CAACOAgAAmwIAALICAADGAgAA3AIAAPACAAAEAwAAMwMAAGEDAACPAwAAvAMA" + + "AMMDAADTAwAA1gMAANoDAADuAwAA8wMAAPwDAAABBAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" + + "EAAAABAAAAAGAAAAAAAAABEAAAAGAAAAMAIAABEAAAAGAAAAOAIAAAUAAQATAAAAAAAAAAAAAAAA" + + "AAAAAQAAAAAAAAAOAAAAAAABABYAAAABAAIAFAAAAAIAAAAAAAAAAwAAABUAAAAAAAAAAAAAAAIA" + + "AAAAAAAADwAAAAAAAAAmBAAAAAAAAAEAAQABAAAACAQAAAQAAABwEAUAAAAOAAMAAQACAAAADQQA" + + "AAkAAABiAAAAGwECAAAAbiAEABAADgAAAAMAAQACAAAAEwQAAAkAAABiAAAAGwEDAAAAbiAEABAA" + + "DgAAAAQAAgACAAAAGQQAACoAAABiAAAAGwENAAAAbiAEABAAcBACAAIAYgAAABsBCwAAAG4gBAAQ" + + "AHIQBgADAGIAAAAbAQwAAABuIAQAEABwEAEAAgBiAAAAGwEKAAAAbiAEABAADgABAAAAAwAAAAEA" + + "AAAEAAY8aW5pdD4ABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAAdSGVs" + + "bG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07ABVMamF2YS9pby9QcmludFN0" + + "cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAUTGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xh" + + "bmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AC1Qb3N0IEZpbmlzaCBwcml2YXRlIG1ldGhv" + + "ZCBjYWxsIC0gVHJhbnNmb3JtZWQALFBvc3QgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRy" + + "YW5zZm9ybWVkACxQcmUgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAr" + + "UHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAFU3RhcnQADlRyYW5z" + + "Zm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00LjEzAANvdXQAB3ByaW50bG4AA3J1bgAF" + + "c2F5SGkAAQAHDgAHAAcOhwADAAcOhwALAQAHDoc8hzyHPIcAAAADAQCAgATsAgEChAMBAqgDAwHM" + + "Aw0AAAAAAAAAAQAAAAAAAAABAAAAFwAAAHAAAAACAAAABwAAAMwAAAADAAAAAwAAAOgAAAAEAAAA" + + "AQAAAAwBAAAFAAAABwAAABQBAAAGAAAAAQAAAEwBAAABIAAABAAAAGwBAAABEAAAAgAAADACAAAC" + + "IAAAFwAAAD4CAAADIAAABAAAAAgEAAAAIAAAAQAAACYEAAAAEAAAAQAAADwEAAA="); + + public static void main(String[] args) { + System.loadLibrary(args[1]); + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + t.sayHi(() -> { + System.out.println("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + }); + t.sayHi(() -> { System.out.println("Not doing anything here"); }); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); +} diff --git a/test/915-obsolete-2/src/Transform.java b/test/915-obsolete-2/src/Transform.java new file mode 100644 index 0000000000..e914e29479 --- /dev/null +++ b/test/915-obsolete-2/src/Transform.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 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. + */ + +class Transform { + private void Start() { + System.out.println("hello - private"); + } + + private void Finish() { + System.out.println("goodbye - private"); + } + + public void sayHi(Runnable r) { + System.out.println("Pre Start private method call"); + Start(); + System.out.println("Post Start private method call"); + r.run(); + System.out.println("Pre Finish private method call"); + Finish(); + System.out.println("Post Finish private method call"); + } +} diff --git a/test/916-obsolete-jit/build b/test/916-obsolete-jit/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/916-obsolete-jit/build @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-build "$@" --experimental agents diff --git a/test/916-obsolete-jit/expected.txt b/test/916-obsolete-jit/expected.txt new file mode 100644 index 0000000000..4caefc6200 --- /dev/null +++ b/test/916-obsolete-jit/expected.txt @@ -0,0 +1,21 @@ +Pre Start private method call +hello - private +Post Start private method call +Not doing anything here +Pre Finish private method call +goodbye - private +Post Finish private method call +Pre Start private method call +hello - private +Post Start private method call +transforming calling function +Pre Finish private method call +Goodbye - private - Transformed +Post Finish private method call +pre Start private method call - Transformed +Hello - private - Transformed +post Start private method call - Transformed +Not doing anything here +pre Finish private method call - Transformed +Goodbye - private - Transformed +post Finish private method call - Transformed diff --git a/test/916-obsolete-jit/info.txt b/test/916-obsolete-jit/info.txt new file mode 100644 index 0000000000..c8b892cedd --- /dev/null +++ b/test/916-obsolete-jit/info.txt @@ -0,0 +1 @@ +Tests basic obsolete method support diff --git a/test/916-obsolete-jit/run b/test/916-obsolete-jit/run new file mode 100755 index 0000000000..25c2c07470 --- /dev/null +++ b/test/916-obsolete-jit/run @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Copyright 2016 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. + +plugin=libopenjdkjvmtid.so +agent=libtiagentd.so +lib=tiagentd +if [[ "$@" == *"-O"* ]]; then + agent=libtiagent.so + plugin=libopenjdkjvmti.so + lib=tiagent +fi + +if [[ "$@" == *"--jit"* ]]; then + other_args="" +else + other_args="--jit" +fi +if [[ "$@" == *"--jvm"* ]]; then + arg="jvm" +else + arg="art" + if [[ "$@" != *"--debuggable"* ]]; then + other_args="$other_args -Xcompiler-option --debuggable " + fi +fi + + +./default-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --runtime-option -agentpath:${agent}=915-obsolete-2,${arg} \ + --android-runtime-option -Xplugin:${plugin} \ + --android-runtime-option -Xfully-deoptable \ + ${other_args} \ + --args ${lib} diff --git a/test/916-obsolete-jit/src/Main.java b/test/916-obsolete-jit/src/Main.java new file mode 100644 index 0000000000..74eb003d5c --- /dev/null +++ b/test/916-obsolete-jit/src/Main.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2016 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.util.function.Consumer; +import java.lang.reflect.Method; +import java.util.Base64; + +public class Main { + + // import java.util.function.Consumer; + // + // class Transform { + // private void Start(Consumer<String> reporter) { + // reporter.accept("Hello - private - Transformed"); + // } + // + // private void Finish(Consumer<String> reporter) { + // reporter.accept("Goodbye - private - Transformed"); + // } + // + // public void sayHi(Runnable r, Consumer<String> reporter) { + // reporter.accept("pre Start private method call - Transformed"); + // Start(reporter); + // reporter.accept("post Start private method call - Transformed"); + // r.run(); + // reporter.accept("pre Finish private method call - Transformed"); + // Finish(reporter); + // reporter.accept("post Finish private method call - Transformed"); + // } + // } + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAMAoADQAcCAAdCwAeAB8IACAIACEKAAwAIggAIwsAJAAlCAAmCgAMACcIACgHACkH" + + "ACoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFU3RhcnQBACAoTGph" + + "dmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjspVgEACVNpZ25hdHVyZQEANChMamF2YS91dGlsL2Z1" + + "bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47KVYBAAZGaW5pc2gBAAVzYXlIaQEA" + + "NChMamF2YS9sYW5nL1J1bm5hYmxlO0xqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAEgo" + + "TGphdmEvbGFuZy9SdW5uYWJsZTtMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xh" + + "bmcvU3RyaW5nOz47KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAA4ADwEAHUhlbGxv" + + "IC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkBwArDAAsAC0BAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRy" + + "YW5zZm9ybWVkAQArcHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwA" + + "EgATAQAscG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHAC4MAC8A" + + "DwEALHByZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAWABMBAC1w" + + "b3N0IEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQBAAlUcmFuc2Zvcm0B" + + "ABBqYXZhL2xhbmcvT2JqZWN0AQAbamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0" + + "AQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADAAN" + + "AAAAAAAEAAAADgAPAAEAEAAAAB0AAQABAAAABSq3AAGxAAAAAQARAAAABgABAAAAEwACABIAEwAC" + + "ABAAAAAlAAIAAgAAAAkrEgK5AAMCALEAAAABABEAAAAKAAIAAAAVAAgAFgAUAAAAAgAVAAIAFgAT" + + "AAIAEAAAACUAAgACAAAACSsSBLkAAwIAsQAAAAEAEQAAAAoAAgAAABkACAAaABQAAAACABUAAQAX" + + "ABgAAgAQAAAAZQACAAMAAAAxLBIFuQADAgAqLLcABiwSB7kAAwIAK7kACAEALBIJuQADAgAqLLcA" + + "CiwSC7kAAwIAsQAAAAEAEQAAACIACAAAAB0ACAAeAA0AHwAVACAAGwAhACMAIgAoACMAMAAkABQA" + + "AAACABkAAQAaAAAAAgAb"); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQBc8wr9PcHqnOR61m+0kimXTSddVMToJPuYBQAAcAAAAHhWNBIAAAAAAAAAAOAEAAAc" + + "AAAAcAAAAAYAAADgAAAABAAAAPgAAAAAAAAAAAAAAAcAAAAoAQAAAQAAAGABAAAYBAAAgAEAAHoC" + + "AAB9AgAAgAIAAIgCAACOAgAAlgIAALcCAADWAgAA4wIAAAIDAAAWAwAALAMAAEADAABeAwAAfQMA" + + "AIQDAACUAwAAlwMAAJsDAACgAwAAqAMAALwDAADrAwAAGQQAAEcEAAB0BAAAeQQAAIAEAAAHAAAA" + + "CAAAAAkAAAAKAAAADQAAABAAAAAQAAAABQAAAAAAAAARAAAABQAAAGQCAAASAAAABQAAAGwCAAAR" + + "AAAABQAAAHQCAAAAAAAAAgAAAAAAAwAEAAAAAAADAA4AAAAAAAIAGgAAAAIAAAACAAAAAwAAABkA" + + "AAAEAAEAEwAAAAAAAAAAAAAAAgAAAAAAAAAPAAAAPAIAAMoEAAAAAAAAAQAAAKgEAAABAAAAuAQA" + + "AAEAAQABAAAAhwQAAAQAAABwEAQAAAAOAAMAAgACAAAAjAQAAAcAAAAbAAUAAAByIAYAAgAOAAAA" + + "AwACAAIAAACTBAAABwAAABsABgAAAHIgBgACAA4AAAAEAAMAAgAAAJoEAAAiAAAAGwAYAAAAciAG" + + "AAMAcCACADEAGwAWAAAAciAGAAMAchAFAAIAGwAXAAAAciAGAAMAcCABADEAGwAVAAAAciAGAAMA" + + "DgAAAAAAAAAAAAMAAAAAAAAAAQAAAIABAAACAAAAgAEAAAMAAACIAQAAAQAAAAIAAAACAAAAAwAE" + + "AAEAAAAEAAEoAAE8AAY8aW5pdD4ABD47KVYABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBU" + + "cmFuc2Zvcm1lZAAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07AB1M" + + "ZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9s" + + "YW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABxMamF2YS91dGlsL2Z1bmN0aW9uL0Nv" + + "bnN1bWVyAB1MamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyOwAFU3RhcnQADlRyYW5zZm9ybS5q" + + "YXZhAAFWAAJWTAADVkxMAAZhY2NlcHQAEmVtaXR0ZXI6IGphY2stNC4xOQAtcG9zdCBGaW5pc2gg" + + "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACxwb3N0IFN0YXJ0IHByaXZhdGUgbWV0" + + "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAscHJlIEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0g" + + "VHJhbnNmb3JtZWQAK3ByZSBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQA" + + "A3J1bgAFc2F5SGkABXZhbHVlABMABw4AGQEABw5pABUBAAcOaQAdAgAABw5pPGk8aTxpAAIBARsc" + + "BRcAFwwXARcLFwMCAQEbHAYXABcKFwwXARcLFwMAAAMBAICABJADAQKoAwECyAMDAegDDwAAAAAA" + + "AAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAGAAAA4AAAAAMAAAAEAAAA+AAAAAUAAAAHAAAAKAEA" + + "AAYAAAABAAAAYAEAAAMQAAACAAAAgAEAAAEgAAAEAAAAkAEAAAYgAAABAAAAPAIAAAEQAAADAAAA" + + "ZAIAAAIgAAAcAAAAegIAAAMgAAAEAAAAhwQAAAQgAAACAAAAqAQAAAAgAAABAAAAygQAAAAQAAAB" + + "AAAA4AQAAA=="); + + // A class that we can use to keep track of the output of this test. + private static class TestWatcher implements Consumer<String> { + private StringBuilder sb; + public TestWatcher() { + sb = new StringBuilder(); + } + + @Override + public void accept(String s) { + sb.append(s); + sb.append('\n'); + } + + public String getOutput() { + return sb.toString(); + } + + public void clear() { + sb = new StringBuilder(); + } + } + + public static void main(String[] args) { + System.loadLibrary(args[1]); + doTest(new Transform(), new TestWatcher()); + } + + // TODO Workaround to (1) inability to ensure that current_method is not put into a register by + // the JIT and/or (2) inability to deoptimize frames near runtime functions. + // TODO Fix one/both of these issues. + public static void doCall(Runnable r) { + r.run(); + } + + private static boolean interpreting = true; + private static boolean retry = false; + + public static void doTest(Transform t, TestWatcher w) { + // Get the methods that need to be optimized. + Method say_hi_method; + Method do_call_method; + // Figure out if we can even JIT at all. + final boolean has_jit = hasJit(); + try { + say_hi_method = Transform.class.getDeclaredMethod( + "sayHi", Runnable.class, Consumer.class); + do_call_method = Main.class.getDeclaredMethod("doCall", Runnable.class); + } catch (Exception e) { + System.out.println("Unable to find methods!"); + e.printStackTrace(); + return; + } + // Makes sure the stack is the way we want it for the test and does the redefinition. It will + // set the retry boolean to true if we need to go around again due to a bad stack. + Runnable do_redefinition = () -> { + if (has_jit && + (Main.isInterpretedFunction(say_hi_method, true) || + Main.isInterpretedFunction(do_call_method, false))) { + // Try again. We are not running the right jitted methods/cannot redefine them now. + retry = true; + } else { + // Actually do the redefinition. The stack looks good. + retry = false; + w.accept("transforming calling function"); + doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES); + } + }; + // This does nothing. + Runnable noop = () -> {}; + // This just prints something out to show we are running the Runnable. + Runnable say_nothing = () -> { w.accept("Not doing anything here"); }; + // This checks to see if we have jitted the methods we are testing. + Runnable check_interpreting = () -> { + // TODO remove the second check when we remove the doCall function. We need to check that + // both of these functions aren't being interpreted because if sayHi is the test doesn't do + // anything and if doCall is then there will be a runtime call right above the sayHi + // function preventing sayHi from being deoptimized. + interpreting = has_jit && (Main.isInterpretedFunction(say_hi_method, true) || + Main.isInterpretedFunction(do_call_method, false)); + }; + do { + w.clear(); + // Wait for the methods to be jitted + long j = 0; + do { + for (int i = 0; i < 10000; i++) { + t.sayHi(noop, w); + j++; + // Clear so that we won't OOM if we go around a few times. + w.clear(); + } + t.sayHi(check_interpreting, w); + if (j >= 1000000) { + System.out.println("FAIL: Could not make sayHi be Jitted!"); + return; + } + j++; + } while(interpreting); + // Clear output. Now we try for real. + w.clear(); + // Try and redefine. + t.sayHi(say_nothing, w); + t.sayHi(do_redefinition, w); + t.sayHi(say_nothing, w); + } while (retry); + // Print output of last run. + System.out.print(w.getOutput()); + } + + private static native boolean hasJit(); + + private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable); + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); +} diff --git a/test/916-obsolete-jit/src/Transform.java b/test/916-obsolete-jit/src/Transform.java new file mode 100644 index 0000000000..f4dcf09dc6 --- /dev/null +++ b/test/916-obsolete-jit/src/Transform.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 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.util.function.Consumer; + +class Transform { + private void Start(Consumer<String> reporter) { + reporter.accept("hello - private"); + } + + private void Finish(Consumer<String> reporter) { + reporter.accept("goodbye - private"); + } + + public void sayHi(Runnable r, Consumer<String> reporter) { + reporter.accept("Pre Start private method call"); + Start(reporter); + reporter.accept("Post Start private method call"); + // TODO Revisit with b/33616143 + // TODO Uncomment this once either b/33630159 or b/33616143 are resolved. + // r.run(); + // TODO This doCall function is a very temporary fix until we get either deoptimization near + // runtime frames working, forcing current method to be always read from the stack or both + // working. + Main.doCall(r); + reporter.accept("Pre Finish private method call"); + Finish(reporter); + reporter.accept("Post Finish private method call"); + } +} diff --git a/test/Android.bp b/test/Android.bp index 2625f56418..5a2c90230e 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -243,6 +243,9 @@ art_cc_defaults { name: "libtiagent-defaults", defaults: ["libartagent-defaults"], srcs: [ + // This is to get the IsInterpreted native method. + "common/stack_inspect.cc", + "common/runtime_state.cc", "ti-agent/common_load.cc", "ti-agent/common_helper.cc", "901-hello-ti-agent/basics.cc", diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 21331cea3e..ec1f6ba239 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -286,6 +286,9 @@ TEST_ART_BROKEN_TARGET_TESTS += \ 911-get-stack-trace \ 912-classes \ 913-heaps \ + 914-hello-obsolescence \ + 915-obsolete-2 \ + 916-obsolete-jit \ 917-fields-transformation \ ifneq (,$(filter target,$(TARGET_TYPES))) diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc index f26e122580..7451cf97de 100644 --- a/test/common/runtime_state.cc +++ b/test/common/runtime_state.cc @@ -19,6 +19,7 @@ #include "base/enums.h" #include "base/logging.h" #include "dex_file-inl.h" +#include "instrumentation.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "mirror/class-inl.h" @@ -30,6 +31,16 @@ namespace art { +// public static native boolean hasJit(); + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasJit(JNIEnv*, jclass) { + Runtime* runtime = Runtime::Current(); + return runtime != nullptr + && runtime->GetJit() != nullptr + && runtime->GetInstrumentation()->GetCurrentInstrumentationLevel() != + instrumentation::Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter; +} + // public static native boolean hasOatFile(); extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasOatFile(JNIEnv* env, jclass cls) { diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc index 4df2d470ec..df7fa20226 100644 --- a/test/common/stack_inspect.cc +++ b/test/common/stack_inspect.cc @@ -18,6 +18,7 @@ #include "base/logging.h" #include "dex_file-inl.h" +#include "jni_internal.h" #include "mirror/class-inl.h" #include "nth_caller_visitor.h" #include "oat_file.h" @@ -52,6 +53,89 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpreted(JNIEnv* env, jclas return IsInterpreted(env, klass, 1); } +// public static native boolean isInterpreted(int depth); + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpretedAt(JNIEnv* env, + jclass klass, + jint depth) { + return IsInterpreted(env, klass, depth); +} + + +// public static native boolean isInterpretedFunction(String smali); + +// TODO Remove 'allow_runtime_frames' option once we have deoptimization through runtime frames. +struct MethodIsInterpretedVisitor : public StackVisitor { + public: + MethodIsInterpretedVisitor(Thread* thread, ArtMethod* goal, bool require_deoptable) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + goal_(goal), + method_is_interpreted_(true), + method_found_(false), + prev_was_runtime_(true), + require_deoptable_(require_deoptable) {} + + virtual bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + if (goal_ == GetMethod()) { + method_is_interpreted_ = (require_deoptable_ && prev_was_runtime_) || IsShadowFrame(); + method_found_ = true; + return false; + } + prev_was_runtime_ = GetMethod()->IsRuntimeMethod(); + return true; + } + + bool IsInterpreted() { + return method_is_interpreted_; + } + + bool IsFound() { + return method_found_; + } + + private: + const ArtMethod* goal_; + bool method_is_interpreted_; + bool method_found_; + bool prev_was_runtime_; + bool require_deoptable_; +}; + +// TODO Remove 'require_deoptimizable' option once we have deoptimization through runtime frames. +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpretedFunction( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method, jboolean require_deoptimizable) { + // Return false if this seems to not be an ART runtime. + if (Runtime::Current() == nullptr) { + return JNI_FALSE; + } + if (method == nullptr) { + env->ThrowNew(env->FindClass("java/lang/NullPointerException"), "method is null!"); + return JNI_FALSE; + } + jmethodID id = env->FromReflectedMethod(method); + if (id == nullptr) { + env->ThrowNew(env->FindClass("java/lang/Error"), "Unable to interpret method argument!"); + return JNI_FALSE; + } + bool result; + bool found; + { + ScopedObjectAccess soa(env); + ArtMethod* goal = jni::DecodeArtMethod(id); + MethodIsInterpretedVisitor v(soa.Self(), goal, require_deoptimizable); + v.WalkStack(); + bool enters_interpreter = Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge( + goal->GetEntryPointFromQuickCompiledCode()); + result = (v.IsInterpreted() || enters_interpreter); + found = v.IsFound(); + } + if (!found) { + env->ThrowNew(env->FindClass("java/lang/Error"), "Unable to find given method in stack!"); + return JNI_FALSE; + } + return result; +} + // public static native void assertIsInterpreted(); extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jclass klass) { diff --git a/test/etc/default-build b/test/etc/default-build index a0408aaf79..e9e388646a 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -69,6 +69,7 @@ DEFAULT_EXPERIMENT="no-experiment" # Setup experimental flag mappings in a bash associative array. declare -A JACK_EXPERIMENTAL_ARGS +JACK_EXPERIMENTAL_ARGS["agents"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24" JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24" JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24" JACK_EXPERIMENTAL_ARGS["method-handles"]="-D jack.java.source.version=1.7 -D jack.android.min-api-level=o-b1" @@ -76,12 +77,14 @@ JACK_EXPERIMENTAL_ARGS["method-handles"]="-D jack.java.source.version=1.7 -D jac declare -A SMALI_EXPERIMENTAL_ARGS SMALI_EXPERIMENTAL_ARGS["default-methods"]="--api-level 24" SMALI_EXPERIMENTAL_ARGS["method-handles"]="--api-level 26" +SMALI_EXPERIMENTAL_ARGS["agents"]="--api-level 26" declare -A JAVAC_EXPERIMENTAL_ARGS JAVAC_EXPERIMENTAL_ARGS["default-methods"]="-source 1.8 -target 1.8" JAVAC_EXPERIMENTAL_ARGS["lambdas"]="-source 1.8 -target 1.8" JAVAC_EXPERIMENTAL_ARGS["method-handles"]="-source 1.8 -target 1.8" JAVAC_EXPERIMENTAL_ARGS[${DEFAULT_EXPERIMENT}]="-source 1.7 -target 1.7" +JAVAC_EXPERIMENTAL_ARGS["agents"]="-source 1.8 -target 1.8" while true; do if [ "x$1" = "x--dx-option" ]; then diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 3e2b16802b..ebf1e4621c 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -18,8 +18,11 @@ #include <stdio.h> +#include "art_method.h" #include "jni.h" #include "openjdkjvmti/jvmti.h" +#include "scoped_thread_state_change-inl.h" +#include "stack.h" #include "ti-agent/common_load.h" #include "utils.h" diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 38861482d2..79c17d744f 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -66,6 +66,9 @@ AgentLib agents[] = { { "911-get-stack-trace", Test911GetStackTrace::OnLoad, nullptr }, { "912-classes", Test912Classes::OnLoad, nullptr }, { "913-heaps", Test913Heaps::OnLoad, nullptr }, + { "914-hello-obsolescence", common_redefine::OnLoad, nullptr }, + { "915-obsolete-2", common_redefine::OnLoad, nullptr }, + { "916-obsolete-jit", common_redefine::OnLoad, nullptr }, { "917-fields-transformation", common_redefine::OnLoad, nullptr }, }; |