Add more standard structural redefinition entrypoints
Add structural redefinition extension function and event that mirror
the 'RedefineClasses' function and 'ClassFileLoadHook' event. The new
extension function is called
'com.android.art.class.structurally_redefine_classes' and the new
extension event is called
'com.android.art.class.structural_dex_file_load_hook'.
These extensions are the preferred way to use structural redefinition.
Like the standard 'RedefineClasses' multiple classes may be redefined
at a time.
The structural_dex_file_load_hook is triggered prior to the
can_retransform_classes ClassFileLoadHook. It is triggered on all
classes, even ones that cannot be structurally changed by
class-loading, class redefinition or by calling the RetransformClasses
function.
Calling 'structurally_redefine_classes' with new definitions that do
not require structural changes will fall back to non-structural
redefinition.
Test: ./test.py --host
Bug: 134162467
Change-Id: If4810930470c5c6509cf6db779910006e114b39f
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index 22822f8..23f7151 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -90,41 +90,42 @@
// Infrastructure to achieve type safety for event dispatch.
-#define FORALL_EVENT_TYPES(fn) \
- fn(VMInit, ArtJvmtiEvent::kVmInit) \
- fn(VMDeath, ArtJvmtiEvent::kVmDeath) \
- fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \
- fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \
- fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \
- fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \
- fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \
- fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \
- fn(VMStart, ArtJvmtiEvent::kVmStart) \
- fn(Exception, ArtJvmtiEvent::kException) \
- fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \
- fn(SingleStep, ArtJvmtiEvent::kSingleStep) \
- fn(FramePop, ArtJvmtiEvent::kFramePop) \
- fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \
- fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \
- fn(FieldModification, ArtJvmtiEvent::kFieldModification) \
- fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \
- fn(MethodExit, ArtJvmtiEvent::kMethodExit) \
- fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \
- fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \
- fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \
- fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \
- fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \
- fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \
- fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \
- fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \
- fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \
- fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \
- fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \
- fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \
- fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \
- fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \
- fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \
- fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated)
+#define FORALL_EVENT_TYPES(fn) \
+ fn(VMInit, ArtJvmtiEvent::kVmInit) \
+ fn(VMDeath, ArtJvmtiEvent::kVmDeath) \
+ fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \
+ fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \
+ fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \
+ fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \
+ fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \
+ fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \
+ fn(VMStart, ArtJvmtiEvent::kVmStart) \
+ fn(Exception, ArtJvmtiEvent::kException) \
+ fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \
+ fn(SingleStep, ArtJvmtiEvent::kSingleStep) \
+ fn(FramePop, ArtJvmtiEvent::kFramePop) \
+ fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \
+ fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \
+ fn(FieldModification, ArtJvmtiEvent::kFieldModification) \
+ fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \
+ fn(MethodExit, ArtJvmtiEvent::kMethodExit) \
+ fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \
+ fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \
+ fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \
+ fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \
+ fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \
+ fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \
+ fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \
+ fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \
+ fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \
+ fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \
+ fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \
+ fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \
+ fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \
+ fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \
+ fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \
+ fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) \
+ fn(StructuralDexFileLoadHook, ArtJvmtiEvent::kStructuralDexFileLoadHook)
template <ArtJvmtiEvent kEvent>
struct EventFnType {
@@ -217,7 +218,8 @@
unsigned char** new_class_data) const {
art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
- kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event");
+ kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable ||
+ kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "Unsupported event");
DCHECK(*new_class_data == nullptr);
jint current_len = class_data_len;
unsigned char* current_class_data = const_cast<unsigned char*>(class_data);
@@ -588,6 +590,31 @@
new_class_data);
}
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+ art::Thread* thread,
+ JNIEnv* jnienv,
+ jclass class_being_redefined,
+ jobject loader,
+ const char* name,
+ jobject protection_domain,
+ jint class_data_len,
+ const unsigned char* class_data,
+ jint* new_class_data_len,
+ unsigned char** new_class_data) const {
+ return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+ thread,
+ jnienv,
+ class_being_redefined,
+ loader,
+ name,
+ protection_domain,
+ class_data_len,
+ class_data,
+ new_class_data_len,
+ new_class_data);
+}
+
template <ArtJvmtiEvent kEvent>
inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const {
bool dispatch = env->event_masks.global_event_mask.Test(kEvent);
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 3f205eb..56406fc 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -100,6 +100,9 @@
case static_cast<jint>(ArtJvmtiEvent::kDdmPublishChunk):
DdmPublishChunk = reinterpret_cast<ArtJvmtiEventDdmPublishChunk>(cb);
return OK;
+ case static_cast<jint>(ArtJvmtiEvent::kStructuralDexFileLoadHook):
+ StructuralDexFileLoadHook = reinterpret_cast<ArtJvmtiEventStructuralDexFileLoadHook>(cb);
+ return OK;
default:
return ERR(ILLEGAL_ARGUMENT);
}
@@ -116,6 +119,7 @@
switch (e) {
case ArtJvmtiEvent::kDdmPublishChunk:
case ArtJvmtiEvent::kObsoleteObjectCreated:
+ case ArtJvmtiEvent::kStructuralDexFileLoadHook:
return true;
default:
return false;
@@ -1175,6 +1179,7 @@
case ArtJvmtiEvent::kClassFileLoadHookRetransformable:
case ArtJvmtiEvent::kDdmPublishChunk:
case ArtJvmtiEvent::kObsoleteObjectCreated:
+ case ArtJvmtiEvent::kStructuralDexFileLoadHook:
return DeoptRequirement::kNone;
}
}
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index d5ab4fb..c9d587a 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -81,7 +81,8 @@
kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1,
kDdmPublishChunk = JVMTI_MAX_EVENT_TYPE_VAL + 2,
kObsoleteObjectCreated = JVMTI_MAX_EVENT_TYPE_VAL + 3,
- kMaxNormalEventTypeVal = kObsoleteObjectCreated,
+ kStructuralDexFileLoadHook = JVMTI_MAX_EVENT_TYPE_VAL + 4,
+ kMaxNormalEventTypeVal = kStructuralDexFileLoadHook,
// All that follow are events used to implement internal JVMTI functions. They are not settable
// directly by agents.
@@ -107,6 +108,17 @@
jlong* obsolete_tag,
jlong* new_tag);
+using ArtJvmtiEventStructuralDexFileLoadHook = void (*)(jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jclass class_being_redefined,
+ jobject loader,
+ const char* name,
+ jobject protection_domain,
+ jint dex_data_len,
+ const unsigned char* dex_data,
+ jint* new_dex_data_len,
+ unsigned char** new_dex_data);
+
// It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the
// thread id.
// Note: We could just use the tid like tracing does.
@@ -119,7 +131,10 @@
};
struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks {
- ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr), ObsoleteObjectCreated(nullptr) {
+ ArtJvmtiEventCallbacks()
+ : DdmPublishChunk(nullptr),
+ ObsoleteObjectCreated(nullptr),
+ StructuralDexFileLoadHook(nullptr) {
memset(this, 0, sizeof(jvmtiEventCallbacks));
}
@@ -131,6 +146,7 @@
ArtJvmtiEventDdmPublishChunk DdmPublishChunk;
ArtJvmtiEventObsoleteObjectCreated ObsoleteObjectCreated;
+ ArtJvmtiEventStructuralDexFileLoadHook StructuralDexFileLoadHook;
};
bool IsExtensionEvent(jint e);
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 988274b..82ce916 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -204,6 +204,9 @@
memcpy(post_non_retransform.data(), def.GetDexData().data(), post_non_retransform.size());
}
+ // Call all structural transformation agents.
+ Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+ event_handler, self, &def);
// Call all retransformable agents.
Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
event_handler, self, &def);
diff --git a/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h
index 224e664..cb0853b 100644
--- a/openjdkjvmti/ti_class_definition.h
+++ b/openjdkjvmti/ti_class_definition.h
@@ -40,6 +40,7 @@
#include "base/array_ref.h"
#include "base/mem_map.h"
+#include "events.h"
namespace openjdkjvmti {
@@ -65,7 +66,8 @@
current_dex_file_(),
redefined_(false),
from_class_ext_(false),
- initialized_(false) {}
+ initialized_(false),
+ structural_transform_update_(false) {}
void InitFirstLoad(const char* descriptor,
art::Handle<art::mirror::ClassLoader> klass_loader,
@@ -76,7 +78,7 @@
ArtClassDefinition(ArtClassDefinition&& o) = default;
ArtClassDefinition& operator=(ArtClassDefinition&& o) = default;
- void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data) {
+ void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data, ArtJvmtiEvent event) {
DCHECK(IsInitialized());
if (new_dex_data == nullptr) {
return;
@@ -86,10 +88,17 @@
dex_data_memory_.resize(new_dex_len);
memcpy(dex_data_memory_.data(), new_dex_data, new_dex_len);
dex_data_ = art::ArrayRef<const unsigned char>(dex_data_memory_);
+ if (event == ArtJvmtiEvent::kStructuralDexFileLoadHook) {
+ structural_transform_update_ = true;
+ }
}
}
}
+ bool HasStructuralChanges() const {
+ return structural_transform_update_;
+ }
+
art::ArrayRef<const unsigned char> GetNewOriginalDexFile() const {
DCHECK(IsInitialized());
if (redefined_) {
@@ -187,6 +196,9 @@
bool initialized_;
+ // Set if we had a new dex from the given transform type.
+ bool structural_transform_update_;
+
DISALLOW_COPY_AND_ASSIGN(ArtClassDefinition);
};
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 5dc7445..058a188 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -30,6 +30,7 @@
#include <vector>
+#include "jvmti.h"
#include "ti_extension.h"
#include "art_jvmti.h"
@@ -45,6 +46,7 @@
#include "ti_monitor.h"
#include "ti_redefine.h"
#include "ti_search.h"
+#include "transform.h"
#include "thread-inl.h"
@@ -416,7 +418,40 @@
return error;
}
- // StructurallyRedefineClass
+ // StructurallyRedefineClasses
+ error = add_extension(
+ reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClasses),
+ "com.android.art.class.structurally_redefine_classes",
+ "Entrypoint for structural class redefinition. Has the same signature as RedefineClasses."
+ " Currently this only supports adding new static fields to a class without any instance"
+ " fields or methods. After calling this com.android.art.structural_dex_file_load_hook"
+ " events will be triggered, followed by re-transformable ClassFileLoadHook events. After"
+ " this method completes subsequent RetransformClasses calls will use the input to this"
+ " function as the initial class definition.",
+ {
+ { "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+ { "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false },
+ },
+ {
+ ERR(CLASS_LOADER_UNSUPPORTED),
+ ERR(FAILS_VERIFICATION),
+ ERR(ILLEGAL_ARGUMENT),
+ ERR(INVALID_CLASS),
+ ERR(MUST_POSSESS_CAPABILITY),
+ ERR(MUST_POSSESS_CAPABILITY),
+ ERR(NULL_POINTER),
+ ERR(OUT_OF_MEMORY),
+ ERR(UNMODIFIABLE_CLASS),
+ ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
+ ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
+ ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
+ ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+ });
+ if (error != ERR(NONE)) {
+ return error;
+ }
+
+ // StructurallyRedefineClassDirect
error = add_extension(
reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClassDirect),
"com.android.art.UNSAFE.class.structurally_redefine_class_direct",
@@ -494,7 +529,7 @@
const char* id,
const char* short_description,
const std::vector<CParamInfo>& params) {
- DCHECK(IsExtensionEvent(extension_event_index));
+ DCHECK(IsExtensionEvent(extension_event_index)) << static_cast<jint>(extension_event_index);
jvmtiExtensionEventInfo event_info;
jvmtiError error;
@@ -592,7 +627,35 @@
if (error != OK) {
return error;
}
-
+ art::Runtime* runtime = art::Runtime::Current();
+ if (runtime->GetJniIdType() == art::JniIdType::kIndices &&
+ (runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) {
+ error = add_extension(
+ ArtJvmtiEvent::kStructuralDexFileLoadHook,
+ "com.android.art.class.structural_dex_file_load_hook",
+ "Called during class load, after a 'RetransformClasses' call, or after a 'RedefineClasses'"
+ " call in order to allow the agent to modify the class. This event is called after any"
+ " non-can_retransform_classes ClassFileLoadHookEvents and before any"
+ " can_retransform_classes ClassFileLoadHookEvents. The transformations applied are"
+ " restricted in the same way that transformations applied via the "
+ " 'com.android.art.class.structurally_redefine_classes' extension function. The arguments"
+ " to the event are identical to the ones in the ClassFileLoadHook and have the same"
+ " semantics.",
+ {
+ { "jni_env", JVMTI_KIND_IN, JVMTI_TYPE_JNIENV, false },
+ { "class_being_redefined", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, true },
+ { "loader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
+ { "name", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false },
+ { "protection_domain", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, true },
+ { "dex_data_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+ { "dex_data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
+ { "new_dex_data_len", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
+ { "new_dex_data", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, true },
+ });
+ } else {
+ LOG(INFO) << "debuggable & jni-type indices are required to implement structural "
+ << "class redefinition extensions.";
+ }
// Copy into output buffer.
*extension_count_ptr = ext_vector.size();
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 05d7de7..22a3bc5 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -376,7 +376,7 @@
return ERR(INVALID_CLASS);
}
art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
- return Redefiner::GetClassRedefinitionError(h_klass, error_msg);
+ return Redefiner::GetClassRedefinitionError<kType>(h_klass, error_msg);
}
template <RedefinitionType kType>
@@ -574,9 +574,10 @@
}
}
-jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv,
- jint class_count,
- const jvmtiClassDefinition* definitions) {
+template<RedefinitionType kType>
+jvmtiError Redefiner::RedefineClassesGeneric(jvmtiEnv* jenv,
+ jint class_count,
+ const jvmtiClassDefinition* definitions) {
art::Runtime* runtime = art::Runtime::Current();
art::Thread* self = art::Thread::Current();
ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
@@ -597,7 +598,8 @@
std::vector<ArtClassDefinition> def_vector;
def_vector.reserve(class_count);
for (jint i = 0; i < class_count; i++) {
- jvmtiError res = Redefiner::GetClassRedefinitionError(definitions[i].klass, &error_msg);
+ jvmtiError res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(
+ definitions[i].klass, &error_msg);
if (res != OK) {
JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg;
return res;
@@ -611,15 +613,35 @@
def_vector.push_back(std::move(def));
}
// Call all the transformation events.
- Transformer::RetransformClassesDirect(self, &def_vector);
- jvmtiError res = RedefineClassesDirect(
- env, runtime, self, def_vector, RedefinitionType::kNormal, &error_msg);
+ Transformer::RetransformClassesDirect<kType>(self, &def_vector);
+ if (kType == RedefinitionType::kStructural) {
+ Transformer::RetransformClassesDirect<RedefinitionType::kNormal>(self, &def_vector);
+ }
+ jvmtiError res = RedefineClassesDirect(env, runtime, self, def_vector, kType, &error_msg);
if (res != OK) {
JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg;
}
return res;
}
+jvmtiError Redefiner::StructurallyRedefineClasses(jvmtiEnv* jenv,
+ jint class_count,
+ const jvmtiClassDefinition* definitions) {
+ ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+ if (art_env == nullptr) {
+ return ERR(INVALID_ENVIRONMENT);
+ } else if (art_env->capabilities.can_redefine_classes != 1) {
+ return ERR(MUST_POSSESS_CAPABILITY);
+ }
+ return RedefineClassesGeneric<RedefinitionType::kStructural>(jenv, class_count, definitions);
+}
+
+jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv,
+ jint class_count,
+ const jvmtiClassDefinition* definitions) {
+ return RedefineClassesGeneric<RedefinitionType::kNormal>(jenv, class_count, definitions);
+}
+
jvmtiError Redefiner::StructurallyRedefineClassDirect(jvmtiEnv* env,
jclass klass,
const unsigned char* data,
@@ -1154,13 +1176,10 @@
art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
jvmtiError res;
- switch (driver_->type_) {
- case RedefinitionType::kNormal:
- res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err);
- break;
- case RedefinitionType::kStructural:
+ if (driver_->type_ == RedefinitionType::kStructural && this->IsStructuralRedefinition()) {
res = Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(h_klass, &err);
- break;
+ } else {
+ res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err);
}
if (res != OK) {
RecordFailure(res, err);
@@ -1171,7 +1190,7 @@
}
bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() {
- return CheckRedefinable() && CheckClass() && CheckFields() && CheckMethods();
+ return CheckClass() && CheckFields() && CheckMethods() && CheckRedefinable();
}
class RedefinitionDataIter;
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index b028dee..58a688c 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -87,6 +87,9 @@
static jvmtiError RedefineClasses(jvmtiEnv* env,
jint class_count,
const jvmtiClassDefinition* definitions);
+ static jvmtiError StructurallyRedefineClasses(jvmtiEnv* env,
+ jint class_count,
+ const jvmtiClassDefinition* definitions);
static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
static jvmtiError IsStructurallyModifiableClass(jvmtiEnv* env,
@@ -287,6 +290,11 @@
REQUIRES_SHARED(art::Locks::mutator_lock_);
template<RedefinitionType kType = RedefinitionType::kNormal>
+ static jvmtiError RedefineClassesGeneric(jvmtiEnv* env,
+ jint class_count,
+ const jvmtiClassDefinition* definitions);
+
+ template<RedefinitionType kType = RedefinitionType::kNormal>
static jvmtiError IsModifiableClassGeneric(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
template<RedefinitionType kType = RedefinitionType::kNormal>
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
index aa37793..6133685 100644
--- a/openjdkjvmti/transform.cc
+++ b/openjdkjvmti/transform.cc
@@ -255,13 +255,17 @@
template
void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def);
+template
+void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+ EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def);
template<ArtJvmtiEvent kEvent>
void Transformer::TransformSingleClassDirect(EventHandler* event_handler,
art::Thread* self,
/*in-out*/ArtClassDefinition* def) {
static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable ||
- kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable,
+ kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
+ kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook,
"bad event type");
// We don't want to do transitions between calling the event and setting the new data so change to
// native state early. This also avoids any problems that the FaultHandler might have in
@@ -282,25 +286,30 @@
dex_data.data(),
/*out*/&new_len,
/*out*/&new_data);
- def->SetNewDexData(new_len, new_data);
+ def->SetNewDexData(new_len, new_data, kEvent);
}
+template <RedefinitionType kType>
void Transformer::RetransformClassesDirect(
- art::Thread* self,
- /*in-out*/std::vector<ArtClassDefinition>* definitions) {
+ art::Thread* self,
+ /*in-out*/ std::vector<ArtClassDefinition>* definitions) {
+ constexpr ArtJvmtiEvent kEvent = kType == RedefinitionType::kNormal
+ ? ArtJvmtiEvent::kClassFileLoadHookRetransformable
+ : ArtJvmtiEvent::kStructuralDexFileLoadHook;
for (ArtClassDefinition& def : *definitions) {
- TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
- gEventHandler, self, &def);
+ TransformSingleClassDirect<kEvent>(gEventHandler, self, &def);
}
}
+template void Transformer::RetransformClassesDirect<RedefinitionType::kNormal>(
+ art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions);
+template void Transformer::RetransformClassesDirect<RedefinitionType::kStructural>(
+ art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions);
+
jvmtiError Transformer::RetransformClasses(jvmtiEnv* env,
jint class_count,
const jclass* classes) {
- if (env == nullptr) {
- JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM env was null!";
- return ERR(INVALID_ENVIRONMENT);
- } else if (class_count < 0) {
+ if (class_count < 0) {
JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM class_count was less then 0";
return ERR(ILLEGAL_ARGUMENT);
} else if (class_count == 0) {
@@ -317,7 +326,7 @@
std::vector<ArtClassDefinition> definitions;
jvmtiError res = OK;
for (jint i = 0; i < class_count; i++) {
- res = Redefiner::GetClassRedefinitionError(classes[i], &error_msg);
+ res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(classes[i], &error_msg);
if (res != OK) {
JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg;
return res;
@@ -330,13 +339,16 @@
}
definitions.push_back(std::move(def));
}
- RetransformClassesDirect(self, &definitions);
- res = Redefiner::RedefineClassesDirect(ArtJvmTiEnv::AsArtJvmTiEnv(env),
- runtime,
- self,
- definitions,
- RedefinitionType::kNormal,
- &error_msg);
+ RetransformClassesDirect<RedefinitionType::kStructural>(self, &definitions);
+ RetransformClassesDirect<RedefinitionType::kNormal>(self, &definitions);
+ RedefinitionType redef_type =
+ std::any_of(definitions.cbegin(),
+ definitions.cend(),
+ [](const auto& it) { return it.HasStructuralChanges(); })
+ ? RedefinitionType::kStructural
+ : RedefinitionType::kNormal;
+ res = Redefiner::RedefineClassesDirect(
+ ArtJvmTiEnv::AsArtJvmTiEnv(env), runtime, self, definitions, redef_type, &error_msg);
if (res != OK) {
JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg;
}
diff --git a/openjdkjvmti/transform.h b/openjdkjvmti/transform.h
index 40c7267..a58b50e 100644
--- a/openjdkjvmti/transform.h
+++ b/openjdkjvmti/transform.h
@@ -39,6 +39,7 @@
#include "art_jvmti.h"
#include "ti_class_definition.h"
+#include "ti_redefine.h"
namespace openjdkjvmti {
@@ -56,6 +57,7 @@
art::Thread* self,
/*in-out*/ArtClassDefinition* def);
+ template<RedefinitionType kType>
static void RetransformClassesDirect(
art::Thread* self,
/*in-out*/std::vector<ArtClassDefinition>* definitions);
diff --git a/test/1988-multi-structural-redefine/expected.txt b/test/1988-multi-structural-redefine/expected.txt
new file mode 100644
index 0000000..00aea88
--- /dev/null
+++ b/test/1988-multi-structural-redefine/expected.txt
@@ -0,0 +1,5 @@
+hello - Transform 1
+hello - Transform 2
+Redefining both class art.Test1988$Transform1 and class art.Test1988$Transform2 to use each other.
+Transform1 says hi and Transform2 says bye!
+Transform2 says hi and Transform1 says bye!
diff --git a/test/1988-multi-structural-redefine/info.txt b/test/1988-multi-structural-redefine/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/1988-multi-structural-redefine/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/1988-multi-structural-redefine/run b/test/1988-multi-structural-redefine/run
new file mode 100755
index 0000000..a36de16
--- /dev/null
+++ b/test/1988-multi-structural-redefine/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1988-multi-structural-redefine/src/Main.java b/test/1988-multi-structural-redefine/src/Main.java
new file mode 100644
index 0000000..7e95671
--- /dev/null
+++ b/test/1988-multi-structural-redefine/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1988.run();
+ }
+}
diff --git a/test/1988-multi-structural-redefine/src/art/Redefinition.java b/test/1988-multi-structural-redefine/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1988-multi-structural-redefine/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1988-multi-structural-redefine/src/art/Test1988.java b/test/1988-multi-structural-redefine/src/art/Test1988.java
new file mode 100644
index 0000000..6dab4da
--- /dev/null
+++ b/test/1988-multi-structural-redefine/src/art/Test1988.java
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+
+public class Test1988 {
+ static class Transform1 {
+ public static void sayHi() {
+ System.out.println("hello - Transform 1");
+ }
+ }
+ static class Transform2 {
+ public static void sayHi() {
+ System.out.println("hello - Transform 2");
+ }
+ }
+
+ /** Base64 encoded dex file for
+ *
+ * static class Trasnform1 {
+ * public static void sayHi() {
+ * System.out.println("Transform1 says hi and " + Transform2.getBye());
+ * }
+ * public static String getBye() {
+ * return "Transform1 says bye!";
+ * }
+ * }
+ */
+ public static final byte[] T1_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQAU4pPI4BKgrMtz7s1Ogc8in1PQhazaRWBcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" +
+ "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" +
+ "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" +
+ "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" +
+ "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" +
+ "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAgAAAAAAAAAAABUA" +
+ "AAAAAAIAGQAAAAEAAAAVAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" +
+ "AAAAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" +
+ "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAMAAAAMASICCABw" +
+ "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAAYADgALAA4ACAAOARoPAAAAAAEA" +
+ "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" +
+ "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" +
+ "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" +
+ "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" +
+ "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" +
+ "YW5zZm9ybTEAFFRyYW5zZm9ybTEgc2F5cyBieWUhABdUcmFuc2Zvcm0xIHNheXMgaGkgYW5kIAAB" +
+ "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" +
+ "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" +
+ "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" +
+ "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAACAgATIAwEJsAMB" +
+ "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" +
+ "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" +
+ "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" +
+ "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" +
+ "mAQAAA==");
+
+
+ /** Base64 encoded dex file for
+ *
+ * static class Trasnform2 {
+ * public static void sayHi() {
+ * System.out.println("Transform2 says hi and " + Transform1.getBye());
+ * }
+ * public static String getBye() {
+ * return "Transform2 says bye!";
+ * }
+ * }
+ */
+ public static final byte[] T2_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQD94cwR+R7Yw7VMom5CwuQd5mZlsV2xrVFcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" +
+ "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" +
+ "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" +
+ "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" +
+ "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" +
+ "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAAAVAAAAAQACAAAA" +
+ "AAABAAAAFQAAAAEAAgAZAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" +
+ "AAEAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" +
+ "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAAAAAAMASICCABw" +
+ "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAA4ADgATAA4AEAAOARoPAAAAAAEA" +
+ "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" +
+ "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" +
+ "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" +
+ "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" +
+ "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" +
+ "YW5zZm9ybTIAFFRyYW5zZm9ybTIgc2F5cyBieWUhABdUcmFuc2Zvcm0yIHNheXMgaGkgYW5kIAAB" +
+ "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" +
+ "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" +
+ "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" +
+ "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAAGAgATIAwEJsAMB" +
+ "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" +
+ "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" +
+ "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" +
+ "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" +
+ "mAQAAA==");
+
+
+ public static void run() {
+ doTest();
+ }
+
+ public static void doTest() {
+ Transform1.sayHi();
+ Transform2.sayHi();
+ System.out.println(
+ "Redefining both " + Transform1.class + " and " + Transform2.class + " to use each other.");
+ Redefinition.doMultiStructuralClassRedefinition(
+ new Redefinition.DexOnlyClassDefinition(Transform1.class, T1_BYTES),
+ new Redefinition.DexOnlyClassDefinition(Transform2.class, T2_BYTES));
+ Transform1.sayHi();
+ Transform2.sayHi();
+ }
+}
diff --git a/test/1991-hello-structural-retransform/expected.txt b/test/1991-hello-structural-retransform/expected.txt
new file mode 100644
index 0000000..7478dda
--- /dev/null
+++ b/test/1991-hello-structural-retransform/expected.txt
@@ -0,0 +1,2 @@
+hello
+I say hello and you say goodbye!
diff --git a/test/1991-hello-structural-retransform/info.txt b/test/1991-hello-structural-retransform/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/1991-hello-structural-retransform/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/1991-hello-structural-retransform/run b/test/1991-hello-structural-retransform/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1991-hello-structural-retransform/run
@@ -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-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1991-hello-structural-retransform/src/Main.java b/test/1991-hello-structural-retransform/src/Main.java
new file mode 100644
index 0000000..531ca4a
--- /dev/null
+++ b/test/1991-hello-structural-retransform/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1991.run();
+ }
+}
diff --git a/test/1991-hello-structural-retransform/src/art/Redefinition.java b/test/1991-hello-structural-retransform/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1991-hello-structural-retransform/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1991-hello-structural-retransform/src/art/Test1991.java b/test/1991-hello-structural-retransform/src/art/Test1991.java
new file mode 100644
index 0000000..6060c20
--- /dev/null
+++ b/test/1991-hello-structural-retransform/src/art/Test1991.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test1991 {
+
+ static class Transform {
+ public static void sayHi() {
+ System.out.println("hello");
+ }
+ }
+
+
+ /**
+ * base64 encoded class/dex file for
+ * static class Transform {
+ * public static void sayHi() {
+ * System.out.println("I say hello and " + sayGoodbye());
+ * }
+ * public static String sayGoodbye() {
+ * return "you say goodbye!";
+ * }
+ * }
+ */
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQCi0OGZvVpTRbHGfNbo3bfcu60kPpJayMgoBQAAcAAAAHhWNBIAAAAAAAAAAGQEAAAc" +
+ "AAAAcAAAAAoAAADgAAAABAAAAAgBAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAACIAwAAoAEAAC4C" +
+ "AAA2AgAASAIAAEsCAABPAgAAaQIAAHkCAACdAgAAvQIAANQCAADoAgAA/AIAABcDAAArAwAAOgMA" +
+ "AEUDAABIAwAATAMAAFkDAABhAwAAZwMAAGwDAAB1AwAAgQMAAIgDAACSAwAAmQMAAKsDAAAEAAAA" +
+ "BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAAPAAAAAgAAAAYAAAAAAAAAAwAAAAcAAAAo" +
+ "AgAADwAAAAkAAAAAAAAAEAAAAAkAAAAoAgAACAAEABQAAAAAAAIAAAAAAAAAAAAWAAAAAAACABcA" +
+ "AAAEAAMAFQAAAAUAAgAAAAAABwACAAAAAAAHAAEAEgAAAAcAAAAYAAAAAAAAAAAAAAAFAAAAAAAA" +
+ "AA0AAABUBAAAMgQAAAAAAAABAAAAAAAAABoCAAADAAAAGgAaABEAAAABAAEAAQAAABYCAAAEAAAA" +
+ "cBAEAAAADgAEAAAAAgAAAB4CAAAbAAAAYgAAAHEAAQAAAAwBIgIHAHAQBQACABoDAQBuIAYAMgBu" +
+ "IAYAEgBuEAcAAgAMAW4gAwAQAA4ABgAOAAsADgAIAA4BGg8AAAAAAQAAAAYABjxpbml0PgAQSSBz" +
+ "YXkgaGVsbG8gYW5kIAABTAACTEwAGExhcnQvVGVzdDE5OTEkVHJhbnNmb3JtOwAOTGFydC9UZXN0" +
+ "MTk5MTsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3Rh" +
+ "dGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVj" +
+ "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwASTGphdmEv" +
+ "bGFuZy9TeXN0ZW07AA1UZXN0MTk5MS5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFn" +
+ "cwAGYXBwZW5kAARuYW1lAANvdXQAB3ByaW50bG4ACnNheUdvb2RieWUABXNheUhpAAh0b1N0cmlu" +
+ "ZwAFdmFsdWUAEHlvdSBzYXkgZ29vZGJ5ZSEAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1" +
+ "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2" +
+ "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgIBGRgBAgMCEQQIExcOAAADAACAgAS4" +
+ "AwEJoAMBCdADAAAAAAIAAAAjBAAAKQQAAEgEAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAA" +
+ "AQAAABwAAABwAAAAAgAAAAoAAADgAAAAAwAAAAQAAAAIAQAABAAAAAEAAAA4AQAABQAAAAgAAABA" +
+ "AQAABgAAAAEAAACAAQAAASAAAAMAAACgAQAAAyAAAAMAAAAWAgAAARAAAAEAAAAoAgAAAiAAABwA" +
+ "AAAuAgAABCAAAAIAAAAjBAAAACAAAAEAAAAyBAAAAxAAAAIAAABEBAAABiAAAAEAAABUBAAAABAA" +
+ "AAEAAABkBAAA");
+
+
+ public static void run() {
+ Redefinition.setTestConfiguration(Redefinition.Config.STRUCTURAL_TRANSFORM);
+ doTest();
+ }
+
+ public static void doTest() {
+ Transform.sayHi();
+ Redefinition.addCommonTransformationResult("art/Test1991$Transform", new byte[0], DEX_BYTES);
+ Redefinition.enableCommonRetransformation(true);
+ Redefinition.doCommonClassRetransformation(Transform.class);
+ Transform.sayHi();
+ }
+}
diff --git a/test/1993-fallback-non-structural/expected.txt b/test/1993-fallback-non-structural/expected.txt
new file mode 100644
index 0000000..7e2cdf4
--- /dev/null
+++ b/test/1993-fallback-non-structural/expected.txt
@@ -0,0 +1,3 @@
+Can structurally Redefine: false
+hello
+Goodbye
diff --git a/test/1993-fallback-non-structural/info.txt b/test/1993-fallback-non-structural/info.txt
new file mode 100644
index 0000000..3b558e1
--- /dev/null
+++ b/test/1993-fallback-non-structural/info.txt
@@ -0,0 +1,4 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition functions will fall back to non-structural
+redefinition when possible.
diff --git a/test/1993-fallback-non-structural/run b/test/1993-fallback-non-structural/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1993-fallback-non-structural/run
@@ -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-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1993-fallback-non-structural/src/Main.java b/test/1993-fallback-non-structural/src/Main.java
new file mode 100644
index 0000000..61e060c
--- /dev/null
+++ b/test/1993-fallback-non-structural/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1993.run();
+ }
+}
diff --git a/test/1993-fallback-non-structural/src/art/Redefinition.java b/test/1993-fallback-non-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1993-fallback-non-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1993-fallback-non-structural/src/art/Test1993.java b/test/1993-fallback-non-structural/src/art/Test1993.java
new file mode 100644
index 0000000..f442099
--- /dev/null
+++ b/test/1993-fallback-non-structural/src/art/Test1993.java
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test1993 {
+
+ static class Transform {
+ public void sayHi() {
+ // 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:
+ // "Goodbye" < "LTransform;" < "hello".
+ System.out.println("hello");
+ }
+ }
+
+ /**
+ * base64 encoded class/dex file for
+ * class Transform {
+ * public void sayHi() {
+ * System.out.println("Goodbye");
+ * }
+ * }
+ */
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQDxrdbiBcsn0r58mdtcdyDxVUxwbWfShNQwBAAAcAAAAHhWNBIAAAAAAAAAAGwDAAAV" +
+ "AAAAcAAAAAkAAADEAAAAAgAAAOgAAAABAAAAAAEAAAQAAAAIAQAAAQAAACgBAADoAgAASAEAAJIB" +
+ "AACaAQAAowEAAL0BAADNAQAA8QEAABECAAAoAgAAPAIAAFACAABkAgAAcwIAAH4CAACBAgAAhQIA" +
+ "AJICAACYAgAAnQIAAKYCAACtAgAAtAIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" +
+ "DAAAAAwAAAAIAAAAAAAAAA0AAAAIAAAAjAEAAAcABAAQAAAAAAAAAAAAAAAAAAAAEgAAAAQAAQAR" +
+ "AAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAACgAAAFwDAAA7AwAAAAAAAAEAAQABAAAAgAEAAAQA" +
+ "AABwEAMAAAAOAAMAAQACAAAAhAEAAAgAAABiAAAAGgEBAG4gAgAQAA4AAwAOAAUADngAAAAAAQAA" +
+ "AAYABjxpbml0PgAHR29vZGJ5ZQAYTGFydC9UZXN0MTk5MyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3Qx" +
+ "OTkzOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0" +
+ "aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0" +
+ "OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTkzLmphdmEA" +
+ "CVRyYW5zZm9ybQABVgACVkwAC2FjY2Vzc0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhp" +
+ "AAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hh" +
+ "LTEiOiJjZDkwMDIzOTMwZDk3M2Y1NzcxMWYxZDRmZGFhZDdhM2U0NzE0NjM3IiwidmVyc2lvbiI6" +
+ "IjEuNy4xNC1kZXYifQACAgETGAECAwIOBAgPFwsAAAEBAICABMgCAQHgAgAAAAAAAAACAAAALAMA" +
+ "ADIDAABQAwAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAVAAAAcAAAAAIAAAAJAAAA" +
+ "xAAAAAMAAAACAAAA6AAAAAQAAAABAAAAAAEAAAUAAAAEAAAACAEAAAYAAAABAAAAKAEAAAEgAAAC" +
+ "AAAASAEAAAMgAAACAAAAgAEAAAEQAAABAAAAjAEAAAIgAAAVAAAAkgEAAAQgAAACAAAALAMAAAAg" +
+ "AAABAAAAOwMAAAMQAAACAAAATAMAAAYgAAABAAAAXAMAAAAQAAABAAAAbAMAAA==");
+
+ public static void run() {
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+ doTest(new Transform());
+ }
+
+ public static void doTest(Transform t) {
+ // TODO Remove this once the class is structurally modifiable.
+ System.out.println("Can structurally Redefine: " +
+ Redefinition.isStructurallyModifiable(Transform.class));
+ t.sayHi();
+ Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+ t.sayHi();
+ }
+}
diff --git a/test/jvmti-common/Redefinition.java b/test/jvmti-common/Redefinition.java
index 2ebce17..3402fa1 100644
--- a/test/jvmti-common/Redefinition.java
+++ b/test/jvmti-common/Redefinition.java
@@ -19,7 +19,7 @@
import java.util.ArrayList;
// Common Redefinition functions. Placed here for use by CTS
public class Redefinition {
- public static final class CommonClassDefinition {
+ public static class CommonClassDefinition {
public final Class<?> target;
public final byte[] class_file_bytes;
public final byte[] dex_file_bytes;
@@ -31,12 +31,19 @@
}
}
+ public static class DexOnlyClassDefinition extends CommonClassDefinition {
+ public DexOnlyClassDefinition(Class<?> target, byte[] dex_file_bytes) {
+ super(target, new byte[0], dex_file_bytes);
+ }
+ }
+
// A set of possible test configurations. Test should set this if they need to.
// This must be kept in sync with the defines in ti-agent/common_helper.cc
public static enum Config {
COMMON_REDEFINE(0),
COMMON_RETRANSFORM(1),
- COMMON_TRANSFORM(2);
+ COMMON_TRANSFORM(2),
+ STRUCTURAL_TRANSFORM(3);
private final int val;
private Config(int val) {
@@ -90,5 +97,18 @@
byte[] dex_bytes);
public static native void doCommonStructuralClassRedefinition(Class<?> target, byte[] dex_file);
+ public static void doMultiStructuralClassRedefinition(CommonClassDefinition... defs) {
+ ArrayList<Class<?>> classes = new ArrayList<>();
+ ArrayList<byte[]> dex_files = new ArrayList<>();
+
+ for (CommonClassDefinition d : defs) {
+ classes.add(d.target);
+ dex_files.add(d.dex_file_bytes);
+ }
+ doCommonMultiStructuralClassRedefinition(classes.toArray(new Class<?>[0]),
+ dex_files.toArray(new byte[0][]));
+ }
+ public static native void doCommonMultiStructuralClassRedefinition(Class<?>[] targets,
+ byte[][] dexfiles);
public static native boolean isStructurallyModifiable(Class<?> target);
}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 2e34943..049ff0d 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1140,9 +1140,12 @@
"1985-structural-redefine-stack-scope",
"1986-structural-redefine-multi-thread-stack-scope",
"1987-structural-redefine-recursive-stack-scope",
+ "1988-multi-structural-redefine",
"1989-transform-bad-monitor",
"1990-structural-bad-verify",
- "1992-retransform-no-such-field"
+ "1991-hello-structural-retransform",
+ "1992-retransform-no-such-field",
+ "1993-fallback-non-structural"
],
"variant": "jvm",
"description": ["Doesn't run on RI."]
diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc
index ae1a8d3..22bc64a 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -238,6 +238,26 @@
}
}
+jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name) {
+ jint n_ext = 0;
+ jint res = -1;
+ bool found_res = false;
+ jvmtiExtensionEventInfo* infos = nullptr;
+ CHECK_EQ(jvmti->GetExtensionEvents(&n_ext, &infos), JVMTI_ERROR_NONE);
+ for (jint i = 0; i < n_ext; i++) {
+ const jvmtiExtensionEventInfo& info = infos[i];
+ if (name == info.id) {
+ res = info.extension_event_index;
+ found_res = true;
+ }
+ DeallocParams(jvmti, info.params, info.param_count);
+ Dealloc(jvmti, info.short_description, info.id, info.params);
+ }
+ Dealloc(jvmti, infos);
+ CHECK(found_res);
+ return res;
+}
+
void* GetExtensionFunctionVoid(JNIEnv* env, jvmtiEnv* jvmti, const std::string_view& name) {
jint n_ext = 0;
void* res = nullptr;
diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h
index a3b9535..74d594f 100644
--- a/test/ti-agent/jvmti_helper.h
+++ b/test/ti-agent/jvmti_helper.h
@@ -94,6 +94,8 @@
return reinterpret_cast<T>(GetExtensionFunctionVoid(env, jvmti, name));
}
+jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name);
+
} // namespace art
#endif // ART_TEST_TI_AGENT_JVMTI_HELPER_H_
diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc
index 9d9f13f..0baa9fe 100644
--- a/test/ti-agent/redefinition_helper.cc
+++ b/test/ti-agent/redefinition_helper.cc
@@ -31,8 +31,13 @@
namespace art {
+enum class RedefineType {
+ kNormal,
+ kStructural,
+};
+
static void SetupCommonRedefine();
-static void SetupCommonRetransform();
+static void SetupCommonRetransform(RedefineType type);
static void SetupCommonTransform();
template <bool is_redefine>
static void throwCommonRedefinitionError(jvmtiEnv* jvmti,
@@ -68,6 +73,7 @@
#define CONFIGURATION_COMMON_REDEFINE 0
#define CONFIGURATION_COMMON_RETRANSFORM 1
#define CONFIGURATION_COMMON_TRANSFORM 2
+#define CONFIGURATION_STRUCTURAL_TRANSFORM 3
extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*,
jclass,
@@ -78,21 +84,54 @@
return;
}
case CONFIGURATION_COMMON_RETRANSFORM: {
- SetupCommonRetransform();
+ SetupCommonRetransform(RedefineType::kNormal);
return;
}
case CONFIGURATION_COMMON_TRANSFORM: {
SetupCommonTransform();
return;
}
+ case CONFIGURATION_STRUCTURAL_TRANSFORM: {
+ SetupCommonRetransform(RedefineType::kStructural);
+ return;
+ }
default: {
LOG(FATAL) << "Unknown test configuration: " << type;
}
}
}
+template<RedefineType kType>
+static bool SupportsAndIsJVM() {
+ if constexpr (kType == RedefineType::kStructural) {
+ return false;
+ } else {
+ return IsJVM();
+ }
+}
+
+
namespace common_redefine {
+template <RedefineType kType>
+static jvmtiError CallRedefineEntrypoint(JNIEnv* env,
+ jvmtiEnv* jvmti,
+ jint num_defs,
+ const jvmtiClassDefinition* defs) {
+ decltype(jvmti->functions->RedefineClasses) entrypoint = nullptr;
+ if constexpr (kType == RedefineType::kNormal) {
+ entrypoint = jvmti->functions->RedefineClasses;
+ } else {
+ entrypoint = GetExtensionFunction<decltype(entrypoint)>(
+ env, jvmti_env, "com.android.art.class.structurally_redefine_classes");
+ }
+ if (entrypoint == nullptr) {
+ LOG(INFO) << "Could not find entrypoint!";
+ return JVMTI_ERROR_NOT_AVAILABLE;
+ }
+ return entrypoint(jvmti, num_defs, defs);
+}
+
static void throwRedefinitionError(jvmtiEnv* jvmti,
JNIEnv* env,
jint num_targets,
@@ -101,6 +140,7 @@
return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res);
}
+template<RedefineType kType>
static void DoMultiClassRedefine(jvmtiEnv* jvmti_env,
JNIEnv* env,
jint num_redefines,
@@ -109,24 +149,25 @@
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];
+ jbyteArray desired_array = SupportsAndIsJVM<kType>() ? 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());
+ jvmtiError res = CallRedefineEntrypoint<kType>(env, jvmti_env, num_redefines, defs.data());
if (res != JVMTI_ERROR_NONE) {
throwRedefinitionError(jvmti_env, env, num_redefines, targets, res);
}
}
+template<RedefineType kType>
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);
+ return DoMultiClassRedefine<kType>(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes);
}
extern "C" JNIEXPORT jboolean JNICALL
@@ -145,25 +186,44 @@
extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonStructuralClassRedefinition(
JNIEnv* env, jclass, jclass target, jbyteArray dex_file_bytes) {
- using ArtStructurallyRedefineClassDirect =
- jvmtiError (*)(jvmtiEnv * env, jclass k, jbyte* data, jint len);
- ArtStructurallyRedefineClassDirect redef =
- GetExtensionFunction<ArtStructurallyRedefineClassDirect>(
- env, jvmti_env, "com.android.art.UNSAFE.class.structurally_redefine_class_direct");
- if (redef == nullptr || env->ExceptionCheck()) {
- return;
- }
- jint len = env->GetArrayLength(dex_file_bytes);
- std::vector<jbyte> v(len, 0);
- env->GetByteArrayRegion(dex_file_bytes, 0, len, v.data());
- JvmtiErrorToException(env, jvmti_env, redef(jvmti_env, target, v.data(), len));
+ DoClassRedefine<RedefineType::kStructural>(jvmti_env, env, target, nullptr, 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_art_Redefinition_doCommonClassRedefinition(
JNIEnv* env, jclass, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) {
- DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes);
+ DoClassRedefine<RedefineType::kNormal>(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_art_Redefinition_doCommonMultiStructuralClassRedefinition(
+ JNIEnv* env,
+ jclass,
+ jobjectArray targets,
+ 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(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(nullptr);
+ }
+ return DoMultiClassRedefine<RedefineType::kStructural>(jvmti_env,
+ env,
+ len,
+ classes.data(),
+ class_files.data(),
+ dex_files.data());
}
// Magic JNI export that classes can use for redefining classes.
@@ -189,12 +249,12 @@
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());
+ return DoMultiClassRedefine<RedefineType::kNormal>(jvmti_env,
+ env,
+ len,
+ classes.data(),
+ class_files.data(),
+ dex_files.data());
}
// Get all capabilities except those related to retransformation.
@@ -380,7 +440,7 @@
printf("Unable to get jvmti env!\n");
return 1;
}
- SetupCommonRetransform();
+ SetupCommonRetransform(RedefineType::kNormal);
return 0;
}
@@ -409,11 +469,20 @@
jvmti_env->AddCapabilities(&caps);
}
-static void SetupCommonRetransform() {
+static void SetupCommonRetransform(RedefineType type) {
SetStandardCapabilities(jvmti_env);
- current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
- jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks));
- CHECK_EQ(res, JVMTI_ERROR_NONE);
+ if (type == RedefineType::kNormal) {
+ current_callbacks.ClassFileLoadHook =
+ common_retransform::CommonClassFileLoadHookRetransformable;
+ jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks));
+ CHECK_EQ(res, JVMTI_ERROR_NONE);
+ } else {
+ jvmtiError res = jvmti_env->SetExtensionEventCallback(
+ GetExtensionEventId(jvmti_env, "com.android.art.class.structural_dex_file_load_hook"),
+ reinterpret_cast<jvmtiExtensionEvent>(
+ common_retransform::CommonClassFileLoadHookRetransformable));
+ CHECK_EQ(res, JVMTI_ERROR_NONE);
+ }
common_retransform::gTransformations.clear();
}