diff options
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( + ¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_callbacks, + sizeof(current_callbacks)))) { return; } if (enter != nullptr && |