summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alex Light <allight@google.com> 2019-10-15 15:46:07 -0700
committer Alex Light <allight@google.com> 2019-10-30 16:53:25 -0700
commitd55b844e39c4d5eb1a56de6cb95c891659f8a27f (patch)
treeed2f7809528e8b44985edc12d75fb0965806045f
parent436c6f5fae95aae332361060778599d0ef24a167 (diff)
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
-rw-r--r--openjdkjvmti/events-inl.h99
-rw-r--r--openjdkjvmti/events.cc5
-rw-r--r--openjdkjvmti/events.h20
-rw-r--r--openjdkjvmti/ti_class.cc3
-rw-r--r--openjdkjvmti/ti_class_definition.h16
-rw-r--r--openjdkjvmti/ti_extension.cc69
-rw-r--r--openjdkjvmti/ti_redefine.cc49
-rw-r--r--openjdkjvmti/ti_redefine.h8
-rw-r--r--openjdkjvmti/transform.cc48
-rw-r--r--openjdkjvmti/transform.h2
-rw-r--r--test/1988-multi-structural-redefine/expected.txt5
-rw-r--r--test/1988-multi-structural-redefine/info.txt1
-rwxr-xr-xtest/1988-multi-structural-redefine/run18
-rw-r--r--test/1988-multi-structural-redefine/src/Main.java21
l---------test/1988-multi-structural-redefine/src/art/Redefinition.java1
-rw-r--r--test/1988-multi-structural-redefine/src/art/Test1988.java126
-rw-r--r--test/1991-hello-structural-retransform/expected.txt2
-rw-r--r--test/1991-hello-structural-retransform/info.txt1
-rwxr-xr-xtest/1991-hello-structural-retransform/run17
-rw-r--r--test/1991-hello-structural-retransform/src/Main.java21
l---------test/1991-hello-structural-retransform/src/art/Redefinition.java1
-rw-r--r--test/1991-hello-structural-retransform/src/art/Test1991.java79
-rw-r--r--test/1993-fallback-non-structural/expected.txt3
-rw-r--r--test/1993-fallback-non-structural/info.txt4
-rwxr-xr-xtest/1993-fallback-non-structural/run17
-rw-r--r--test/1993-fallback-non-structural/src/Main.java21
l---------test/1993-fallback-non-structural/src/art/Redefinition.java1
-rw-r--r--test/1993-fallback-non-structural/src/art/Test1993.java77
-rw-r--r--test/jvmti-common/Redefinition.java24
-rw-r--r--test/knownfailures.json5
-rw-r--r--test/ti-agent/jvmti_helper.cc20
-rw-r--r--test/ti-agent/jvmti_helper.h2
-rw-r--r--test/ti-agent/redefinition_helper.cc127
33 files changed, 805 insertions, 108 deletions
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index 22822f8b40..23f7151c99 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -90,41 +90,42 @@ class ScopedEventDispatchEnvironment final : public art::ValueObject {
// 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 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread,
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 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetr
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 3f205eb6f4..56406fc81d 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -100,6 +100,9 @@ jvmtiError ArtJvmtiEventCallbacks::Set(jint index, jvmtiExtensionEvent cb) {
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 @@ bool IsExtensionEvent(ArtJvmtiEvent e) {
switch (e) {
case ArtJvmtiEvent::kDdmPublishChunk:
case ArtJvmtiEvent::kObsoleteObjectCreated:
+ case ArtJvmtiEvent::kStructuralDexFileLoadHook:
return true;
default:
return false;
@@ -1175,6 +1179,7 @@ static DeoptRequirement GetDeoptRequirement(ArtJvmtiEvent event, jthread thread)
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 d5ab4fbc98..c9d587af94 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -81,7 +81,8 @@ enum class ArtJvmtiEvent : jint {
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 @@ using ArtJvmtiEventObsoleteObjectCreated = void (*)(jvmtiEnv *jvmti_env,
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 UniqueThreadHasher {
};
struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks {
- ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr), ObsoleteObjectCreated(nullptr) {
+ ArtJvmtiEventCallbacks()
+ : DdmPublishChunk(nullptr),
+ ObsoleteObjectCreated(nullptr),
+ StructuralDexFileLoadHook(nullptr) {
memset(this, 0, sizeof(jvmtiEventCallbacks));
}
@@ -131,6 +146,7 @@ struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks {
ArtJvmtiEventDdmPublishChunk DdmPublishChunk;
ArtJvmtiEventObsoleteObjectCreated ObsoleteObjectCreated;
+ ArtJvmtiEventStructuralDexFileLoadHook StructuralDexFileLoadHook;
};
bool IsExtensionEvent(jint e);
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 988274b4d7..82ce916ccd 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -204,6 +204,9 @@ struct ClassCallback : public art::ClassLoadCallback {
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 224e664459..cb0853bdb5 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 @@ class ArtClassDefinition {
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 @@ class ArtClassDefinition {
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 @@ class ArtClassDefinition {
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 @@ class ArtClassDefinition {
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 5dc7445681..058a188630 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 @@ jvmtiError ExtensionUtil::GetExtensionFunctions(jvmtiEnv* env,
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 @@ jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env,
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 @@ jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env,
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 05d7de7f10..22a3bc5f06 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -376,7 +376,7 @@ jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/ std::strin
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 @@ Redefiner::ClassRedefinition::~ClassRedefinition() {
}
}
-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 @@ jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv,
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 @@ jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv,
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 @@ bool Redefiner::ClassRedefinition::CheckRedefinable() {
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::CheckRedefinable() {
}
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 b028dee8a2..58a688c1a0 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -87,6 +87,9 @@ class Redefiner {
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 @@ class Redefiner {
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 aa37793e49..613368525e 100644
--- a/openjdkjvmti/transform.cc
+++ b/openjdkjvmti/transform.cc
@@ -255,13 +255,17 @@ void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookNo
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 @@ void Transformer::TransformSingleClassDirect(EventHandler* event_handler,
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 @@ jvmtiError Transformer::RetransformClasses(jvmtiEnv* env,
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 @@ jvmtiError Transformer::RetransformClasses(jvmtiEnv* env,
}
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 40c7267ad1..a58b50ea10 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 @@ class Transformer {
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 0000000000..00aea886c4
--- /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 0000000000..875a5f6ec1
--- /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 0000000000..a36de16ea6
--- /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 0000000000..7e95671cab
--- /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 0000000000..81eaf31bbb
--- /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 0000000000..6dab4daa20
--- /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 0000000000..7478dda33a
--- /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 0000000000..875a5f6ec1
--- /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 0000000000..03e41a58e7
--- /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 0000000000..531ca4afaf
--- /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 0000000000..81eaf31bbb
--- /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 0000000000..6060c202cd
--- /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 0000000000..7e2cdf4ee5
--- /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 0000000000..3b558e1cd1
--- /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 0000000000..03e41a58e7
--- /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 0000000000..61e060c773
--- /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 0000000000..81eaf31bbb
--- /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 0000000000..f4420993f7
--- /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 2ebce17686..3402fa12b5 100644
--- a/test/jvmti-common/Redefinition.java
+++ b/test/jvmti-common/Redefinition.java
@@ -19,7 +19,7 @@ package art;
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 class Redefinition {
}
}
+ 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 @@ public class Redefinition {
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 2e34943401..049ff0dcbb 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 ae1a8d34f3..22bc64ac01 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -238,6 +238,26 @@ void DeallocParams(jvmtiEnv* env, jvmtiParamInfo* params, jint n_params) {
}
}
+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 a3b95353c7..74d594ff04 100644
--- a/test/ti-agent/jvmti_helper.h
+++ b/test/ti-agent/jvmti_helper.h
@@ -94,6 +94,8 @@ template<typename T> T GetExtensionFunction(JNIEnv* env, jvmtiEnv* jvmti, const
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 9d9f13fa39..0baa9fe547 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 @@ static void throwCommonRedefinitionError(jvmtiEnv* jvmti,
#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 @@ extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfigurati
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 @@ static void throwRedefinitionError(jvmtiEnv* jvmti,
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 @@ static void DoMultiClassRedefine(jvmtiEnv* jvmti_env,
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 @@ Java_art_Redefinition_isStructurallyModifiable(JNIEnv* env, jclass, jclass targe
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 @@ extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiClassRedefi
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 @@ jint OnLoad(JavaVM* vm,
printf("Unable to get jvmti env!\n");
return 1;
}
- SetupCommonRetransform();
+ SetupCommonRetransform(RedefineType::kNormal);
return 0;
}
@@ -409,11 +469,20 @@ static void SetupCommonRedefine() {
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(&current_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(&current_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();
}