Implement RetransformClasses
This CL implements basic support for the RetransformClasses function
and callbacks of the ClassFileLoadHook.
We do not yet support calling the ClassFileLoadHook events on first
load of class.
Bug: 32369913
Bug: 31684920
Test: mma -j40 test-art-host
Change-Id: I7959474f03f9903cc6f10ae3c06d9fd531ec7957
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 2c6d3ed..8799c91 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -18,6 +18,7 @@
#include <stdio.h>
#include <sstream>
+#include <deque>
#include "art_method.h"
#include "jni.h"
@@ -60,17 +61,17 @@
return true;
}
-namespace common_redefine {
-static void throwRedefinitionError(jvmtiEnv* jvmti,
- JNIEnv* env,
- jint num_targets,
- jclass* target,
- jvmtiError res) {
+template <bool is_redefine>
+static void throwCommonRedefinitionError(jvmtiEnv* jvmti,
+ JNIEnv* env,
+ jint num_targets,
+ jclass* target,
+ jvmtiError res) {
std::stringstream err;
char* error = nullptr;
jvmti->GetErrorName(res, &error);
- err << "Failed to redefine class";
+ err << "Failed to " << (is_redefine ? "redefine" : "retransform") << " class";
if (num_targets > 1) {
err << "es";
}
@@ -92,6 +93,16 @@
env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str());
}
+namespace common_redefine {
+
+static void throwRedefinitionError(jvmtiEnv* jvmti,
+ JNIEnv* env,
+ jint num_targets,
+ jclass* target,
+ jvmtiError res) {
+ return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res);
+}
+
static void DoMultiClassRedefine(jvmtiEnv* jvmti_env,
JNIEnv* env,
jint num_redefines,
@@ -161,7 +172,138 @@
dex_files.data());
}
-// Don't do anything
+// Get all capabilities except those related to retransformation.
+jint OnLoad(JavaVM* vm,
+ char* options ATTRIBUTE_UNUSED,
+ void* reserved ATTRIBUTE_UNUSED) {
+ if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+ printf("Unable to get jvmti env!\n");
+ return 1;
+ }
+ jvmtiCapabilities caps;
+ jvmti_env->GetPotentialCapabilities(&caps);
+ caps.can_retransform_classes = 0;
+ caps.can_retransform_any_class = 0;
+ jvmti_env->AddCapabilities(&caps);
+ return 0;
+}
+
+} // namespace common_redefine
+
+namespace common_retransform {
+
+struct CommonTransformationResult {
+ std::vector<unsigned char> class_bytes;
+ std::vector<unsigned char> dex_bytes;
+
+ CommonTransformationResult(size_t class_size, size_t dex_size)
+ : class_bytes(class_size), dex_bytes(dex_size) {}
+
+ CommonTransformationResult() = default;
+ CommonTransformationResult(CommonTransformationResult&&) = default;
+ CommonTransformationResult(CommonTransformationResult&) = default;
+};
+
+// Map from class name to transformation result.
+std::map<std::string, std::deque<CommonTransformationResult>> gTransformations;
+
+extern "C" JNIEXPORT void JNICALL Java_Main_addCommonTransformationResult(JNIEnv* env,
+ jclass,
+ jstring class_name,
+ jbyteArray class_array,
+ jbyteArray dex_array) {
+ const char* name_chrs = env->GetStringUTFChars(class_name, nullptr);
+ std::string name_str(name_chrs);
+ env->ReleaseStringUTFChars(class_name, name_chrs);
+ CommonTransformationResult trans(env->GetArrayLength(class_array),
+ env->GetArrayLength(dex_array));
+ if (env->ExceptionOccurred()) {
+ return;
+ }
+ env->GetByteArrayRegion(class_array,
+ 0,
+ env->GetArrayLength(class_array),
+ reinterpret_cast<jbyte*>(trans.class_bytes.data()));
+ if (env->ExceptionOccurred()) {
+ return;
+ }
+ env->GetByteArrayRegion(dex_array,
+ 0,
+ env->GetArrayLength(dex_array),
+ reinterpret_cast<jbyte*>(trans.dex_bytes.data()));
+ if (env->ExceptionOccurred()) {
+ return;
+ }
+ if (gTransformations.find(name_str) == gTransformations.end()) {
+ std::deque<CommonTransformationResult> list;
+ gTransformations[name_str] = std::move(list);
+ }
+ gTransformations[name_str].push_back(std::move(trans));
+}
+
+// The hook we are using.
+void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env,
+ JNIEnv* jni_env ATTRIBUTE_UNUSED,
+ jclass class_being_redefined ATTRIBUTE_UNUSED,
+ jobject loader ATTRIBUTE_UNUSED,
+ const char* name,
+ jobject protection_domain ATTRIBUTE_UNUSED,
+ jint class_data_len ATTRIBUTE_UNUSED,
+ const unsigned char* class_dat ATTRIBUTE_UNUSED,
+ jint* new_class_data_len,
+ unsigned char** new_class_data) {
+ std::string name_str(name);
+ if (gTransformations.find(name_str) != gTransformations.end()) {
+ CommonTransformationResult& res = gTransformations[name_str][0];
+ const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes;
+ unsigned char* new_data;
+ jvmti_env->Allocate(desired_array.size(), &new_data);
+ memcpy(new_data, desired_array.data(), desired_array.size());
+ *new_class_data = new_data;
+ *new_class_data_len = desired_array.size();
+ gTransformations[name_str].pop_front();
+ }
+}
+
+extern "C" JNIEXPORT void Java_Main_enableCommonRetransformation(JNIEnv* env,
+ jclass,
+ jboolean enable) {
+ jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE,
+ JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+ nullptr);
+ if (res != JVMTI_ERROR_NONE) {
+ JvmtiErrorToException(env, res);
+ }
+}
+
+static void throwRetransformationError(jvmtiEnv* jvmti,
+ JNIEnv* env,
+ jint num_targets,
+ jclass* targets,
+ jvmtiError res) {
+ return throwCommonRedefinitionError<false>(jvmti, env, num_targets, targets, res);
+}
+
+static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) {
+ std::vector<jclass> classes;
+ jint len = env->GetArrayLength(targets);
+ for (jint i = 0; i < len; i++) {
+ classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i)));
+ }
+ jvmtiError res = jvmti_env->RetransformClasses(len, classes.data());
+ if (res != JVMTI_ERROR_NONE) {
+ throwRetransformationError(jvmti_env, env, len, classes.data(), res);
+ }
+}
+
+// TODO Write something useful.
+extern "C" JNIEXPORT void JNICALL Java_Main_doCommonClassRetransformation(JNIEnv* env,
+ jclass,
+ jobjectArray targets) {
+ DoClassRetransformation(jvmti_env, env, targets);
+}
+
+// Get all capabilities except those related to retransformation.
jint OnLoad(JavaVM* vm,
char* options ATTRIBUTE_UNUSED,
void* reserved ATTRIBUTE_UNUSED) {
@@ -170,9 +312,16 @@
return 1;
}
SetAllCapabilities(jvmti_env);
+ jvmtiEventCallbacks cb;
+ memset(&cb, 0, sizeof(cb));
+ cb.ClassFileLoadHook = CommonClassFileLoadHookRetransformable;
+ if (jvmti_env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
+ printf("Unable to set class file load hook cb!\n");
+ return 1;
+ }
return 0;
}
-} // namespace common_redefine
+} // namespace common_retransform
} // namespace art
diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h
index 642ca03..8599fc4 100644
--- a/test/ti-agent/common_helper.h
+++ b/test/ti-agent/common_helper.h
@@ -27,6 +27,10 @@
jint OnLoad(JavaVM* vm, char* options, void* reserved);
} // namespace common_redefine
+namespace common_retransform {
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+} // namespace common_retransform
+
extern bool RuntimeIsJVM;
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index 521e672..1b11442 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -64,8 +64,9 @@
{ "916-obsolete-jit", common_redefine::OnLoad, nullptr },
{ "917-fields-transformation", common_redefine::OnLoad, nullptr },
{ "919-obsolete-fields", common_redefine::OnLoad, nullptr },
- { "921-hello-failure", common_redefine::OnLoad, nullptr },
+ { "921-hello-failure", common_retransform::OnLoad, nullptr },
{ "926-multi-obsolescence", common_redefine::OnLoad, nullptr },
+ { "930-hello-retransform", common_retransform::OnLoad, nullptr },
};
static AgentLib* FindAgent(char* name) {