summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--openjdkjvmti/ti_redefine.cc34
-rw-r--r--openjdkjvmti/ti_redefine.h4
-rw-r--r--openjdkjvmti/transform.cc5
-rwxr-xr-xtest/1950-unprepared-transform/check22
-rw-r--r--test/1950-unprepared-transform/expected.txt7
-rw-r--r--test/1950-unprepared-transform/info.txt1
-rw-r--r--test/1950-unprepared-transform/jvm-expected.patch6
-rwxr-xr-xtest/1950-unprepared-transform/run17
-rw-r--r--test/1950-unprepared-transform/src-ex/Transform.java22
-rw-r--r--test/1950-unprepared-transform/src/Main.java153
-rw-r--r--test/1950-unprepared-transform/src/art/Redefinition.java91
-rw-r--r--test/1950-unprepared-transform/unprepared_transform.cc77
-rw-r--r--test/Android.bp1
-rw-r--r--test/ti-agent/breakpoint_helper.cc9
-rw-r--r--test/ti-agent/exceptions_helper.cc11
-rw-r--r--test/ti-agent/frame_pop_helper.cc9
-rw-r--r--test/ti-agent/monitors_helper.cc15
-rw-r--r--test/ti-agent/redefinition_helper.cc12
-rw-r--r--test/ti-agent/test_env.cc1
-rw-r--r--test/ti-agent/test_env.h5
-rw-r--r--test/ti-agent/trace_helper.cc23
21 files changed, 477 insertions, 48 deletions
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 5d430d2073..a23baa5095 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -234,12 +234,39 @@ jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
std::string err_unused;
*is_redefinable =
- Redefiner::GetClassRedefinitionError(h_klass, &err_unused) == OK ? JNI_TRUE : JNI_FALSE;
+ Redefiner::GetClassRedefinitionError(h_klass, &err_unused) != ERR(UNMODIFIABLE_CLASS)
+ ? JNI_TRUE : JNI_FALSE;
return OK;
}
+jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg) {
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::StackHandleScope<1> hs(self);
+ art::ObjPtr<art::mirror::Object> obj(self->DecodeJObject(klass));
+ if (obj.IsNull()) {
+ return ERR(INVALID_CLASS);
+ }
+ art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
+ return Redefiner::GetClassRedefinitionError(h_klass, error_msg);
+}
+
jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
/*out*/std::string* error_msg) {
+ if (!klass->IsResolved()) {
+ // It's only a problem to try to retransform/redefine a unprepared class if it's happening on
+ // the same thread as the class-linking process. If it's on another thread we will be able to
+ // wait for the preparation to finish and continue from there.
+ if (klass->GetLockOwnerThreadId() == art::Thread::Current()->GetThreadId()) {
+ *error_msg = "Modification of class " + klass->PrettyClass() +
+ " from within the classes ClassLoad callback is not supported to prevent deadlocks." +
+ " Please use ClassFileLoadHook directly instead.";
+ return ERR(INTERNAL);
+ } else {
+ LOG(WARNING) << klass->PrettyClass() << " is not yet resolved. Attempting to transform "
+ << "it could cause arbitrary length waits as the class is being resolved.";
+ }
+ }
if (klass->IsPrimitive()) {
*error_msg = "Modification of primitive classes is not supported";
return ERR(UNMODIFIABLE_CLASS);
@@ -332,12 +359,9 @@ jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env,
std::vector<ArtClassDefinition> def_vector;
def_vector.reserve(class_count);
for (jint i = 0; i < class_count; i++) {
- jboolean is_modifiable = JNI_FALSE;
- jvmtiError res = env->IsModifiableClass(definitions[i].klass, &is_modifiable);
+ jvmtiError res = Redefiner::GetClassRedefinitionError(definitions[i].klass, error_msg);
if (res != OK) {
return res;
- } else if (!is_modifiable) {
- return ERR(UNMODIFIABLE_CLASS);
}
// We make a copy of the class_bytes to pass into the retransformation.
// This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 778f87e68e..fa7d28648d 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -98,6 +98,10 @@ class Redefiner {
art::ArrayRef<const unsigned char> data,
std::string* error_msg);
+ // Helper for checking if redefinition/retransformation is allowed.
+ static jvmtiError GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg)
+ REQUIRES(!art::Locks::mutator_lock_);
+
private:
class ClassRedefinition {
public:
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
index 43b8fe94f4..62094a327d 100644
--- a/openjdkjvmti/transform.cc
+++ b/openjdkjvmti/transform.cc
@@ -313,12 +313,9 @@ jvmtiError Transformer::RetransformClasses(ArtJvmTiEnv* env,
std::vector<ArtClassDefinition> definitions;
jvmtiError res = OK;
for (jint i = 0; i < class_count; i++) {
- jboolean is_modifiable = JNI_FALSE;
- res = env->IsModifiableClass(classes[i], &is_modifiable);
+ res = Redefiner::GetClassRedefinitionError(classes[i], error_msg);
if (res != OK) {
return res;
- } else if (!is_modifiable) {
- return ERR(UNMODIFIABLE_CLASS);
}
ArtClassDefinition def;
res = def.Init(self, classes[i]);
diff --git a/test/1950-unprepared-transform/check b/test/1950-unprepared-transform/check
new file mode 100755
index 0000000000..8a84388a8f
--- /dev/null
+++ b/test/1950-unprepared-transform/check
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+# The RI sends an extra event that art doesn't. Add it to the expected output.
+if [[ "$TEST_RUNTIME" == "jvm" ]]; then
+ patch -p0 expected.txt < jvm-expected.patch >/dev/null
+fi
+
+./default-check "$@"
diff --git a/test/1950-unprepared-transform/expected.txt b/test/1950-unprepared-transform/expected.txt
new file mode 100644
index 0000000000..3be28a5696
--- /dev/null
+++ b/test/1950-unprepared-transform/expected.txt
@@ -0,0 +1,7 @@
+Redefine in ClassLoad on current thread.
+Trying to redefine: class Transform. Caught error class java.lang.Exception: Failed to retransform class <LTransform;> due to JVMTI_ERROR_INTERNAL
+Object out is: NON Transformed Object
+Redefine during ClassLoad on another thread.
+retransformClasses on an unprepared class succeeded
+Object out is: Transformed object!
+Redefinition thread finished.
diff --git a/test/1950-unprepared-transform/info.txt b/test/1950-unprepared-transform/info.txt
new file mode 100644
index 0000000000..875a5f6ec1
--- /dev/null
+++ b/test/1950-unprepared-transform/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/1950-unprepared-transform/jvm-expected.patch b/test/1950-unprepared-transform/jvm-expected.patch
new file mode 100644
index 0000000000..fd0131e5e4
--- /dev/null
+++ b/test/1950-unprepared-transform/jvm-expected.patch
@@ -0,0 +1,6 @@
+2,3c2,3
+< Trying to redefine: class Transform. Caught error class java.lang.Exception: Failed to retransform class <LTransform;> due to JVMTI_ERROR_INTERNAL
+< Object out is: NON Transformed Object
+---
+> retransformClasses on an unprepared class succeeded
+> Object out is: Transformed object!
diff --git a/test/1950-unprepared-transform/run b/test/1950-unprepared-transform/run
new file mode 100755
index 0000000000..adb1a1c507
--- /dev/null
+++ b/test/1950-unprepared-transform/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 --no-app-image
diff --git a/test/1950-unprepared-transform/src-ex/Transform.java b/test/1950-unprepared-transform/src-ex/Transform.java
new file mode 100644
index 0000000000..29aa8c68dd
--- /dev/null
+++ b/test/1950-unprepared-transform/src-ex/Transform.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+// This class is caught during loading.
+public class Transform {
+ public String toString() {
+ return "NON Transformed Object";
+ }
+}
diff --git a/test/1950-unprepared-transform/src/Main.java b/test/1950-unprepared-transform/src/Main.java
new file mode 100644
index 0000000000..adbcf38ce3
--- /dev/null
+++ b/test/1950-unprepared-transform/src/Main.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import art.Redefinition;
+
+import java.lang.reflect.*;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+class Main {
+ public static String TEST_NAME = "1950-unprepared-transform";
+
+ // Base 64 encoding of the following class:
+ //
+ // public class Transform {
+ // public String toString() {
+ // return "Transformed object!";
+ // }
+ // }
+ public static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAEQoABAANCAAOBwAPBwAQAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1i" +
+ "ZXJUYWJsZQEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAO" +
+ "VHJhbnNmb3JtLmphdmEMAAUABgEAE1RyYW5zZm9ybWVkIG9iamVjdCEBAAlUcmFuc2Zvcm0BABBq" +
+ "YXZhL2xhbmcvT2JqZWN0ACEAAwAEAAAAAAACAAEABQAGAAEABwAAAB0AAQABAAAABSq3AAGxAAAA" +
+ "AQAIAAAABgABAAAAEgABAAkACgABAAcAAAAbAAEAAQAAAAMSArAAAAABAAgAAAAGAAEAAAAUAAEA" +
+ "CwAAAAIADA==");
+
+ public static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzOACaXU/P8oJOECPrdN1Cu9/ob2cUb2vOKxqYAgAAcAAAAHhWNBIAAAAAAAAAABACAAAK" +
+ "AAAAcAAAAAQAAACYAAAAAgAAAKgAAAAAAAAAAAAAAAMAAADAAAAAAQAAANgAAACgAQAA+AAAADAB" +
+ "AAA4AQAAOwEAAEgBAABcAQAAcAEAAIABAACVAQAAmAEAAKIBAAACAAAAAwAAAAQAAAAHAAAAAQAA" +
+ "AAIAAAAAAAAABwAAAAMAAAAAAAAAAAABAAAAAAAAAAAACAAAAAEAAQAAAAAAAAAAAAEAAAABAAAA" +
+ "AAAAAAUAAAAAAAAAAAIAAAAAAAACAAEAAAAAACwBAAADAAAAGgAGABEAAAABAAEAAQAAACgBAAAE" +
+ "AAAAcBACAAAADgASAA4AFAAOAAY8aW5pdD4AAUwAC0xUcmFuc2Zvcm07ABJMamF2YS9sYW5nL09i" +
+ "amVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAOVHJhbnNmb3JtLmphdmEAE1RyYW5zZm9ybWVkIG9i" +
+ "amVjdCEAAVYACHRvU3RyaW5nAFx+fkQ4eyJtaW4tYXBpIjoyNywic2hhLTEiOiI3YTdjNDlhY2Nj" +
+ "NTkzNTIyNzY4MTY3MThhNGM3YWU1MmY5NjgzZjk5IiwidmVyc2lvbiI6InYxLjIuNC1kZXYifQAA" +
+ "AAEBAIGABJACAQH4AQAACwAAAAAAAAABAAAAAAAAAAEAAAAKAAAAcAAAAAIAAAAEAAAAmAAAAAMA" +
+ "AAACAAAAqAAAAAUAAAADAAAAwAAAAAYAAAABAAAA2AAAAAEgAAACAAAA+AAAAAMgAAACAAAAKAEA" +
+ "AAIgAAAKAAAAMAEAAAAgAAABAAAAAAIAAAAQAAABAAAAEAIAAA==");
+
+ public static native void setupClassLoadHook(Thread target);
+ public static native void clearClassLoadHook(Thread target);
+ private static Consumer<Class<?>> doRedefine = null;
+
+ public static void doClassLoad(Class<?> c) {
+ try {
+ if (c.getName().equals("Transform")) {
+ Redefinition.addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES);
+ doRedefine.accept(c);
+ System.out.println("retransformClasses on an unprepared class succeeded");
+ }
+ } catch (Throwable e) {
+ System.out.println("Trying to redefine: " + c + ". " +
+ "Caught error " + e.getClass() + ": " + e.getMessage());
+ }
+ }
+
+ public static ClassLoader getClassLoaderFor(String location) throws Exception {
+ try {
+ Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader");
+ Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class);
+ /* on Dalvik, this is a DexFile; otherwise, it's null */
+ return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar",
+ Main.class.getClassLoader());
+ } catch (ClassNotFoundException e) {
+ // Running on RI. Use URLClassLoader.
+ return new java.net.URLClassLoader(
+ new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") });
+ }
+ }
+
+ public static void testCurrentThread() throws Throwable {
+ System.out.println("Redefine in ClassLoad on current thread.");
+ doRedefine = (c) -> { Redefinition.doCommonClassRetransformation(c); };
+ ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION"));
+ Class<?> klass = (Class<?>)new_loader.loadClass("Transform");
+ if (klass == null) {
+ throw new AssertionError("loadClass failed");
+ }
+ Object o = klass.newInstance();
+ System.out.println("Object out is: " + o);
+ }
+
+ public static void testRemoteThread() throws Throwable {
+ System.out.println("Redefine during ClassLoad on another thread.");
+ final Class[] loaded = new Class[] { null, };
+ final CountDownLatch gotClass = new CountDownLatch(1);
+ final CountDownLatch wokeUp = new CountDownLatch(1);
+ Thread redef_thread = new Thread(() -> {
+ try {
+ gotClass.await();
+ wokeUp.countDown();
+ // This will wait until the otehr thread returns so we need to wake up the other thread
+ // first.
+ Redefinition.doCommonClassRetransformation(loaded[0]);
+ } catch (Exception e) {
+ throw new Error("Failed to do redef!", e);
+ }
+ });
+ redef_thread.start();
+ doRedefine = (c) -> {
+ try {
+ loaded[0] = c;
+ gotClass.countDown();
+ wokeUp.await();
+ // Let the other thread do some stuff.
+ Thread.sleep(5000);
+ } catch (Exception e) {
+ throw new Error("Failed to do redef!", e);
+ }
+ };
+ ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION"));
+ Class<?> klass = (Class<?>)new_loader.loadClass("Transform");
+ if (klass == null) {
+ throw new AssertionError("loadClass failed");
+ }
+ Object o = klass.newInstance();
+ System.out.println("Object out is: " + o);
+ redef_thread.join();
+ System.out.println("Redefinition thread finished.");
+ }
+
+ public static void main(String[] args) {
+ // make sure we can do the transform.
+ Redefinition.setTestConfiguration(Redefinition.Config.COMMON_RETRANSFORM);
+ Redefinition.setPopRetransformations(false);
+ Redefinition.enableCommonRetransformation(true);
+ setupClassLoadHook(Thread.currentThread());
+ try {
+ testCurrentThread();
+ testRemoteThread();
+ } catch (Throwable e) {
+ System.out.println(e.toString());
+ e.printStackTrace(System.out);
+ }
+ clearClassLoadHook(Thread.currentThread());
+ }
+}
diff --git a/test/1950-unprepared-transform/src/art/Redefinition.java b/test/1950-unprepared-transform/src/art/Redefinition.java
new file mode 100644
index 0000000000..56d2938a01
--- /dev/null
+++ b/test/1950-unprepared-transform/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+ public static final class CommonClassDefinition {
+ public final Class<?> target;
+ public final byte[] class_file_bytes;
+ public final byte[] dex_file_bytes;
+
+ public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+ this.target = target;
+ this.class_file_bytes = class_file_bytes;
+ this.dex_file_bytes = dex_file_bytes;
+ }
+ }
+
+ // 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);
+
+ private final int val;
+ private Config(int val) {
+ this.val = val;
+ }
+ }
+
+ public static void setTestConfiguration(Config type) {
+ nativeSetTestConfiguration(type.val);
+ }
+
+ private static native void nativeSetTestConfiguration(int type);
+
+ // Transforms the class
+ public static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+
+ public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+ ArrayList<Class<?>> classes = new ArrayList<>();
+ ArrayList<byte[]> class_files = new ArrayList<>();
+ ArrayList<byte[]> dex_files = new ArrayList<>();
+
+ for (CommonClassDefinition d : defs) {
+ classes.add(d.target);
+ class_files.add(d.class_file_bytes);
+ dex_files.add(d.dex_file_bytes);
+ }
+ doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+ class_files.toArray(new byte[0][]),
+ dex_files.toArray(new byte[0][]));
+ }
+
+ public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+ for (CommonClassDefinition d : defs) {
+ addCommonTransformationResult(d.target.getCanonicalName(),
+ d.class_file_bytes,
+ d.dex_file_bytes);
+ }
+ }
+
+ public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+ byte[][] classfiles,
+ byte[][] dexfiles);
+ public static native void doCommonClassRetransformation(Class<?>... target);
+ public static native void setPopRetransformations(boolean pop);
+ public static native void popTransformationFor(String name);
+ public static native void enableCommonRetransformation(boolean enable);
+ public static native void addCommonTransformationResult(String target_name,
+ byte[] class_bytes,
+ byte[] dex_bytes);
+}
diff --git a/test/1950-unprepared-transform/unprepared_transform.cc b/test/1950-unprepared-transform/unprepared_transform.cc
new file mode 100644
index 0000000000..620ede887f
--- /dev/null
+++ b/test/1950-unprepared-transform/unprepared_transform.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+
+#include <cstdio>
+#include <iostream>
+#include <mutex>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1950UnpreparedTransform {
+
+jclass kMainClass = nullptr;
+jmethodID kPrepareFunc = nullptr;
+
+extern "C" JNIEXPORT void ClassLoadCallback(jvmtiEnv* jvmti ATTRIBUTE_UNUSED,
+ JNIEnv* env,
+ jthread thr ATTRIBUTE_UNUSED,
+ jclass klass) {
+ env->CallStaticVoidMethod(kMainClass, kPrepareFunc, klass);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_clearClassLoadHook(
+ JNIEnv* env, jclass main ATTRIBUTE_UNUSED, jthread thr) {
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+ JVMTI_EVENT_CLASS_LOAD,
+ thr));
+}
+extern "C" JNIEXPORT void JNICALL Java_Main_setupClassLoadHook(
+ JNIEnv* env, jclass main, jthread thr) {
+ kMainClass = reinterpret_cast<jclass>(env->NewGlobalRef(main));
+ kPrepareFunc = env->GetStaticMethodID(main, "doClassLoad", "(Ljava/lang/Class;)V");
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ current_callbacks.ClassLoad = ClassLoadCallback;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(
+ &current_callbacks, sizeof(current_callbacks)))) {
+ return;
+ }
+ JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_CLASS_LOAD,
+ thr));
+}
+
+} // namespace Test1950UnpreparedTransform
+} // namespace art
diff --git a/test/Android.bp b/test/Android.bp
index 0c1edcaab8..bd13de2fa9 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -277,6 +277,7 @@ art_cc_defaults {
"1942-suspend-raw-monitor-exit/native_suspend_monitor.cc",
"1943-suspend-raw-monitor-wait/native_suspend_monitor.cc",
"1946-list-descriptors/descriptors.cc",
+ "1950-unprepared-transform/unprepared_transform.cc",
],
// Use NDK-compatible headers for ctstiagent.
header_libs: [
diff --git a/test/ti-agent/breakpoint_helper.cc b/test/ti-agent/breakpoint_helper.cc
index 78aab4376f..db4ea61f1c 100644
--- a/test/ti-agent/breakpoint_helper.cc
+++ b/test/ti-agent/breakpoint_helper.cc
@@ -172,10 +172,11 @@ extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch(
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.Breakpoint = breakpointCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.Breakpoint = breakpointCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
if (JvmtiErrorToException(env,
diff --git a/test/ti-agent/exceptions_helper.cc b/test/ti-agent/exceptions_helper.cc
index 74f7ecc881..e56c39b9eb 100644
--- a/test/ti-agent/exceptions_helper.cc
+++ b/test/ti-agent/exceptions_helper.cc
@@ -147,11 +147,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Exceptions_setupExceptionTracing(
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.Exception = exceptionCB;
- cb.ExceptionCatch = exceptionCatchCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.Exception = exceptionCB;
+ current_callbacks.ExceptionCatch = exceptionCatchCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
}
diff --git a/test/ti-agent/frame_pop_helper.cc b/test/ti-agent/frame_pop_helper.cc
index 4571032ce6..f39e1854bc 100644
--- a/test/ti-agent/frame_pop_helper.cc
+++ b/test/ti-agent/frame_pop_helper.cc
@@ -90,10 +90,11 @@ extern "C" JNIEXPORT void JNICALL Java_art_FramePop_enableFramePopEvent(
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.FramePop = framePopCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.FramePop = framePopCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
JvmtiErrorToException(env,
diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc
index 81d4cdc3ae..4434baf01c 100644
--- a/test/ti-agent/monitors_helper.cc
+++ b/test/ti-agent/monitors_helper.cc
@@ -182,13 +182,14 @@ extern "C" JNIEXPORT void JNICALL Java_art_Monitors_setupMonitorEvents(
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.MonitorContendedEnter = monitorEnterCB;
- cb.MonitorContendedEntered = monitorEnteredCB;
- cb.MonitorWait = monitorWaitCB;
- cb.MonitorWaited = monitorWaitedCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.MonitorContendedEnter = monitorEnterCB;
+ current_callbacks.MonitorContendedEntered = monitorEnteredCB;
+ current_callbacks.MonitorWait = monitorWaitCB;
+ current_callbacks.MonitorWaited = monitorWaitedCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
if (JvmtiErrorToException(env,
diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc
index 76371de20a..0e4b1bdd8c 100644
--- a/test/ti-agent/redefinition_helper.cc
+++ b/test/ti-agent/redefinition_helper.cc
@@ -381,10 +381,8 @@ static void SetupCommonRedefine() {
static void SetupCommonRetransform() {
SetStandardCapabilities(jvmti_env);
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
- jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb));
+ current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
+ jvmtiError res = jvmti_env->SetEventCallbacks(&current_callbacks, sizeof(current_callbacks));
CHECK_EQ(res, JVMTI_ERROR_NONE);
common_retransform::gTransformations.clear();
}
@@ -397,10 +395,8 @@ static void SetupCommonTransform() {
jvmti_env->AddCapabilities(&caps);
// Use the same callback as the retransform test.
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
- jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb));
+ current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
+ jvmtiError res = jvmti_env->SetEventCallbacks(&current_callbacks, sizeof(current_callbacks));
CHECK_EQ(res, JVMTI_ERROR_NONE);
common_retransform::gTransformations.clear();
}
diff --git a/test/ti-agent/test_env.cc b/test/ti-agent/test_env.cc
index cf47f22b03..49313876c9 100644
--- a/test/ti-agent/test_env.cc
+++ b/test/ti-agent/test_env.cc
@@ -19,6 +19,7 @@
namespace art {
jvmtiEnv* jvmti_env = nullptr;
+jvmtiEventCallbacks current_callbacks = {};
static bool gRuntimeIsJVM = false;
diff --git a/test/ti-agent/test_env.h b/test/ti-agent/test_env.h
index 2eb631c36c..a8a9f57097 100644
--- a/test/ti-agent/test_env.h
+++ b/test/ti-agent/test_env.h
@@ -23,6 +23,11 @@ namespace art {
extern jvmtiEnv* jvmti_env;
+// This is a jvmtiEventCallbacks struct that is used by all common ti-agent code whenever it calls
+// SetEventCallbacks. This can be used by single tests to add additional event callbacks without
+// being unable to use the rest of the ti-agent support code.
+extern jvmtiEventCallbacks current_callbacks;
+
bool IsJVM();
void SetJVM(bool b);
diff --git a/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc
index b590175d77..11e1c15757 100644
--- a/test/ti-agent/trace_helper.cc
+++ b/test/ti-agent/trace_helper.cc
@@ -513,17 +513,18 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing2(
return;
}
- jvmtiEventCallbacks cb;
- memset(&cb, 0, sizeof(cb));
- cb.MethodEntry = methodEntryCB;
- cb.MethodExit = methodExitCB;
- cb.FieldAccess = fieldAccessCB;
- cb.FieldModification = fieldModificationCB;
- cb.ClassPrepare = classPrepareCB;
- cb.SingleStep = singleStepCB;
- cb.ThreadStart = threadStartCB;
- cb.ThreadEnd = threadEndCB;
- if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ current_callbacks.MethodEntry = methodEntryCB;
+ current_callbacks.MethodExit = methodExitCB;
+ current_callbacks.FieldAccess = fieldAccessCB;
+ current_callbacks.FieldModification = fieldModificationCB;
+ current_callbacks.ClassPrepare = classPrepareCB;
+ current_callbacks.SingleStep = singleStepCB;
+ current_callbacks.ThreadStart = threadStartCB;
+ current_callbacks.ThreadEnd = threadEndCB;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventCallbacks(&current_callbacks,
+ sizeof(current_callbacks)))) {
return;
}
if (enter != nullptr &&