diff options
| -rw-r--r-- | runtime/openjdkjvmti/OpenjdkJvmTi.cc | 60 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_redefine.cc | 467 | ||||
| -rw-r--r-- | runtime/openjdkjvmti/ti_redefine.h | 226 | ||||
| -rw-r--r-- | test/921-hello-failure/expected.txt | 8 | ||||
| -rw-r--r-- | test/921-hello-failure/src/CommonClassDefinition.java | 27 | ||||
| -rw-r--r-- | test/921-hello-failure/src/Main.java | 21 | ||||
| -rw-r--r-- | test/921-hello-failure/src/MultiRedef.java | 100 | ||||
| -rwxr-xr-x | test/926-multi-obsolescence/build | 17 | ||||
| -rw-r--r-- | test/926-multi-obsolescence/expected.txt | 15 | ||||
| -rw-r--r-- | test/926-multi-obsolescence/info.txt | 2 | ||||
| -rwxr-xr-x | test/926-multi-obsolescence/run | 19 | ||||
| -rw-r--r-- | test/926-multi-obsolescence/src/CommonClassDefinition.java | 27 | ||||
| -rw-r--r-- | test/926-multi-obsolescence/src/Main.java | 128 | ||||
| -rw-r--r-- | test/926-multi-obsolescence/src/Transform.java | 30 | ||||
| -rw-r--r-- | test/926-multi-obsolescence/src/Transform2.java | 23 | ||||
| -rw-r--r-- | test/Android.run-test.mk | 2 | ||||
| -rw-r--r-- | test/ti-agent/common_helper.cc | 107 | ||||
| -rw-r--r-- | test/ti-agent/common_load.cc | 1 |
18 files changed, 983 insertions, 297 deletions
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 03903841e5..7b24ec4969 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -632,7 +632,17 @@ class JvmtiFunctions { static jvmtiError RedefineClasses(jvmtiEnv* env, jint class_count, const jvmtiClassDefinition* class_definitions) { - return ERR(NOT_IMPLEMENTED); + std::string error_msg; + jvmtiError res = Redefiner::RedefineClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env), + art::Runtime::Current(), + art::Thread::Current(), + class_count, + class_definitions, + &error_msg); + if (res != OK) { + LOG(WARNING) << "FAILURE TO REDEFINE " << error_msg; + } + return res; } static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr) { @@ -1180,34 +1190,6 @@ class JvmtiFunctions { return RetransformClassesWithHook(reinterpret_cast<ArtJvmTiEnv*>(env), classes, hook); } - static jvmtiError RedefineClassDirect(ArtJvmTiEnv* env, - jclass klass, - jint dex_size, - unsigned char* dex_file) { - if (!IsValidEnv(env)) { - return ERR(INVALID_ENVIRONMENT); - } - jvmtiError ret = OK; - std::string location; - if ((ret = GetClassLocation(env, klass, &location)) != OK) { - // TODO Do something more here? Maybe give log statements? - return ret; - } - std::string error; - ret = Redefiner::RedefineClass(env, - art::Runtime::Current(), - art::Thread::Current(), - klass, - location, - dex_size, - reinterpret_cast<uint8_t*>(dex_file), - &error); - if (ret != OK) { - LOG(WARNING) << "FAILURE TO REDEFINE " << error; - } - return ret; - } - // TODO This will be called by the event handler for the art::ti Event Load Event static jvmtiError RetransformClassesWithHook(ArtJvmTiEnv* env, const std::vector<jclass>& classes, @@ -1252,14 +1234,13 @@ class JvmtiFunctions { /*out*/&new_dex_data); // Check if anything actually changed. if ((new_data_len != 0 || new_dex_data != nullptr) && new_dex_data != dex_data) { - res = Redefiner::RedefineClass(env, - art::Runtime::Current(), - art::Thread::Current(), - klass, - location, - new_data_len, - new_dex_data, - &error); + jvmtiClassDefinition def = { klass, new_data_len, new_dex_data }; + res = Redefiner::RedefineClasses(env, + art::Runtime::Current(), + art::Thread::Current(), + 1, + &def, + &error); env->Deallocate(new_dex_data); } // Deallocate the old dex data. @@ -1318,10 +1299,7 @@ const jvmtiInterface_1 gJvmtiInterface = { reinterpret_cast<void*>(JvmtiFunctions::RetransformClassWithHook), // nullptr, // reserved1 JvmtiFunctions::SetEventNotificationMode, - // SPECIAL FUNCTION: RedefineClassDirect Is normally reserved3 - // TODO Remove once we have events working. - reinterpret_cast<void*>(JvmtiFunctions::RedefineClassDirect), - // nullptr, // reserved3 + nullptr, // reserved3 JvmtiFunctions::GetAllThreads, JvmtiFunctions::SuspendThread, JvmtiFunctions::ResumeThread, diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 5bf844564c..14477a1db9 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -53,6 +53,7 @@ #include "object_lock.h" #include "runtime.h" #include "ScopedLocalRef.h" +#include "transform.h" namespace openjdkjvmti { @@ -207,7 +208,7 @@ jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> // 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, - unsigned char* dex_data, + const unsigned char* dex_data, std::string* error_msg) { std::unique_ptr<art::MemMap> map(art::MemMap::MapAnonymous( StringPrintf("%s-transformed", original_location.c_str()).c_str(), @@ -227,34 +228,87 @@ std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& orig return map; } +Redefiner::ClassRedefinition::ClassRedefinition(Redefiner* driver, + jclass klass, + const art::DexFile* redefined_dex_file, + const char* class_sig) : + driver_(driver), klass_(klass), dex_file_(redefined_dex_file), class_sig_(class_sig) { + GetMirrorClass()->MonitorEnter(driver_->self_); +} + +Redefiner::ClassRedefinition::~ClassRedefinition() { + if (driver_ != nullptr) { + GetMirrorClass()->MonitorExit(driver_->self_); + } +} + // 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, - jclass klass, - const std::string& original_dex_location, - jint data_len, - unsigned char* dex_data, - std::string* error_msg) { +jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env, + art::Runtime* runtime, + art::Thread* self, + jint class_count, + const jvmtiClassDefinition* definitions, + std::string* error_msg) { + if (env == nullptr) { + *error_msg = "env was null!"; + return ERR(INVALID_ENVIRONMENT); + } else if (class_count < 0) { + *error_msg = "class_count was less then 0"; + return ERR(ILLEGAL_ARGUMENT); + } else if (class_count == 0) { + // We don't actually need to do anything. Just return OK. + return OK; + } else if (definitions == nullptr) { + *error_msg = "null definitions!"; + return ERR(NULL_POINTER); + } + // 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 so we can lock all the classes. + art::ScopedObjectAccess soa(self); + std::vector<Redefiner::ClassRedefinition> redefinitions; + redefinitions.reserve(class_count); + Redefiner r(runtime, self, error_msg); + for (jint i = 0; i < class_count; i++) { + jvmtiError res = r.AddRedefinition(env, definitions[i]); + if (res != OK) { + return res; + } + } + return r.Run(); +} + +jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) { + std::string original_dex_location; + jvmtiError ret = OK; + if ((ret = GetClassLocation(env, def.klass, &original_dex_location))) { + *error_msg_ = "Unable to get original dex file location!"; + return ret; + } std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location, - data_len, - dex_data, - error_msg)); + def.class_byte_count, + def.class_bytes, + error_msg_)); std::ostringstream os; char* generic_ptr_unused = nullptr; char* signature_ptr = nullptr; - if (env->GetClassSignature(klass, &signature_ptr, &generic_ptr_unused) != OK) { - signature_ptr = const_cast<char*>("<UNKNOWN CLASS>"); + if (env->GetClassSignature(def.klass, &signature_ptr, &generic_ptr_unused) != OK) { + *error_msg_ = "A jclass passed in does not seem to be valid"; + return ERR(INVALID_CLASS); } + // These will make sure we deallocate the signature. + JvmtiUniquePtr sig_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr)); + JvmtiUniquePtr generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused)); if (map.get() == nullptr) { os << "Failed to create anonymous mmap for modified dex file of class " << signature_ptr - << "in dex file " << original_dex_location << " because: " << *error_msg; - *error_msg = os.str(); + << "in dex file " << original_dex_location << " because: " << *error_msg_; + *error_msg_ = os.str(); return ERR(OUT_OF_MEMORY); } if (map->Size() < sizeof(art::DexFile::Header)) { - *error_msg = "Could not read dex file header because dex_data was too short"; + *error_msg_ = "Could not read dex file header because dex_data was too short"; return ERR(INVALID_CLASS_FORMAT); } uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_; @@ -263,28 +317,21 @@ jvmtiError Redefiner::RedefineClass(ArtJvmTiEnv* env, std::move(map), /*verify*/true, /*verify_checksum*/true, - error_msg)); + error_msg_)); if (dex_file.get() == nullptr) { - os << "Unable to load modified dex file for " << signature_ptr << ": " << *error_msg; - *error_msg = os.str(); + os << "Unable to load modified dex file for " << signature_ptr << ": " << *error_msg_; + *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); - Redefiner r(runtime, self, klass, signature_ptr, dex_file, error_msg); - // Lock around this class to avoid races. - art::ObjectLock<art::mirror::Class> lock(self, hs.NewHandle(r.GetMirrorClass())); - return r.Run(); + redefinitions_.push_back( + Redefiner::ClassRedefinition(this, def.klass, dex_file.release(), signature_ptr)); + return OK; } // TODO *MAJOR* This should return the actual source java.lang.DexFile object for the klass. // TODO Make mirror of DexFile and associated types to make this less hellish. // TODO Make mirror of BaseDexClassLoader and associated types to make this less hellish. -art::mirror::Object* Redefiner::FindSourceDexFileObject( +art::mirror::Object* Redefiner::ClassRedefinition::FindSourceDexFileObject( art::Handle<art::mirror::ClassLoader> loader) { const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;"; const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;"; @@ -292,14 +339,14 @@ art::mirror::Object* Redefiner::FindSourceDexFileObject( const char* dex_path_list_name = "Ldalvik/system/DexPathList;"; const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;"; - CHECK(!self_->IsExceptionPending()); - art::StackHandleScope<11> hs(self_); - art::ClassLinker* class_linker = runtime_->GetClassLinker(); + CHECK(!driver_->self_->IsExceptionPending()); + art::StackHandleScope<11> hs(driver_->self_); + art::ClassLinker* class_linker = driver_->runtime_->GetClassLinker(); art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>( nullptr)); art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass( - self_, dex_class_loader_name, null_loader))); + driver_->self_, dex_class_loader_name, null_loader))); // Get all the ArtFields so we can look in the BaseDexClassLoader art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField( @@ -307,12 +354,12 @@ art::mirror::Object* Redefiner::FindSourceDexFileObject( CHECK(path_list_field != nullptr); art::ArtField* dex_path_list_element_field = - class_linker->FindClass(self_, dex_path_list_name, null_loader) + class_linker->FindClass(driver_->self_, dex_path_list_name, null_loader) ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name); CHECK(dex_path_list_element_field != nullptr); art::ArtField* element_dex_file_field = - class_linker->FindClass(self_, dex_path_list_element_name, null_loader) + class_linker->FindClass(driver_->self_, dex_path_list_element_name, null_loader) ->FindDeclaredInstanceField("dexFile", dex_file_name); CHECK(element_dex_file_field != nullptr); @@ -327,11 +374,11 @@ art::mirror::Object* Redefiner::FindSourceDexFileObject( art::Handle<art::mirror::Object> path_list( hs.NewHandle(path_list_field->GetObject(loader.Get()))); CHECK(path_list.Get() != nullptr); - CHECK(!self_->IsExceptionPending()); + CHECK(!driver_->self_->IsExceptionPending()); art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle( dex_path_list_element_field->GetObject(path_list.Get())-> AsObjectArray<art::mirror::Object>())); - CHECK(!self_->IsExceptionPending()); + CHECK(!driver_->self_->IsExceptionPending()); CHECK(dex_elements_list.Get() != nullptr); size_t num_elements = dex_elements_list->GetLength(); art::MutableHandle<art::mirror::Object> current_element( @@ -343,9 +390,9 @@ art::mirror::Object* Redefiner::FindSourceDexFileObject( for (size_t i = 0; i < num_elements; i++) { current_element.Assign(dex_elements_list->Get(i)); CHECK(current_element.Get() != nullptr); - CHECK(!self_->IsExceptionPending()); + CHECK(!driver_->self_->IsExceptionPending()); CHECK(dex_elements_list.Get() != nullptr); - CHECK_EQ(current_element->GetClass(), class_linker->FindClass(self_, + CHECK_EQ(current_element->GetClass(), class_linker->FindClass(driver_->self_, dex_path_list_element_name, null_loader)); // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class @@ -360,22 +407,23 @@ art::mirror::Object* Redefiner::FindSourceDexFileObject( return nullptr; } -art::mirror::Class* Redefiner::GetMirrorClass() { - return self_->DecodeJObject(klass_)->AsClass(); +art::mirror::Class* Redefiner::ClassRedefinition::GetMirrorClass() { + return driver_->self_->DecodeJObject(klass_)->AsClass(); } -art::mirror::ClassLoader* Redefiner::GetClassLoader() { +art::mirror::ClassLoader* Redefiner::ClassRedefinition::GetClassLoader() { return GetMirrorClass()->GetClassLoader(); } -art::mirror::DexCache* Redefiner::CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader) { - return runtime_->GetClassLinker()->RegisterDexFile(*dex_file_, loader.Get()); +art::mirror::DexCache* Redefiner::ClassRedefinition::CreateNewDexCache( + art::Handle<art::mirror::ClassLoader> loader) { + return driver_->runtime_->GetClassLinker()->RegisterDexFile(*dex_file_, loader.Get()); } // TODO Really wishing I had that mirror of java.lang.DexFile now. -art::mirror::LongArray* Redefiner::AllocateDexFileCookie( +art::mirror::LongArray* Redefiner::ClassRedefinition::AllocateDexFileCookie( art::Handle<art::mirror::Object> java_dex_file_obj) { - art::StackHandleScope<2> hs(self_); + art::StackHandleScope<2> hs(driver_->self_); // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until // the object is finalized. Since they always point to the same array if mCookie is not null we // just use the mInternalCookie field. We will update one or both of these fields later. @@ -390,9 +438,9 @@ art::mirror::LongArray* Redefiner::AllocateDexFileCookie( CHECK(cookie.Get() != nullptr); CHECK_GE(cookie->GetLength(), 1); art::Handle<art::mirror::LongArray> new_cookie( - hs.NewHandle(art::mirror::LongArray::Alloc(self_, cookie->GetLength() + 1))); + hs.NewHandle(art::mirror::LongArray::Alloc(driver_->self_, cookie->GetLength() + 1))); if (new_cookie.Get() == nullptr) { - self_->AssertPendingOOMException(); + driver_->self_->AssertPendingOOMException(); return nullptr; } // Copy the oat-dex field at the start. @@ -405,19 +453,21 @@ art::mirror::LongArray* Redefiner::AllocateDexFileCookie( return new_cookie.Get(); } -void Redefiner::RecordFailure(jvmtiError result, const std::string& error_msg) { +void Redefiner::RecordFailure(jvmtiError result, + const std::string& class_sig, + const std::string& error_msg) { *error_msg_ = StringPrintf("Unable to perform redefinition of '%s': %s", - class_sig_, + class_sig.c_str(), error_msg.c_str()); result_ = result; } -bool Redefiner::FinishRemainingAllocations( +bool Redefiner::ClassRedefinition::FinishRemainingAllocations( /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader, /*out*/art::MutableHandle<art::mirror::Object>* java_dex_file_obj, /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie, /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) { - art::StackHandleScope<4> hs(self_); + art::StackHandleScope<4> hs(driver_->self_); // This shouldn't allocate art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader())); if (loader.Get() == nullptr) { @@ -433,15 +483,15 @@ bool Redefiner::FinishRemainingAllocations( } art::Handle<art::mirror::LongArray> new_cookie(hs.NewHandle(AllocateDexFileCookie(dex_file_obj))); if (new_cookie.Get() == nullptr) { - self_->AssertPendingOOMException(); - self_->ClearException(); + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); return false; } art::Handle<art::mirror::DexCache> dex_cache(hs.NewHandle(CreateNewDexCache(loader))); if (dex_cache.Get() == nullptr) { - self_->AssertPendingOOMException(); - self_->ClearException(); + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache"); return false; } @@ -453,13 +503,11 @@ bool Redefiner::FinishRemainingAllocations( } struct CallbackCtx { - Redefiner* const r; art::LinearAlloc* allocator; std::unordered_map<art::ArtMethod*, art::ArtMethod*> obsolete_map; std::unordered_set<art::ArtMethod*> obsolete_methods; - CallbackCtx(Redefiner* self, art::LinearAlloc* alloc) - : r(self), allocator(alloc) {} + explicit CallbackCtx(art::LinearAlloc* alloc) : allocator(alloc) {} }; void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS { @@ -472,11 +520,12 @@ void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SA // This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is // updated so they will be run. -void Redefiner::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) { +// TODO Rewrite so we can do this only once regardless of how many redefinitions there are. +void Redefiner::ClassRedefinition::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()); + CallbackCtx ctx(art_klass->GetClassLoader()->GetAllocator()); // Add all the declared methods to the map for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) { ctx.obsolete_methods.insert(&m); @@ -484,7 +533,7 @@ void Redefiner::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) { DCHECK(!m.IsIntrinsic()); } { - art::MutexLock mu(self_, *art::Locks::thread_list_lock_); + art::MutexLock mu(driver_->self_, *art::Locks::thread_list_lock_); art::ThreadList* list = art::Runtime::Current()->GetThreadList(); list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx)); } @@ -493,7 +542,7 @@ void Redefiner::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) { // 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( +void Redefiner::ClassRedefinition::FillObsoleteMethodMap( art::mirror::Class* art_klass, const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) { int32_t index = 0; @@ -532,9 +581,9 @@ void Redefiner::EnsureObsoleteMethodsAreDeoptimized() { i->ReJitEverything("libOpenJkdJvmti - Class Redefinition"); } -bool Redefiner::CheckClass() { +bool Redefiner::ClassRedefinition::CheckClass() { // TODO Might just want to put it in a ObjPtr and NoSuspend assert. - art::StackHandleScope<1> hs(self_); + art::StackHandleScope<1> hs(driver_->self_); // Easy check that only 1 class def is present. if (dex_file_->NumClassDefs() != 1) { RecordFailure(ERR(ILLEGAL_ARGUMENT), @@ -607,14 +656,15 @@ bool Redefiner::CheckClass() { } } LOG(WARNING) << "No verification is done on annotations of redefined classes."; + LOG(WARNING) << "Bytecodes of redefinitions are not verified."; return true; } // TODO Move this to use IsRedefinable when that function is made. -bool Redefiner::CheckRedefinable() { +bool Redefiner::ClassRedefinition::CheckRedefinable() { std::string err; - art::StackHandleScope<1> hs(self_); + art::StackHandleScope<1> hs(driver_->self_); art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass())); jvmtiError res = Redefiner::GetClassRedefinitionError(h_klass, &err); @@ -626,46 +676,200 @@ bool Redefiner::CheckRedefinable() { } } -bool Redefiner::CheckRedefinitionIsValid() { +bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() { return CheckRedefinable() && CheckClass() && CheckSameFields() && CheckSameMethods(); } -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) - // in order to make cleanup easier. Not a huge deal though. - // - // First we just allocate the ClassExt and its fields that we need. These can be updated - // atomically without any issues (since we allocate the map arrays as empty) so we don't bother - // doing a try loop. The other allocations we need to ensure that nothing has changed in the time - // between allocating them and pausing all threads before we can update them so we need to do a - // try loop. - if (!CheckRedefinitionIsValid() || !EnsureClassAllocationsFinished()) { - return result_; +// A wrapper that lets us hold onto the arbitrary sized data needed for redefinitions in a +// reasonably sane way. This adds no fields to the normal ObjectArray. By doing this we can avoid +// having to deal with the fact that we need to hold an arbitrary number of references live. +class RedefinitionDataHolder { + public: + enum DataSlot : int32_t { + kSlotSourceClassLoader = 0, + kSlotJavaDexFile = 1, + kSlotNewDexFileCookie = 2, + kSlotNewDexCache = 3, + kSlotMirrorClass = 4, + + // Must be last one. + kNumSlots = 5, + }; + + // This needs to have a HandleScope passed in that is capable of creating a new Handle without + // overflowing. Only one handle will be created. This object has a lifetime identical to that of + // the passed in handle-scope. + RedefinitionDataHolder(art::StackHandleScope<1>* hs, + art::Runtime* runtime, + art::Thread* self, + int32_t num_redefinitions) REQUIRES_SHARED(art::Locks::mutator_lock_) : + arr_( + hs->NewHandle( + art::mirror::ObjectArray<art::mirror::Object>::Alloc( + self, + runtime->GetClassLinker()->GetClassRoot(art::ClassLinker::kObjectArrayClass), + num_redefinitions * kNumSlots))) {} + + bool IsNull() const REQUIRES_SHARED(art::Locks::mutator_lock_) { + return arr_.IsNull(); + } + + // TODO Maybe make an iterable view type to simplify using this. + art::mirror::ClassLoader* GetSourceClassLoader(jint klass_index) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return art::down_cast<art::mirror::ClassLoader*>(GetSlot(klass_index, kSlotSourceClassLoader)); + } + art::mirror::Object* GetJavaDexFile(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) { + return GetSlot(klass_index, kSlotJavaDexFile); + } + art::mirror::LongArray* GetNewDexFileCookie(jint klass_index) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return art::down_cast<art::mirror::LongArray*>(GetSlot(klass_index, kSlotNewDexFileCookie)); + } + art::mirror::DexCache* GetNewDexCache(jint klass_index) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return art::down_cast<art::mirror::DexCache*>(GetSlot(klass_index, kSlotNewDexCache)); + } + art::mirror::Class* GetMirrorClass(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) { + return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass)); + } + + void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + SetSlot(klass_index, kSlotSourceClassLoader, loader); + } + void SetJavaDexFile(jint klass_index, art::mirror::Object* dexfile) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + SetSlot(klass_index, kSlotJavaDexFile, dexfile); + } + void SetNewDexFileCookie(jint klass_index, art::mirror::LongArray* cookie) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + SetSlot(klass_index, kSlotNewDexFileCookie, cookie); + } + void SetNewDexCache(jint klass_index, art::mirror::DexCache* cache) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + SetSlot(klass_index, kSlotNewDexCache, cache); } + void SetMirrorClass(jint klass_index, art::mirror::Class* klass) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + SetSlot(klass_index, kSlotMirrorClass, klass); + } + + int32_t Length() REQUIRES_SHARED(art::Locks::mutator_lock_) { + return arr_->GetLength() / kNumSlots; + } + + private: + art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_; + + art::mirror::Object* GetSlot(jint klass_index, + DataSlot slot) REQUIRES_SHARED(art::Locks::mutator_lock_) { + DCHECK_LT(klass_index, Length()); + return arr_->Get((kNumSlots * klass_index) + slot); + } + + void SetSlot(jint klass_index, + DataSlot slot, + art::ObjPtr<art::mirror::Object> obj) REQUIRES_SHARED(art::Locks::mutator_lock_) { + DCHECK(!art::Runtime::Current()->IsActiveTransaction()); + DCHECK_LT(klass_index, Length()); + arr_->Set<false>((kNumSlots * klass_index) + slot, obj); + } + + DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder); +}; + +bool Redefiner::CheckAllRedefinitionAreValid() { + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + if (!redef.CheckRedefinitionIsValid()) { + return false; + } + } + return true; +} + +bool Redefiner::EnsureAllClassAllocationsFinished() { + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + if (!redef.EnsureClassAllocationsFinished()) { + return false; + } + } + return true; +} + +bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) { + int32_t cnt = 0; + art::StackHandleScope<4> hs(self_); + art::MutableHandle<art::mirror::Object> java_dex_file(hs.NewHandle<art::mirror::Object>(nullptr)); art::MutableHandle<art::mirror::ClassLoader> source_class_loader( hs.NewHandle<art::mirror::ClassLoader>(nullptr)); - art::MutableHandle<art::mirror::Object> java_dex_file( - hs.NewHandle<art::mirror::Object>(nullptr)); art::MutableHandle<art::mirror::LongArray> new_dex_file_cookie( hs.NewHandle<art::mirror::LongArray>(nullptr)); art::MutableHandle<art::mirror::DexCache> new_dex_cache( hs.NewHandle<art::mirror::DexCache>(nullptr)); - if (!FinishRemainingAllocations(&source_class_loader, - &java_dex_file, - &new_dex_file_cookie, - &new_dex_cache)) { + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + // Reset the out pointers to null + source_class_loader.Assign(nullptr); + java_dex_file.Assign(nullptr); + new_dex_file_cookie.Assign(nullptr); + new_dex_cache.Assign(nullptr); + // Allocate the data this redefinition requires. + if (!redef.FinishRemainingAllocations(&source_class_loader, + &java_dex_file, + &new_dex_file_cookie, + &new_dex_cache)) { + return false; + } + // Save the allocated data into the holder. + holder.SetSourceClassLoader(cnt, source_class_loader.Get()); + holder.SetJavaDexFile(cnt, java_dex_file.Get()); + holder.SetNewDexFileCookie(cnt, new_dex_file_cookie.Get()); + holder.SetNewDexCache(cnt, new_dex_cache.Get()); + holder.SetMirrorClass(cnt, redef.GetMirrorClass()); + cnt++; + } + return true; +} + +void Redefiner::ClassRedefinition::ReleaseDexFile() { + dex_file_.release(); +} + +void Redefiner::ReleaseAllDexFiles() { + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + redef.ReleaseDexFile(); + } +} + +jvmtiError Redefiner::Run() { + art::StackHandleScope<1> hs(self_); + // Allocate an array to hold onto all java temporary objects associated with this redefinition. + // We will let this be collected after the end of this function. + RedefinitionDataHolder holder(&hs, runtime_, self_, redefinitions_.size()); + if (holder.IsNull()) { + self_->AssertPendingOOMException(); + self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate storage for temporaries"); + return result_; + } + + // First we just allocate the ClassExt and its fields that we need. These can be updated + // atomically without any issues (since we allocate the map arrays as empty) so we don't bother + // doing a try loop. The other allocations we need to ensure that nothing has changed in the time + // between allocating them and pausing all threads before we can update them so we need to do a + // try loop. + if (!CheckAllRedefinitionAreValid() || + !EnsureAllClassAllocationsFinished() || + !FinishAllRemainingAllocations(holder)) { // TODO Null out the ClassExt fields we allocated (if possible, might be racing with another // redefineclass call which made it even bigger. Leak shouldn't be huge (2x array of size - // declared_methods_.length) but would be good to get rid of. - // new_dex_file_cookie & new_dex_cache should be cleaned up by the GC. + // declared_methods_.length) but would be good to get rid of. All other allocations should be + // cleaned up by the GC eventually. return result_; } - // Get the mirror class now that we aren't allocating anymore. - art::Handle<art::mirror::Class> art_class(hs.NewHandle(GetMirrorClass())); // Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done // allocating so no deadlocks. art::gc::Heap* heap = runtime_->GetHeap(); @@ -673,31 +877,29 @@ jvmtiError Redefiner::Run() { // GC moving objects can cause deadlocks as we are deoptimizing the stack. heap->IncrementDisableMovingGC(self_); } - // Enable assertion that this thread isn't interrupted during this installation. - // After this we will need to do real cleanup in case of failure. Prior to this we could simply - // return and would let everything get cleaned up or harmlessly leaked. // Do transition to final suspension // TODO We might want to give this its own suspended state! // TODO This isn't right. We need to change state without any chance of suspend ideally! self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative); runtime_->GetThreadList()->SuspendAll( - "Final installation of redefined Class!", /*long_suspend*/true); + "Final installation of redefined Classes!", /*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. - // Set the new DexFileCookie returns the original so we can fix it back up if redefinition fails - art::ObjPtr<art::mirror::LongArray> original_dex_file_cookie(nullptr); - UpdateJavaDexFile(java_dex_file.Get(), new_dex_file_cookie.Get(), &original_dex_file_cookie); - FindAndAllocateObsoleteMethods(art_class.Get()); - UpdateClass(art_class.Get(), new_dex_cache.Get()); + int32_t cnt = 0; + for (Redefiner::ClassRedefinition& redef : redefinitions_) { + art::mirror::Class* klass = holder.GetMirrorClass(cnt); + redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt)); + // TODO Rewrite so we don't do a stack walk for each and every class. + redef.FindAndAllocateObsoleteMethods(klass); + redef.UpdateClass(klass, holder.GetNewDexCache(cnt)); + cnt++; + } // 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. + // DexCache. (b/33630159) // 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 @@ -705,26 +907,25 @@ jvmtiError Redefiner::Run() { // 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 the dex_file_ release at a more reasonable place. This works but it muddles who really - // owns the DexFile. - dex_file_.release(); + // TODO Do the dex_file release at a more reasonable place. This works but it muddles who really + // owns the DexFile and when ownership is transferred. + ReleaseAllDexFiles(); if (heap->IsGcConcurrentAndMoving()) { heap->DecrementDisableMovingGC(self_); } return OK; } -void Redefiner::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache, - const art::DexFile::ClassDef& class_def) { - art::ClassLinker* linker = runtime_->GetClassLinker(); +void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, + art::ObjPtr<art::mirror::DexCache> new_dex_cache, + const art::DexFile::ClassDef& class_def) { + art::ClassLinker* linker = driver_->runtime_->GetClassLinker(); art::PointerSize image_pointer_size = linker->GetImagePointerSize(); const art::DexFile::TypeId& declaring_class_id = dex_file_->GetTypeId(class_def.class_idx_); const art::DexFile& old_dex_file = mclass->GetDexFile(); @@ -759,14 +960,14 @@ void Redefiner::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, 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(); + art::jit::Jit* jit = driver_->runtime_->GetJit(); if (jit != nullptr) { jit->GetCodeCache()->NotifyMethodRedefined(&method); } } } -void Redefiner::UpdateFields(art::ObjPtr<art::mirror::Class> mclass) { +void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> mclass) { // TODO The IFields & SFields pointers should be combined like the methods_ arrays were. for (auto fields_iter : {mclass->GetIFields(), mclass->GetSFields()}) { for (art::ArtField& field : fields_iter) { @@ -787,10 +988,10 @@ void Redefiner::UpdateFields(art::ObjPtr<art::mirror::Class> mclass) { } // Performs updates to class that will allow us to verify it. -void Redefiner::UpdateClass(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache) { +void Redefiner::ClassRedefinition::UpdateClass(art::ObjPtr<art::mirror::Class> mclass, + art::ObjPtr<art::mirror::DexCache> new_dex_cache) { const art::DexFile::ClassDef* class_def = art::OatFile::OatDexFile::FindClassDef( - *dex_file_, class_sig_, art::ComputeModifiedUtf8Hash(class_sig_)); + *dex_file_, class_sig_.c_str(), art::ComputeModifiedUtf8Hash(class_sig_.c_str())); DCHECK(class_def != nullptr); UpdateMethods(mclass, new_dex_cache, *class_def); UpdateFields(mclass); @@ -800,12 +1001,12 @@ void Redefiner::UpdateClass(art::ObjPtr<art::mirror::Class> mclass, // to call GetReturnTypeDescriptor and GetParameterTypeList above). mclass->SetDexCache(new_dex_cache.Ptr()); mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(*class_def)); - mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_))); + mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str()))); } -void Redefiner::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, - art::ObjPtr<art::mirror::LongArray> new_cookie, - /*out*/art::ObjPtr<art::mirror::LongArray>* original_cookie) { +void Redefiner::ClassRedefinition::UpdateJavaDexFile( + art::ObjPtr<art::mirror::Object> java_dex_file, + art::ObjPtr<art::mirror::LongArray> new_cookie) { art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField( "mInternalCookie", "Ljava/lang/Object;"); art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField( @@ -816,7 +1017,6 @@ void Redefiner::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file art::ObjPtr<art::mirror::LongArray> orig_cookie( cookie_field->GetObject(java_dex_file)->AsLongArray()); internal_cookie_field->SetObject<false>(java_dex_file, new_cookie); - *original_cookie = orig_internal_cookie; if (!orig_cookie.IsNull()) { cookie_field->SetObject<false>(java_dex_file, new_cookie); } @@ -824,21 +1024,22 @@ void Redefiner::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file // This function does all (java) allocations we need to do for the Class being redefined. // TODO Change this name maybe? -bool Redefiner::EnsureClassAllocationsFinished() { - art::StackHandleScope<2> hs(self_); - art::Handle<art::mirror::Class> klass(hs.NewHandle(self_->DecodeJObject(klass_)->AsClass())); +bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished() { + art::StackHandleScope<2> hs(driver_->self_); + art::Handle<art::mirror::Class> klass(hs.NewHandle( + driver_->self_->DecodeJObject(klass_)->AsClass())); if (klass.Get() == nullptr) { RecordFailure(ERR(INVALID_CLASS), "Unable to decode class argument!"); return false; } // Allocate the classExt - art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(self_))); + art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(driver_->self_))); if (ext.Get() == nullptr) { // No memory. Clear exception (it's not useful) and return error. // TODO This doesn't need to be fatal. We could just not support obsolete methods after hitting // this case. - self_->AssertPendingOOMException(); - self_->ClearException(); + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate ClassExt"); return false; } @@ -849,12 +1050,12 @@ bool Redefiner::EnsureClassAllocationsFinished() { // TODO Clear these after we walk the stacks in order to free them in the (likely?) event there // are no obsolete methods. { - art::ObjectLock<art::mirror::ClassExt> lock(self_, ext); + art::ObjectLock<art::mirror::ClassExt> lock(driver_->self_, ext); if (!ext->ExtendObsoleteArrays( - self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) { + driver_->self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) { // OOM. Clear exception and return error. - self_->AssertPendingOOMException(); - self_->ClearException(); + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate/extend obsolete methods map"); return false; } diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 5852309291..8626bc54d5 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -63,50 +63,154 @@ namespace openjdkjvmti { +class RedefinitionDataHolder; + // 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 - // the dex_data pointer. It is not used after this call however and may be freed if desired. + // Redefine the given classes with the given dex data. Note this function does not take ownership + // of the dex_data pointers. It is not used after this call however and may be freed if desired. // The caller is responsible for freeing it. The runtime makes its own copy of the data. - static jvmtiError RedefineClass(ArtJvmTiEnv* env, - art::Runtime* runtime, - art::Thread* self, - jclass klass, - const std::string& original_dex_location, - jint data_len, - unsigned char* dex_data, - std::string* error_msg); + static jvmtiError RedefineClasses(ArtJvmTiEnv* env, + art::Runtime* runtime, + art::Thread* self, + jint class_count, + const jvmtiClassDefinition* definitions, + std::string* error_msg); static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); private: + class ClassRedefinition { + public: + ClassRedefinition(Redefiner* driver, + jclass klass, + const art::DexFile* redefined_dex_file, + const char* class_sig) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor. + ~ClassRedefinition() NO_THREAD_SAFETY_ANALYSIS; + + // Move constructor so we can put these into a vector. + ClassRedefinition(ClassRedefinition&& other) + : driver_(other.driver_), + klass_(other.klass_), + dex_file_(std::move(other.dex_file_)), + class_sig_(std::move(other.class_sig_)) { + other.driver_ = nullptr; + } + + art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_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. + // TODO Make sure the DexFile object returned is the one that the klass_ actually comes from. + art::mirror::Object* FindSourceDexFileObject(art::Handle<art::mirror::ClassLoader> loader) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + // Allocates and fills the new DexFileCookie + art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + void RecordFailure(jvmtiError e, const std::string& err) { + driver_->RecordFailure(e, class_sig_, err); + } + + bool FinishRemainingAllocations( + /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader, + /*out*/art::MutableHandle<art::mirror::Object>* source_dex_file_obj, + /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie, + /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + void 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_); + + + // Checks that the dex file contains only the single expected class and that the top-level class + // data has not been modified in an incompatible manner. + bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_); + + // Preallocates all needed allocations in klass so that we can pause execution safely. + // TODO We should be able to free the arrays if they end up not being used. Investigate doing + // this in the future. For now we will just take the memory hit. + bool EnsureClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_); + + // This will check that no constraints are violated (more than 1 class in dex file, any changes + // in number/declaration of methods & fields, changes in access flags, etc.) + bool CheckRedefinitionIsValid() REQUIRES_SHARED(art::Locks::mutator_lock_); + + // Checks that the class can even be redefined. + bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_); + + // Checks that the dex file does not add/remove methods. + bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_) { + LOG(WARNING) << "methods are not checked for modification currently"; + return true; + } + + // Checks that the dex file does not modify fields + bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_) { + LOG(WARNING) << "Fields are not checked for modification currently"; + return true; + } + + void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, + art::ObjPtr<art::mirror::LongArray> new_cookie) + REQUIRES(art::Locks::mutator_lock_); + + void UpdateFields(art::ObjPtr<art::mirror::Class> mclass) + REQUIRES(art::Locks::mutator_lock_); + + void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, + art::ObjPtr<art::mirror::DexCache> new_dex_cache, + const art::DexFile::ClassDef& class_def) + REQUIRES(art::Locks::mutator_lock_); + + void UpdateClass(art::ObjPtr<art::mirror::Class> mclass, + art::ObjPtr<art::mirror::DexCache> new_dex_cache) + REQUIRES(art::Locks::mutator_lock_); + + void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_); + + private: + Redefiner* driver_; + jclass klass_; + std::unique_ptr<const art::DexFile> dex_file_; + std::string class_sig_; + }; + jvmtiError result_; art::Runtime* runtime_; art::Thread* self_; + std::vector<ClassRedefinition> redefinitions_; // Kept as a jclass since we have weird run-state changes that make keeping it around as a // mirror::Class difficult and confusing. - jclass klass_; - std::unique_ptr<const art::DexFile> dex_file_; std::string* error_msg_; - char* class_sig_; // TODO Maybe change jclass to a mirror::Class Redefiner(art::Runtime* runtime, art::Thread* self, - jclass klass, - char* class_sig, - std::unique_ptr<const art::DexFile>& redefined_dex_file, std::string* error_msg) : result_(ERR(INTERNAL)), runtime_(runtime), self_(self), - klass_(klass), - dex_file_(std::move(redefined_dex_file)), - error_msg_(error_msg), - class_sig_(class_sig) { } + redefinitions_(), + error_msg_(error_msg) { } + + jvmtiError AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) + REQUIRES_SHARED(art::Locks::mutator_lock_); static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass, /*out*/std::string* error_msg) @@ -114,23 +218,17 @@ class Redefiner { static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, jint data_len, - unsigned char* dex_data, + const unsigned char* dex_data, std::string* error_msg); // TODO Put on all the lock qualifiers. jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_); - bool FinishRemainingAllocations( - /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader, - /*out*/art::MutableHandle<art::mirror::Object>* source_dex_file_obj, - /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie, - /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) + bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_); + bool EnsureAllClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_); + bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder) REQUIRES_SHARED(art::Locks::mutator_lock_); - - // Preallocates all needed allocations in klass so that we can pause execution safely. - // TODO We should be able to free the arrays if they end up not being used. Investigate doing this - // in the future. For now we will just take the memory hit. - bool EnsureClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_); + void ReleaseAllDexFiles() 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 @@ -140,70 +238,12 @@ class Redefiner { 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. - // TODO Make sure the DexFile object returned is the one that the klass_ actually comes from. - art::mirror::Object* FindSourceDexFileObject(art::Handle<art::mirror::ClassLoader> loader) - REQUIRES_SHARED(art::Locks::mutator_lock_); - - art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_); - - // Allocates and fills the new DexFileCookie - art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> java_dex_file_obj) - REQUIRES_SHARED(art::Locks::mutator_lock_); - - art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader) - REQUIRES_SHARED(art::Locks::mutator_lock_); - - void RecordFailure(jvmtiError result, const std::string& error_msg); - - // This will check that no constraints are violated (more than 1 class in dex file, any changes in - // number/declaration of methods & fields, changes in access flags, etc.) - bool CheckRedefinitionIsValid() REQUIRES_SHARED(art::Locks::mutator_lock_); - - // Checks that the class can even be redefined. - bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_); - - // Checks that the dex file does not add/remove methods. - bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_) { - LOG(WARNING) << "methods are not checked for modification currently"; - return true; - } - - // Checks that the dex file does not modify fields - bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_) { - LOG(WARNING) << "Fields are not checked for modification currently"; - return true; + void RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg); + void RecordFailure(jvmtiError result, const std::string& error_msg) { + RecordFailure(result, "NO CLASS", error_msg); } - // Checks that the dex file contains only the single expected class and that the top-level class - // data has not been modified in an incompatible manner. - bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_); - - void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file, - art::ObjPtr<art::mirror::LongArray> new_cookie, - /*out*/art::ObjPtr<art::mirror::LongArray>* original_cookie) - REQUIRES(art::Locks::mutator_lock_); - - void UpdateFields(art::ObjPtr<art::mirror::Class> mclass) - REQUIRES(art::Locks::mutator_lock_); - - void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache, - const art::DexFile::ClassDef& class_def) - REQUIRES(art::Locks::mutator_lock_); - - void UpdateClass(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache) - REQUIRES(art::Locks::mutator_lock_); - - void 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_); + friend struct CallbackCtx; }; } // namespace openjdkjvmti diff --git a/test/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt index e2665ef30b..1c1d4d9b80 100644 --- a/test/921-hello-failure/expected.txt +++ b/test/921-hello-failure/expected.txt @@ -13,3 +13,11 @@ hello2 - MissingInterface hello2 - ReorderInterface Transformation error : java.lang.Exception(Failed to redefine class <LTransform2;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED) hello2 - ReorderInterface +hello - MultiRedef +hello2 - MultiRedef +Transformation error : java.lang.Exception(Failed to redefine classes <LTransform2;, LTransform;> due to JVMTI_ERROR_NAMES_DONT_MATCH) +hello - MultiRedef +hello2 - MultiRedef +Transformation error : java.lang.Exception(Failed to redefine classes <LTransform;, LTransform2;> due to JVMTI_ERROR_NAMES_DONT_MATCH) +hello - MultiRedef +hello2 - MultiRedef diff --git a/test/921-hello-failure/src/CommonClassDefinition.java b/test/921-hello-failure/src/CommonClassDefinition.java new file mode 100644 index 0000000000..62602a02e9 --- /dev/null +++ b/test/921-hello-failure/src/CommonClassDefinition.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } +} diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java index 69c48e26cc..1fe259961d 100644 --- a/test/921-hello-failure/src/Main.java +++ b/test/921-hello-failure/src/Main.java @@ -14,6 +14,7 @@ * limitations under the License. */ +import java.util.ArrayList; public class Main { public static void main(String[] args) { @@ -23,10 +24,30 @@ public class Main { NewInterface.doTest(new Transform2()); MissingInterface.doTest(new Transform2()); ReorderInterface.doTest(new Transform2()); + MultiRedef.doTest(new Transform(), new Transform2()); } // Transforms the class. This throws an exception if something goes wrong. public static native void doCommonClassRedefinition(Class<?> target, byte[] classfile, byte[] dexfile) throws Exception; + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) throws Exception { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles) throws Exception; } diff --git a/test/921-hello-failure/src/MultiRedef.java b/test/921-hello-failure/src/MultiRedef.java new file mode 100644 index 0000000000..c64342c8f8 --- /dev/null +++ b/test/921-hello-failure/src/MultiRedef.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Base64; + +class MultiRedef { + + // class NotTransform { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static CommonClassDefinition INVALID_DEFINITION_T1 = new CommonClassDefinition( + Transform.class, + Base64.getDecoder().decode( + "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + + "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" + + "aWxlAQARTm90VHJhbnNmb3JtLmphdmEMAAcACAEAD2phdmEvbGFuZy9FcnJvcgEAFVNob3VsZCBu" + + "b3QgYmUgY2FsbGVkIQwABwAMAQAMTm90VHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAAgAAUA" + + "BgAAAAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAEAAQALAAwA" + + "AQAJAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEACgAAAAYAAQAAAAMAAQANAAAAAgAO"), + Base64.getDecoder().decode( + "ZGV4CjAzNQDLV95i5xnv6iUi6uIeDoY5jP5Xe9NP1AiYAgAAcAAAAHhWNBIAAAAAAAAAAAQCAAAL" + + "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACQAQAACAEAAEoB" + + "AABSAQAAYgEAAHUBAACJAQAAnQEAALABAADHAQAAygEAAM4BAADiAQAAAQAAAAIAAAADAAAABAAA" + + "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" + + "AAAAAAAAAAAAAAAAAgAAAAAAAAAFAAAAAAAAAPQBAAAAAAAAAQABAAEAAADpAQAABAAAAHAQAwAA" + + "AA4ABAACAAIAAADuAQAACQAAACIAAQAbAQYAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgAOTE5v" + + "dFRyYW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh" + + "L2xhbmcvU3RyaW5nOwARTm90VHJhbnNmb3JtLmphdmEAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAB" + + "VgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMAAFc2F5SGkAAQAHDgADAQAHDgAAAAEBAICABIgCAQGg" + + "AgAADAAAAAAAAAABAAAAAAAAAAEAAAALAAAAcAAAAAIAAAAFAAAAnAAAAAMAAAACAAAAsAAAAAUA" + + "AAAEAAAAyAAAAAYAAAABAAAA6AAAAAEgAAACAAAACAEAAAEQAAABAAAARAEAAAIgAAALAAAASgEA" + + "AAMgAAACAAAA6QEAAAAgAAABAAAA9AEAAAAQAAABAAAABAIAAA==")); + + // Valid redefinition of Transform2 + // class Transform2 implements Iface1, Iface2 { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static CommonClassDefinition VALID_DEFINITION_T2 = new CommonClassDefinition( + Transform2.class, + Base64.getDecoder().decode( + "yv66vgAAADQAGQoABgARBwASCAATCgACABQHABUHABYHABcHABgBAAY8aW5pdD4BAAMoKVYBAARD" + + "b2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApT" + + "b3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAJAAoBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91" + + "bGQgbm90IGJlIGNhbGxlZCEMAAkADgEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAG" + + "SWZhY2UxAQAGSWZhY2UyACAABQAGAAIABwAIAAAAAgAAAAkACgABAAsAAAAdAAEAAQAAAAUqtwAB" + + "sQAAAAEADAAAAAYAAQAAAAEAAQANAA4AAQALAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEADAAA" + + "AAYAAQAAAAMAAQAPAAAAAgAQ"), + Base64.getDecoder().decode( + "ZGV4CjAzNQDSWls05CPkX+gbTGMVRvx9dc9vozzVbu7AAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAN" + + "AAAAcAAAAAcAAACkAAAAAgAAAMAAAAAAAAAAAAAAAAQAAADYAAAAAQAAAPgAAACoAQAAGAEAAGIB" + + "AABqAQAAdAEAAH4BAACMAQAAnwEAALMBAADHAQAA3gEAAO8BAADyAQAA9gEAAAoCAAABAAAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAABcAQAAAgAAAAAAAAACAAEA" + + "DAAAAAMAAQAAAAAABAAAAAAAAAACAAAAAAAAAAQAAABUAQAACAAAAAAAAAAcAgAAAAAAAAEAAQAB" + + "AAAAEQIAAAQAAABwEAMAAAAOAAQAAgACAAAAFgIAAAkAAAAiAAMAGwEHAAAAcCACABAAJwAAAAIA" + + "AAAAAAEAAQAAAAUABjxpbml0PgAITElmYWNlMTsACExJZmFjZTI7AAxMVHJhbnNmb3JtMjsAEUxq" + + "YXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAV" + + "U2hvdWxkIG5vdCBiZSBjYWxsZWQhAA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBq" + + "YWNrLTQuMjAABXNheUhpAAEABw4AAwEABw4AAAABAQCAgASYAgEBsAIAAAwAAAAAAAAAAQAAAAAA" + + "AAABAAAADQAAAHAAAAACAAAABwAAAKQAAAADAAAAAgAAAMAAAAAFAAAABAAAANgAAAAGAAAAAQAA" + + "APgAAAABIAAAAgAAABgBAAABEAAAAgAAAFQBAAACIAAADQAAAGIBAAADIAAAAgAAABECAAAAIAAA" + + "AQAAABwCAAAAEAAAAQAAACwCAAA=")); + + public static void doTest(Transform t1, Transform2 t2) { + t1.sayHi("MultiRedef"); + t2.sayHi("MultiRedef"); + try { + Main.doMultiClassRedefinition(VALID_DEFINITION_T2, INVALID_DEFINITION_T1); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t1.sayHi("MultiRedef"); + t2.sayHi("MultiRedef"); + try { + Main.doMultiClassRedefinition(INVALID_DEFINITION_T1, VALID_DEFINITION_T2); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } + t1.sayHi("MultiRedef"); + t2.sayHi("MultiRedef"); + } +} diff --git a/test/926-multi-obsolescence/build b/test/926-multi-obsolescence/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/926-multi-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/926-multi-obsolescence/expected.txt b/test/926-multi-obsolescence/expected.txt new file mode 100644 index 0000000000..0546490c44 --- /dev/null +++ b/test/926-multi-obsolescence/expected.txt @@ -0,0 +1,15 @@ +hello +hello - 2 +Not doing anything here +goodbye - 2 +goodbye +hello +hello - 2 +transforming calling functions +goodbye - 2 +goodbye +Hello - Transformed +Hello 2 - Transformed +Not doing anything here +Goodbye 2 - Transformed +Goodbye - Transformed diff --git a/test/926-multi-obsolescence/info.txt b/test/926-multi-obsolescence/info.txt new file mode 100644 index 0000000000..1399b966f1 --- /dev/null +++ b/test/926-multi-obsolescence/info.txt @@ -0,0 +1,2 @@ +Tests that we can redefine multiple classes at once using the RedefineClasses +function. diff --git a/test/926-multi-obsolescence/run b/test/926-multi-obsolescence/run new file mode 100755 index 0000000000..4379349cb2 --- /dev/null +++ b/test/926-multi-obsolescence/run @@ -0,0 +1,19 @@ +#!/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-run "$@" --experimental agents \ + --experimental runtime-plugins \ + --jvmti diff --git a/test/926-multi-obsolescence/src/CommonClassDefinition.java b/test/926-multi-obsolescence/src/CommonClassDefinition.java new file mode 100644 index 0000000000..62602a02e9 --- /dev/null +++ b/test/926-multi-obsolescence/src/CommonClassDefinition.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } +} diff --git a/test/926-multi-obsolescence/src/Main.java b/test/926-multi-obsolescence/src/Main.java new file mode 100644 index 0000000000..8a6cf84b8b --- /dev/null +++ b/test/926-multi-obsolescence/src/Main.java @@ -0,0 +1,128 @@ +/* + * 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.ArrayList; +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 CommonClassDefinition VALID_DEFINITION_T1 = new CommonClassDefinition( + Transform.class, + Base64.getDecoder().decode( + "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" + + "KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAeAQATSGVsbG8gLSBU" + + "cmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABVHb29kYnllIC0gVHJhbnNmb3JtZWQBAAlUcmFu" + + "c2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZh" + + "L2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZh" + + "L2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAAAAACAAAA" + + "CQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQABAA0ADgABAAsAAAA7AAIA" + + "AgAAABeyAAISA7YABCu5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4ABQAWAAYA" + + "AQAPAAAAAgAQ"), + Base64.getDecoder().decode( + "ZGV4CjAzNQAYeAMMXgYWxoeSHAS9EWKCCtVRSAGpqZVQAwAAcAAAAHhWNBIAAAAAAAAAALACAAAR" + + "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAMAgAARAEAAKIB" + + "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAAB3AgAAfAIA" + + "AIUCAACKAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" + + "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" + + "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAJ8CAAAAAAAAAQABAAEAAACRAgAABAAAAHAQ" + + "AwAAAA4ABAACAAIAAACWAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAwBiAAAAGwEBAAAAbiAC" + + "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" + + "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" + + "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" + + "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00" + + "LjEzAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAEBAICABMQCAQHc" + + "AgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEQAAAHAAAAACAAAABwAAALQAAAADAAAAAwAAANAAAAAE" + + "AAAAAQAAAPQAAAAFAAAABQAAAPwAAAAGAAAAAQAAACQBAAABIAAAAgAAAEQBAAABEAAAAgAAAJQB" + + "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA=")); + // class Transform2 { + // public void sayHi(Runnable r) { + // System.out.println("Hello 2 - Transformed"); + // r.run(); + // System.out.println("Goodbye 2 - Transformed"); + // } + // } + private static CommonClassDefinition VALID_DEFINITION_T2 = new CommonClassDefinition( + Transform2.class, + Base64.getDecoder().decode( + "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" + + "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" + + "KVYBAApTb3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAJAAoHABwMAB0AHgEAFUhlbGxvIDIg" + + "LSBUcmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABdHb29kYnllIDIgLSBUcmFuc2Zvcm1lZAEA" + + "ClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEA" + + "FUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAV" + + "KExqYXZhL2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAA" + + "AAACAAAACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQABAA0ADgABAAsA" + + "AAA7AAIAAgAAABeyAAISA7YABCu5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4A" + + "BQAWAAYAAQAPAAAAAgAQ"), + Base64.getDecoder().decode( + "ZGV4CjAzNQCee5Z6+AuFcjnPjjn7QYgZmKSmFQCO4nxUAwAAcAAAAHhWNBIAAAAAAAAAALQCAAAR" + + "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAQAgAARAEAAKIB" + + "AACqAQAAwwEAANoBAADoAQAA/wEAABMCAAApAgAAPQIAAFECAABiAgAAZQIAAGkCAAB9AgAAggIA" + + "AIsCAACQAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" + + "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" + + "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAKUCAAAAAAAAAQABAAEAAACXAgAABAAAAHAQ" + + "AwAAAA4ABAACAAIAAACcAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAwBiAAAAGwEBAAAAbiAC" + + "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AF0dvb2RieWUgMiAtIFRyYW5zZm9ybWVkABVIZWxs" + + "byAyIC0gVHJhbnNmb3JtZWQADExUcmFuc2Zvcm0yOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJM" + + "amF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmlu" + + "ZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAPVHJhbnNmb3JtMi5qYXZhAAFWAAJWTAASZW1pdHRlcjog" + + "amFjay00LjIwAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAEBAICA" + + "BMQCAQHcAgANAAAAAAAAAAEAAAAAAAAAAQAAABEAAABwAAAAAgAAAAcAAAC0AAAAAwAAAAMAAADQ" + + "AAAABAAAAAEAAAD0AAAABQAAAAUAAAD8AAAABgAAAAEAAAAkAQAAASAAAAIAAABEAQAAARAAAAIA" + + "AACUAQAAAiAAABEAAACiAQAAAyAAAAIAAACXAgAAACAAAAEAAAClAgAAABAAAAEAAAC0AgAA")); + + public static void main(String[] args) { + System.loadLibrary(args[1]); + doTest(new Transform(), new Transform2()); + } + + public static void doTest(final Transform t1, final Transform2 t2) { + t1.sayHi(() -> { t2.sayHi(() -> { System.out.println("Not doing anything here"); }); }); + t1.sayHi(() -> { + t2.sayHi(() -> { + System.out.println("transforming calling functions"); + doMultiClassRedefinition(VALID_DEFINITION_T1, VALID_DEFINITION_T2); + }); + }); + t1.sayHi(() -> { t2.sayHi(() -> { System.out.println("Not doing anything here"); }); }); + } + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); +} diff --git a/test/926-multi-obsolescence/src/Transform.java b/test/926-multi-obsolescence/src/Transform.java new file mode 100644 index 0000000000..8cda6cdf53 --- /dev/null +++ b/test/926-multi-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/926-multi-obsolescence/src/Transform2.java b/test/926-multi-obsolescence/src/Transform2.java new file mode 100644 index 0000000000..4877f8455b --- /dev/null +++ b/test/926-multi-obsolescence/src/Transform2.java @@ -0,0 +1,23 @@ +/* + * 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 Transform2 { + public void sayHi(Runnable r) { + System.out.println("hello - 2"); + r.run(); + System.out.println("goodbye - 2"); + } +} diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index a93efd2208..4243370284 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -298,6 +298,7 @@ TEST_ART_BROKEN_TARGET_TESTS += \ 923-monitors \ 924-threads \ 925-threadgroups \ + 926-multi-obsolescence \ ifneq (,$(filter target,$(TARGET_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ @@ -566,6 +567,7 @@ TEST_ART_BROKEN_JIT_RUN_TESTS := \ 915-obsolete-2 \ 917-fields-transformation \ 919-obsolete-fields \ + 926-multi-obsolescence \ ifneq (,$(filter jit,$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 6f98f10072..2c6d3eda00 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -62,48 +62,64 @@ bool JvmtiErrorToException(JNIEnv* env, jvmtiError error) { namespace common_redefine { -static void throwRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, jclass target, jvmtiError res) { +static void throwRedefinitionError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* target, + jvmtiError res) { std::stringstream err; - char* signature = nullptr; - char* generic = nullptr; - jvmti->GetClassSignature(target, &signature, &generic); char* error = nullptr; jvmti->GetErrorName(res, &error); - err << "Failed to redefine class <" << signature << "> due to " << error; + err << "Failed to redefine class"; + if (num_targets > 1) { + err << "es"; + } + err << " <"; + for (jint i = 0; i < num_targets; i++) { + char* signature = nullptr; + char* generic = nullptr; + jvmti->GetClassSignature(target[i], &signature, &generic); + if (i != 0) { + err << ", "; + } + err << signature; + jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature)); + jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic)); + } + err << "> due to " << error; std::string message = err.str(); - jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature)); - jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic)); jvmti->Deallocate(reinterpret_cast<unsigned char*>(error)); env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); } -using RedefineDirectFunction = jvmtiError (*)(jvmtiEnv*, jclass, jint, const unsigned char*); -static void DoClassTransformation(jvmtiEnv* jvmti_env, - JNIEnv* env, - jclass target, - jbyteArray class_file_bytes, - jbyteArray dex_file_bytes) { - jbyteArray desired_array = IsJVM() ? class_file_bytes : dex_file_bytes; - jint len = static_cast<jint>(env->GetArrayLength(desired_array)); - const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>( - env->GetByteArrayElements(desired_array, nullptr)); - jvmtiError res; - if (IsJVM()) { - jvmtiClassDefinition def; - def.klass = target; - def.class_byte_count = static_cast<jint>(len); - def.class_bytes = redef_bytes; - res = jvmti_env->RedefineClasses(1, &def); - } else { - RedefineDirectFunction f = - reinterpret_cast<RedefineDirectFunction>(jvmti_env->functions->reserved3); - res = f(jvmti_env, target, len, redef_bytes); +static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, + JNIEnv* env, + jint num_redefines, + jclass* targets, + jbyteArray* class_file_bytes, + jbyteArray* dex_file_bytes) { + std::vector<jvmtiClassDefinition> defs; + for (jint i = 0; i < num_redefines; i++) { + jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i]; + jint len = static_cast<jint>(env->GetArrayLength(desired_array)); + const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>( + env->GetByteArrayElements(desired_array, nullptr)); + defs.push_back({targets[i], static_cast<jint>(len), redef_bytes}); } + jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data()); if (res != JVMTI_ERROR_NONE) { - throwRedefinitionError(jvmti_env, env, target, res); + throwRedefinitionError(jvmti_env, env, num_redefines, targets, res); } } +static void DoClassRedefine(jvmtiEnv* jvmti_env, + JNIEnv* env, + jclass target, + jbyteArray class_file_bytes, + jbyteArray dex_file_bytes) { + return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes); +} + // Magic JNI export that classes can use for redefining classes. // To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V extern "C" JNIEXPORT void JNICALL Java_Main_doCommonClassRedefinition(JNIEnv* env, @@ -111,7 +127,38 @@ extern "C" JNIEXPORT void JNICALL Java_Main_doCommonClassRedefinition(JNIEnv* en jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) { - DoClassTransformation(jvmti_env, env, target, class_file_bytes, dex_file_bytes); + DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes); +} + +// Magic JNI export that classes can use for redefining classes. +// To use classes should declare this as a native function with signature +// ([Ljava/lang/Class;[[B[[B)V +extern "C" JNIEXPORT void JNICALL Java_Main_doCommonMultiClassRedefinition( + JNIEnv* env, + jclass, + jobjectArray targets, + jobjectArray class_file_bytes, + jobjectArray dex_file_bytes) { + std::vector<jclass> classes; + std::vector<jbyteArray> class_files; + std::vector<jbyteArray> dex_files; + jint len = env->GetArrayLength(targets); + if (len != env->GetArrayLength(class_file_bytes) || len != env->GetArrayLength(dex_file_bytes)) { + env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), + "the three array arguments passed to this function have different lengths!"); + return; + } + for (jint i = 0; i < len; i++) { + classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); + dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i))); + class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i))); + } + return DoMultiClassRedefine(jvmti_env, + env, + len, + classes.data(), + class_files.data(), + dex_files.data()); } // Don't do anything diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 8abd063a01..521e672330 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -65,6 +65,7 @@ AgentLib agents[] = { { "917-fields-transformation", common_redefine::OnLoad, nullptr }, { "919-obsolete-fields", common_redefine::OnLoad, nullptr }, { "921-hello-failure", common_redefine::OnLoad, nullptr }, + { "926-multi-obsolescence", common_redefine::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |