diff --git a/test/jdk/java/lang/instrument/NativeMethodPrefixAgent.java b/test/jdk/java/lang/instrument/NativeMethodPrefixAgent.java index 5ddede39959..5107f122ba3 100644 --- a/test/jdk/java/lang/instrument/NativeMethodPrefixAgent.java +++ b/test/jdk/java/lang/instrument/NativeMethodPrefixAgent.java @@ -35,70 +35,69 @@ class NativeMethodPrefixAgent { static ClassFileTransformer t0, t1, t2; static Instrumentation inst; - private static Throwable agentError; + private static Throwable agentError; // to be accessed/updated in a synchronized block - public static void checkErrors() { + private static final String CLASS_TO_TRANSFORM = "NativeMethodPrefixApp$Dummy"; + + public static synchronized void checkErrors() { if (agentError != null) { throw new RuntimeException("Agent error", agentError); } } + private static synchronized void trackError(final Throwable t) { + if (agentError == null) { + agentError = t; + return; + } + if (agentError != t) { + agentError.addSuppressed(t); + } + } + static class Tr implements ClassFileTransformer { private static final ClassDesc CD_StringIdCallbackReporter = ClassDesc.ofInternalName("bootreporter/StringIdCallbackReporter"); private static final MethodTypeDesc MTD_void_String_int = MethodTypeDesc.of(CD_void, CD_String, CD_int); final String trname; final int transformId; + private final String nativeMethodPrefix; Tr(int transformId) { this.trname = "tr" + transformId; this.transformId = transformId; + this.nativeMethodPrefix = "wrapped_" + trname + "_"; } - public byte[] - transform( - ClassLoader loader, - String className, - Class classBeingRedefined, - ProtectionDomain protectionDomain, - byte[] classfileBuffer) { - boolean redef = classBeingRedefined != null; - System.out.println(trname + ": " + - (redef? "Retransforming " : "Loading ") + className); - if (className != null) { - try { - byte[] newcf = Instrumentor.instrFor(classfileBuffer) - .addNativeMethodTrackingInjection( - "wrapped_" + trname + "_", (name, h) -> { - h.loadConstant(name); - h.loadConstant(transformId); - h.invokestatic( - CD_StringIdCallbackReporter, - "tracker", - MTD_void_String_int); - }) - .apply(); - /*** debugging ... - if (newcf != null) { - String fname = trname + (redef?"_redef" : "") + "/" + className; - System.err.println("dumping to: " + fname); - write_buffer(fname + "_before.class", classfileBuffer); - write_buffer(fname + "_instr.class", newcf); - } - ***/ - - return redef? null : newcf; - } catch (Throwable ex) { - if (agentError == null) { - agentError = ex; - } - System.err.println("ERROR: Injection failure: " + ex); - ex.printStackTrace(); + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) { + + try { + // we only transform a specific application class + if (!className.equals(CLASS_TO_TRANSFORM)) { + return null; + } + if (classBeingRedefined != null) { return null; } + // use a byte code generator which creates wrapper methods, + // with a configured native method prefix, for each native method on the + // class being transformed + final Instrumentor byteCodeGenerator = Instrumentor.instrFor(classfileBuffer) + .addNativeMethodTrackingInjection(nativeMethodPrefix, + (name, cb) -> { + cb.loadConstant(name); + cb.loadConstant(transformId); + cb.invokestatic(CD_StringIdCallbackReporter, + "tracker", MTD_void_String_int); + }); + // generate the bytecode + return byteCodeGenerator.apply(); + } catch (Throwable t) { + trackError(t); + return null; } - return null; } - } // for debugging diff --git a/test/jdk/java/lang/instrument/NativeMethodPrefixApp.java b/test/jdk/java/lang/instrument/NativeMethodPrefixApp.java index 5060e67ca87..97cf4fde2f1 100644 --- a/test/jdk/java/lang/instrument/NativeMethodPrefixApp.java +++ b/test/jdk/java/lang/instrument/NativeMethodPrefixApp.java @@ -22,9 +22,7 @@ */ -import java.io.File; import java.nio.file.Path; -import java.lang.management.*; import bootreporter.*; import jdk.test.lib.helpers.ClassFileInstaller; @@ -33,27 +31,24 @@ /* * @test - * @bug 6263319 + * @bug 6263319 8334167 * @summary test setNativeMethodPrefix * @requires ((vm.opt.StartFlightRecording == null) | (vm.opt.StartFlightRecording == false)) & ((vm.opt.FlightRecorder == null) | (vm.opt.FlightRecorder == false)) - * @modules java.management - * java.instrument + * @modules java.instrument * @library /test/lib * @build bootreporter.StringIdCallback bootreporter.StringIdCallbackReporter * asmlib.Instrumentor NativeMethodPrefixAgent * @enablePreview * @comment The test uses asmlib/Instrumentor.java which relies on ClassFile API PreviewFeature. - * @run driver/timeout=240 NativeMethodPrefixApp roleDriver - * @comment The test uses a higher timeout to prevent test timeouts noted in JDK-6528548 + * @run main/native NativeMethodPrefixApp roleDriver */ public class NativeMethodPrefixApp implements StringIdCallback { - // This test is fragile like a golden file test. - // It assumes that a specific non-native library method will call a specific - // native method. The below may need to be updated based on library changes. - static String goldenNativeMethodName = "getStartupTime"; - + // we expect this native method, which is part of this test's application, + // to be instrumented and invoked + static String goldenNativeMethodName = "fooBarNativeMethod"; static boolean[] gotIt = {false, false, false}; + private static final String testLibraryPath = System.getProperty("test.nativepath"); public static void main(String[] args) throws Exception { if (args.length == 1) { @@ -68,21 +63,19 @@ public static void main(String[] args) throws Exception { launchApp(agentJar); } else { System.err.println("running app"); + System.loadLibrary("NativeMethodPrefix"); // load the native library new NativeMethodPrefixApp().run(); } } private static Path createAgentJar() throws Exception { - final String testClassesDir = System.getProperty("test.classes"); final Path agentJar = Path.of("NativeMethodPrefixAgent.jar"); final String manifest = """ Manifest-Version: 1.0 Premain-Class: NativeMethodPrefixAgent Can-Retransform-Classes: true Can-Set-Native-Method-Prefix: true - """ - + "Boot-Class-Path: " + testClassesDir.replace(File.separatorChar, '/') + "/" - + "\n"; + """; System.out.println("Manifest is:\n" + manifest); // create the agent jar ClassFileInstaller.writeJar(agentJar.getFileName().toString(), @@ -96,6 +89,7 @@ private static void launchApp(final Path agentJar) throws Exception { final OutputAnalyzer oa = ProcessTools.executeTestJava( "--enable-preview", // due to usage of ClassFile API PreviewFeature in the agent "-javaagent:" + agentJar.toString(), + "-Djava.library.path=" + testLibraryPath, NativeMethodPrefixApp.class.getName()); oa.shouldHaveExitValue(0); // make available stdout/stderr in the logs, even in case of successful completion @@ -105,10 +99,11 @@ private static void launchApp(final Path agentJar) throws Exception { private void run() throws Exception { StringIdCallbackReporter.registerCallback(this); System.err.println("start"); - - java.lang.reflect.Array.getLength(new short[5]); - RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean(); - System.err.println(mxbean.getVmVendor()); + final long val = new Dummy().callSomeNativeMethod(); + if (val != 42) { + throw new RuntimeException("unexpected return value " + val + + " from native method, expected 42"); + } NativeMethodPrefixAgent.checkErrors(); @@ -128,4 +123,13 @@ public void tracker(String name, int id) { System.err.println("Tracked #" + id + ": " + name); } } + + private static class Dummy { + + private long callSomeNativeMethod() { + return fooBarNativeMethod(); + } + + private native long fooBarNativeMethod(); + } } diff --git a/test/jdk/java/lang/instrument/libNativeMethodPrefix.c b/test/jdk/java/lang/instrument/libNativeMethodPrefix.c new file mode 100644 index 00000000000..39dd7edbf23 --- /dev/null +++ b/test/jdk/java/lang/instrument/libNativeMethodPrefix.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jni.h" + +JNIEXPORT jlong JNICALL +Java_NativeMethodPrefixApp_00024Dummy_fooBarNativeMethod(JNIEnv *env, jclass clazz) +{ + fprintf(stderr, "native method called\n"); + return 42; +} + +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *vm, void *reserved) +{ + fprintf(stderr, "native library loaded\n"); + return JNI_VERSION_1_1; // this native library needs the very basic JNI support +}