Basic obsolete methods support
Add support for executing obsolete methods following redefinitions.
This support includes methods that have been jitted. This does not add
any additional validity checks to our redefinition functions.
Note using work-arounds to ensure 916 works pending some fixes to the
compiler, deoptimizer, or both.
Test: ./test/run-test --host 914
Test: ./test/run-test --host 915
Test: ./test/run-test --host 916
Test: mma -j40 test-art-host
Test: ART_TEST_JIT=true \
ART_TEST_INTERPRETER=true mma -j40 test-art-host
Test: ./art/tools/run-jdwp-tests.sh --mode=host --variant=X64
Test: ./art/tools/run-jdwp-tests.sh --mode=host --variant=X64 --no-jit
Bug: 32369913
Bug: 33630159
Change-Id: I78ef95f484146f1fb93c37fc50f56575bdab2432
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index c537483..cc7df1c 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 ef03bb3..c78db3e 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -134,8 +134,7 @@
// 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 @@
// 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 @@
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 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,20 @@
}
inline mirror::DexCache* ArtMethod::GetDexCache() {
- DCHECK(!IsProxyMethod());
- if (UNLIKELY(IsObsolete())) {
- return GetObsoleteDexCache();
- } else {
+ if (LIKELY(!IsObsolete())) {
return GetDeclaringClass()->GetDexCache();
+ } else {
+ DCHECK(!IsProxyMethod());
+ return GetObsoleteDexCache();
+ }
+}
+
+inline mirror::StringDexCacheType* ArtMethod::GetDexCacheStrings() {
+ if (LIKELY(!IsObsolete())) {
+ return GetDeclaringClass()->GetDexCacheStrings();
+ } else {
+ DCHECK(!IsProxyMethod());
+ return GetObsoleteDexCache()->GetStrings();
}
}
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 3bc6f5d..83ed1db 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 @@
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,10 @@
ALWAYS_INLINE ArtMethod* GetDexCacheResolvedMethod(uint16_t method_index,
PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
+
+ ALWAYS_INLINE mirror::StringDexCacheType* GetDexCacheStrings()
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
ALWAYS_INLINE void SetDexCacheResolvedMethod(uint16_t method_index,
ArtMethod* new_method,
PointerSize pointer_size)
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index f6eeffc..14c9c21 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -563,7 +563,7 @@
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 @@
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 @@
}
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 @@
} 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 870d1ae..3956af4 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -557,8 +557,10 @@
}
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 @@
}
}
+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 @@
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 @@
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::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 @@
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 1e5fcf2..872efac 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -133,6 +133,9 @@
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 @@
}
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_)
@@ -435,6 +445,10 @@
private:
InstrumentationLevel GetCurrentInstrumentationLevel() const;
+ // 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 c9a5b44..02a99e7 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -249,17 +249,16 @@
}
}
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->GetDexCacheStrings(),
+ mirror::StringDexCachePair::Lookup(method->GetDexCacheStrings(),
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 93f50ad..38c4728 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -624,6 +624,38 @@
return CodeCacheSizeLocked();
}
+// 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.
+ if (old_method->GetProfilingInfo(kRuntimePointerSize) != nullptr) {
+ DCHECK_EQ(old_method->GetProfilingInfo(kRuntimePointerSize)->GetMethod(), old_method);
+ ProfilingInfo* info = old_method->GetProfilingInfo(kRuntimePointerSize);
+ // 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 30e2efb..5bfe661 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -217,6 +217,11 @@
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 9902bb5..9fbf2e3 100644
--- a/runtime/jit/profiling_info.h
+++ b/runtime/jit/profiling_info.h
@@ -128,7 +128,9 @@
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 ec265e5..6f88cc5 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 d0349b9..429409a 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -38,6 +38,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"
@@ -49,6 +51,142 @@
namespace openjdkjvmti {
+// 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) {}
+
+ ~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);
+ 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)
+ REQUIRES(art::Locks::mutator_lock_) {
+ LOG(FATAL) << "Restoring stack frames is not yet supported.";
+ }
+
+ 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()) {
+ // This 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_ = art::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_ = art::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);
+ 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 && jit->GetCodeCache()->ContainsMethod(old_method)) {
+ // Notify the JIT we are making this obsolete method. It will update it's maps and change
+ // entrypoint etc over.
+ jit->GetCodeCache()->MoveObsoleteMethod(old_method, new_obsolete_method);
+ }
+ DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass());
+ } else {
+ new_obsolete_method = obsolete_method_pair->second;
+ }
+ DCHECK(new_obsolete_method != nullptr);
+ SetMethod(new_obsolete_method);
+ }
+ *success_ = true;
+ 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,
@@ -72,6 +210,8 @@
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,
@@ -112,6 +252,8 @@
*error_msg = os.str();
return ERR(INVALID_CLASS_FORMAT);
}
+ // Stop JIT for the duration of this redefine.
+ art::jit::ScopedJitSuspend suspend_jit;
// Get shared mutator lock.
art::ScopedObjectAccess soa(self);
art::StackHandleScope<1> hs(self);
@@ -292,6 +434,109 @@
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);
+}
+
+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);
+ }
+}
+
+void Redefiner::AddAllDeclaredMethods(
+ art::mirror::Class* art_klass,
+ art::PointerSize ptr_size,
+ /*out*/std::unordered_set<art::ArtMethod*>* declared_methods) {
+ for (auto& m : art_klass->GetDeclaredMethods(ptr_size)) {
+ declared_methods->insert(&m);
+ }
+}
+
+bool Redefiner::AllocateObsoleteMethods(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_);
+ AddAllDeclaredMethods(art_klass, art::kRuntimePointerSize, &ctx.obsolete_methods);
+ for (art::ArtMethod* old_method : ctx.obsolete_methods) {
+ if (old_method->IsIntrinsic()) {
+ *error_msg_ = art::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;
+}
+
+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)
@@ -341,7 +586,8 @@
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) ||
+ !AllocateObsoleteMethods(art_class.Get())) {
// Release suspendAll
runtime_->GetThreadList()->ResumeAll();
// Get back shared mutator lock as expected for return.
@@ -357,18 +603,20 @@
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 Mingyao@ suggested we could maybe just do a retry loop instead of fixing the above.
+ // 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.
@@ -421,19 +669,23 @@
}
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);
+ if (!method.IsNative()) {
+ // Reset profiling info.
+ method.SetProfilingInfo(nullptr);
+ }
}
// Update the class fields.
// Need to update class last since the ArtMethod gets its DexFile from the class (which is needed
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index c819acd..2e3df0c 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,15 @@
// 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.
+ // TODO Make this fallible
+ 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.
@@ -162,6 +173,17 @@
bool UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
art::ObjPtr<art::mirror::DexCache> new_dex_cache)
REQUIRES(art::Locks::mutator_lock_);
+
+ bool AllocateObsoleteMethods(art::mirror::Class* art_klass) REQUIRES(art::Locks::mutator_lock_);
+
+ void AddAllDeclaredMethods(art::mirror::Class* art_klass,
+ art::PointerSize ptr_size,
+ /*out*/std::unordered_set<art::ArtMethod*>* declared_methods)
+ REQUIRES_SHARED(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 792da88..726ca2f 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -614,6 +614,23 @@
return result;
}
+static instrumentation::InstrumentationStackFrame& GetInstrumentationStackFrame(Thread* thread,
+ uint32_t depth) {
+ CHECK_LT(depth, thread->GetInstrumentationStack()->size());
+ return thread->GetInstrumentationStack()->at(depth);
+}
+
+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()) {
@@ -775,8 +792,8 @@
void StackVisitor::WalkStack(bool include_transitions) {
DCHECK(thread_ == Thread::Current() || thread_->IsSuspended());
CHECK_EQ(cur_depth_, 0U);
+ size_t instrumentation_stack_depth = 0;
bool exit_stubs_installed = Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled();
- uint32_t instrumentation_stack_depth = 0;
size_t inlined_frames_count = 0;
for (const ManagedStack* current_fragment = thread_->GetManagedStack();
@@ -839,7 +856,7 @@
if (reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) == return_pc) {
CHECK_LT(instrumentation_stack_depth, thread_->GetInstrumentationStack()->size());
const instrumentation::InstrumentationStackFrame& instrumentation_frame =
- thread_->GetInstrumentationStack()->at(instrumentation_stack_depth);
+ GetInstrumentationStackFrame(thread_, instrumentation_stack_depth);
instrumentation_stack_depth++;
if (GetMethod() ==
Runtime::Current()->GetCalleeSaveMethod(Runtime::kSaveAllCalleeSaves)) {
diff --git a/runtime/stack.h b/runtime/stack.h
index b1e99e5..9dceb29 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -327,6 +327,12 @@
}
}
+ 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 @@
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 0000000..898e2e5
--- /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 0000000..83efda1
--- /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 0000000..c8b892c
--- /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 0000000..b2f0b04
--- /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 0000000..46266ef
--- /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 0000000..8cda6cd
--- /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 0000000..898e2e5
--- /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 0000000..04aff3a
--- /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 0000000..c8b892c
--- /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 0000000..bfe227f
--- /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 0000000..bbeb726
--- /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 0000000..e914e29
--- /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 0000000..898e2e5
--- /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 0000000..04aff3a
--- /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 0000000..c8b892c
--- /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 0000000..25c2c07
--- /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 0000000..d4fa667
--- /dev/null
+++ b/test/916-obsolete-jit/src/Main.java
@@ -0,0 +1,142 @@
+/*
+ * 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 {
+ private static boolean do_print = false;
+
+ public static boolean doPrint() {
+ return do_print;
+ }
+
+ // Since the transformed code isn't really an issue this is the same as test 915.
+ // 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());
+ }
+
+ // 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;
+ public static void doTest(Transform t) {
+ do_print = false;
+ Runnable noop = () -> {};
+ long j = 0;
+ do {
+ for (int i = 0; i < 10000; i++) {
+ t.sayHi(noop);
+ j++;
+ }
+ t.sayHi(() -> {
+ // TODO 2 is wrong It is checking a piece of this lambda method but should be fine due to
+ // how we implement lambdas. Be sure to fix before really submitting.
+ interpreting = Main.isInterpretedFunction("LTransform;->sayHi()V");
+ });
+ if (j >= 1000000) {
+ System.out.println("FAIL: Could not make sayHi be Jitted!");
+ return;
+ }
+ j++;
+ } while(interpreting);
+ // Start printing.
+ do_print = true;
+ t.sayHi(() -> {
+ System.out.println("Not doing anything here");
+ });
+ t.sayHi(() -> {
+ if (Main.isInterpretedFunction("LTransform;->sayHi()V")) {
+ System.out.println("FAIL: sayHi is being interpreted!");
+ }
+ System.out.println("transforming calling function");
+ doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+ });
+ t.sayHi(() -> { System.out.println("Not doing anything here"); });
+ }
+
+ private static native boolean isInterpretedFunction(String name);
+
+ // 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 0000000..a22f389
--- /dev/null
+++ b/test/916-obsolete-jit/src/Transform.java
@@ -0,0 +1,52 @@
+/*
+ * 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() {
+ if (Main.doPrint()) {
+ System.out.println("hello - private");
+ }
+ }
+
+ private void Finish() {
+ if (Main.doPrint()) {
+ System.out.println("goodbye - private");
+ }
+ }
+
+ public void sayHi(Runnable r) {
+ if (Main.doPrint()) {
+ System.out.println("Pre Start private method call");
+ }
+ Start();
+ if (Main.doPrint()) {
+ System.out.println("Post Start private method call");
+ }
+ // TODO Revist with b/33616143
+ // TODO Uncomment this
+ // r.run();
+ // TODO This 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);
+ if (Main.doPrint()) {
+ System.out.println("Pre Finish private method call");
+ }
+ Finish();
+ if (Main.doPrint()) {
+ System.out.println("Post Finish private method call");
+ }
+ }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 2625f56..c42ffc9 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -243,6 +243,8 @@
name: "libtiagent-defaults",
defaults: ["libartagent-defaults"],
srcs: [
+ // This is to get the IsInterpreted native method.
+ "common/stack_inspect.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 2f1ca6d..7cdca62 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -280,6 +280,9 @@
911-get-stack-trace \
912-classes \
913-heaps \
+ 914-hello-obsolescence \
+ 915-obsolete-2 \
+ 916-obsolete-jit \
ifneq (,$(filter target,$(TARGET_TYPES)))
ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \
diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc
index 4df2d47..eefaabc 100644
--- a/test/common/stack_inspect.cc
+++ b/test/common/stack_inspect.cc
@@ -52,6 +52,53 @@
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);
+
+struct MethodIsInterpretedVisitor : public StackVisitor {
+ public:
+ MethodIsInterpretedVisitor(Thread* thread, std::string shorty)
+ : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ shorty_(shorty),
+ method_is_interpreted_(false) {}
+
+ virtual bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!GetMethod()->IsRuntimeMethod() && shorty_ == GetMethod()->GetShorty()) {
+ method_is_interpreted_ = IsShadowFrame();
+ return false;
+ }
+ return true;
+ }
+
+ bool IsInterpreted() {
+ return method_is_interpreted_;
+ }
+
+ private:
+ const std::string shorty_;
+ bool method_is_interpreted_;
+};
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpretedFunction(JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jstring name) {
+ if (Runtime::Current() == nullptr) {
+ return JNI_TRUE;
+ }
+ ScopedObjectAccess soa(env);
+ MethodIsInterpretedVisitor v(soa.Self(), soa.Decode<mirror::String>(name)->ToModifiedUtf8());
+ v.WalkStack();
+ return v.IsInterpreted();
+}
+
// 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 51ae175..faa0813 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -69,6 +69,7 @@
# 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 @@
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 3e2b168..ebf1e46 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 2795cbc..826c5ad 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -66,6 +66,8 @@
{ "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 },
};
static AgentLib* FindAgent(char* name) {