summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/openjdkjvmti/OpenjdkJvmTi.cc60
-rw-r--r--runtime/openjdkjvmti/ti_redefine.cc467
-rw-r--r--runtime/openjdkjvmti/ti_redefine.h226
-rw-r--r--test/921-hello-failure/expected.txt8
-rw-r--r--test/921-hello-failure/src/CommonClassDefinition.java27
-rw-r--r--test/921-hello-failure/src/Main.java21
-rw-r--r--test/921-hello-failure/src/MultiRedef.java100
-rwxr-xr-xtest/926-multi-obsolescence/build17
-rw-r--r--test/926-multi-obsolescence/expected.txt15
-rw-r--r--test/926-multi-obsolescence/info.txt2
-rwxr-xr-xtest/926-multi-obsolescence/run19
-rw-r--r--test/926-multi-obsolescence/src/CommonClassDefinition.java27
-rw-r--r--test/926-multi-obsolescence/src/Main.java128
-rw-r--r--test/926-multi-obsolescence/src/Transform.java30
-rw-r--r--test/926-multi-obsolescence/src/Transform2.java23
-rw-r--r--test/Android.run-test.mk2
-rw-r--r--test/ti-agent/common_helper.cc107
-rw-r--r--test/ti-agent/common_load.cc1
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) {