From 71717ae94b4c64e1b14875cfc25714c0db299c65 Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Tue, 14 Apr 2026 17:27:42 +0100 Subject: [PATCH 1/3] test agent retransformation in AOT mode --- .../cds/appcds/aotCache/JavaAgent2.java | 142 ++++++++++++++++++ .../aotCache/JavaAgentRetransformer.java | 109 ++++++++++++++ .../appcds/aotCache/JavaAgentRetransformer.mf | 5 + 3 files changed, 256 insertions(+) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.mf diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java new file mode 100644 index 0000000000000..9766311a993e5 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2025, 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. + * + */ + + +/** + * @test id=aot + * @summary -javaagent should be allowed in AOT workflow. However, + * classes transformed/redefined by agents will not be cached. + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build JavaAgent2 JavaAgentRetransformer Util + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar JavaAgentApp2 JavaAgentApp2$ShouldBeTransformed + * @run driver JavaAgent2 AOT + */ + +import java.util.random.RandomGenerator; + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.helpers.ClassFileInstaller; + +public class JavaAgent2 { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "JavaAgentApp2"; + + public static String agentClasses[] = { + "JavaAgentRetransformer", + "Util", + }; + static String agentJar; + + public static void main(String... args) throws Exception { + agentJar = ClassFileInstaller.writeJar("agent.jar", + ClassFileInstaller.Manifest.fromSourceFile("JavaAgentRetransformer.mf"), + agentClasses); + + Tester t = new Tester(); + t.run(args); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-javaagent:" + agentJar, + "-Xlog:aot,cds", + "-XX:+AOTClassLinking", + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + checkExecutionForAOTWorkflow(out, runMode); + } + + static String agentLoadedMsg = "JavaAgentRetransformer.premain() is called"; + static String agentPremainFinished = "JavaAgentRetransformer::premain() is finished"; + + public void checkExecutionForAOTWorkflow(OutputAnalyzer out, RunMode runMode) throws Exception { + + if (runMode.isApplicationExecuted()) { + out.shouldContain(agentLoadedMsg); + out.shouldContain("Transforming: JavaAgentApp2$ShouldBeTransformed"); + out.shouldContain(agentPremainFinished); + } else { + out.shouldNotContain(agentLoadedMsg); + out.shouldNotContain(agentPremainFinished); + } + + switch (runMode) { + case RunMode.TRAINING: + out.shouldContain("Skipping JavaAgentApp2$ShouldBeTransformed: From ClassFileLoadHook"); + out.shouldContain("Skipping JavaAgentApp2$ShouldBeTransformed: Has been redefined"); + out.shouldContain("Skipping JavaAgentRetransformer: Unsupported location"); + break; + case RunMode.ASSEMBLY: + out.shouldContain("Disabled all JVMTI agents during -XX:AOTMode=create"); + break; + } + + } + } +} + +class JavaAgentApp2 { + public static void main(String[] args) { + ShouldBeTransformed s = new ShouldBeTransformed(); + for (int i = 0; i < 10_000; i++) { + s.doWork(); + } + JavaAgentRetransformer.doRetransform(ShouldBeTransformed.class); + for (int i = 0; i < 10_000; i++) { + s.doWork(); + } + } + + static class ShouldBeTransformed { + private RandomGenerator random = RandomGenerator.of("L64X128MixRandom"); + private int value = 0; + public void doWork() { + value = (value * (random.nextInt(5) + 1) + random.nextInt(7)); + } + public int getValue() { return value; } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java new file mode 100644 index 0000000000000..1296b9e4e0815 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2025, 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. + * + */ + +import java.lang.classfile.ClassFile; +import java.lang.classfile.MethodModel; +import java.lang.classfile.ClassTransform; +import java.lang.constant.ConstantDescs; + +import java.lang.System.Logger.Level; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.AccessFlag; +import java.security.ProtectionDomain; + +// This class is available on the classpath so it can be accessed by JavaAgentRetransformApp +public class JavaAgentRetransformer implements ClassFileTransformer { + private static Instrumentation savedInstrumentation; + private static final System.Logger LOGGER = System.getLogger(JavaAgentRetransformer.class.getName()); + private static Class current = null; + public static void premain(String agentArguments, Instrumentation instrumentation) { + System.out.println("JavaAgentRetransformer.premain() is called"); + instrumentation.addTransformer(new JavaAgentRetransformer(), /*canRetransform=*/true); + savedInstrumentation = instrumentation; + + LOGGER.log(Level.WARNING, "JavaAgentRetransformer::premain() is finished"); + } + + public static Instrumentation getInstrumentation() { + return savedInstrumentation; + } + + public static void agentmain(String args, Instrumentation inst) throws Exception { + premain(args, inst); + } + + public static void doRetransform(Class clazz) { + current = clazz; + Class classes[] = new Class[1]; + classes[0] = clazz; + try { + savedInstrumentation.retransformClasses(classes); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public byte[] transform(ClassLoader loader, String name, Class classBeingRedefined, + ProtectionDomain pd, byte[] buffer) throws IllegalClassFormatException { + // only accept retransform requests for the current class + if (classBeingRedefined == null) { + return null; + } + if (classBeingRedefined == current) { + System.out.println("Transforming: " + name); + try { + buffer = transformDoWork(buffer); + } catch (Throwable t) { + t.printStackTrace(); + } + Thread.dumpStack(); + return buffer; + } + return null; + } + + // replace the body of any public void instance method called + // doWork with a simple return + static byte[] transformDoWork(byte[] buffer) { + ClassTransform t = (builder, element) -> { + if (element instanceof MethodModel method && + method.methodName().equalsString("doWork") && + method.methodTypeSymbol() == ConstantDescs.MTD_void && + method.flags().has(AccessFlag.PUBLIC) && + !method.flags().has(AccessFlag.STATIC)) { + builder.withMethodBody("doWork", + ConstantDescs.MTD_void, + ClassFile.ACC_PUBLIC, + cob -> cob.return_()); + } else { + builder.with(element); // leaves the element in place + } + }; + var c = ClassFile.of(); + byte[] newBytes = c.transformClass(c.parse(buffer), t); + return newBytes; + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.mf b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.mf new file mode 100644 index 0000000000000..35629315ba44a --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +Premain-Class: JavaAgentRetransformer +Agent-Class: JavaAgentRetransformer +Can-Retransform-Classes: true +Can-Redefine-Classes: true From 9651abf675941d100247133b9e9eb624ed09573e Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Thu, 16 Apr 2026 11:26:04 +0100 Subject: [PATCH 2/3] ensure code from transformed method is inlined into code of untransformed class --- .../cds/appcds/aotCache/JavaAgent2.java | 45 ++++++++++++++----- .../aotCache/JavaAgentRetransformer.java | 15 ++++--- .../cds/appcds/aotCache/TEST.properties | 1 + 3 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/TEST.properties diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java index 9766311a993e5..47cf0d69fc46d 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java @@ -30,11 +30,13 @@ * @requires vm.cds.supports.aot.class.linking * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build JavaAgent2 JavaAgentRetransformer Util - * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar JavaAgentApp2 JavaAgentApp2$ShouldBeTransformed + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar JavaAgentApp2 JavaAgentApp2$ShouldNotBeTransformed JavaAgentApp2$ShouldBeTransformed * @run driver JavaAgent2 AOT */ import java.util.random.RandomGenerator; +import java.util.LinkedList; +import java.util.Arrays; import jdk.test.lib.cds.CDSAppTester; import jdk.test.lib.process.OutputAnalyzer; @@ -71,11 +73,24 @@ public String classpath(RunMode runMode) { @Override public String[] vmArgs(RunMode runMode) { - return new String[] { - "-javaagent:" + agentJar, - "-Xlog:aot,cds", - "-XX:+AOTClassLinking", - }; + switch (runMode) { + case RunMode.TRAINING: + return new String[] { + "-javaagent:" + agentJar, + "-Xlog:aot,cds", + "-XX:+AOTClassLinking", + // "-XX:CompileCommand=dontinline ShouldNotBeTransformed::doWork", + "-XX:+PrintCompilation", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+PrintInlining", + }; + default: + return new String[] { + "-javaagent:" + agentJar, + "-Xlog:aot,cds", + "-XX:+AOTClassLinking", + }; + } } @Override @@ -109,6 +124,8 @@ public void checkExecutionForAOTWorkflow(OutputAnalyzer out, RunMode runMode) th out.shouldContain("Skipping JavaAgentApp2$ShouldBeTransformed: From ClassFileLoadHook"); out.shouldContain("Skipping JavaAgentApp2$ShouldBeTransformed: Has been redefined"); out.shouldContain("Skipping JavaAgentRetransformer: Unsupported location"); + out.shouldMatch("[0-9]* *[0-9]* *2 *JavaAgentApp2\\$ShouldNotBeTransformed::doWork"); + out.shouldMatch("JavaAgentApp2\\$ShouldBeTransformed::doWork \\([0-9]* bytes\\) *inline"); break; case RunMode.ASSEMBLY: out.shouldContain("Disabled all JVMTI agents during -XX:AOTMode=create"); @@ -121,22 +138,30 @@ public void checkExecutionForAOTWorkflow(OutputAnalyzer out, RunMode runMode) th class JavaAgentApp2 { public static void main(String[] args) { - ShouldBeTransformed s = new ShouldBeTransformed(); + ShouldNotBeTransformed s = new ShouldNotBeTransformed(); for (int i = 0; i < 10_000; i++) { s.doWork(); } + // transform the class whose method shoudl be inlined JavaAgentRetransformer.doRetransform(ShouldBeTransformed.class); for (int i = 0; i < 10_000; i++) { s.doWork(); } } - static class ShouldBeTransformed { - private RandomGenerator random = RandomGenerator.of("L64X128MixRandom"); + static class ShouldNotBeTransformed { + private ShouldBeTransformed r = new ShouldBeTransformed(); private int value = 0; public void doWork() { - value = (value * (random.nextInt(5) + 1) + random.nextInt(7)); + value = r.doWork(value); } public int getValue() { return value; } } + + static class ShouldBeTransformed { + private RandomGenerator random = RandomGenerator.of("L64X128MixRandom"); + public int doWork(int value) { + return (value * (random.nextInt(5) + 1) + random.nextInt(7)); + } + } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java index 1296b9e4e0815..9fb45d8dde379 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentRetransformer.java @@ -26,6 +26,8 @@ import java.lang.classfile.MethodModel; import java.lang.classfile.ClassTransform; import java.lang.constant.ConstantDescs; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; import java.lang.System.Logger.Level; import java.lang.instrument.ClassFileTransformer; @@ -85,19 +87,22 @@ public byte[] transform(ClassLoader loader, String name, Class classBeingRede return null; } - // replace the body of any public void instance method called - // doWork with a simple return + final static ClassDesc CD_int = ClassDesc.ofDescriptor("I"); + final static MethodTypeDesc MTD_int_int = MethodTypeDesc.of(CD_int, CD_int); + + // replace the body of any public void instance method that looks + // like "int doWork(int arg0)" with "return arg0" static byte[] transformDoWork(byte[] buffer) { ClassTransform t = (builder, element) -> { if (element instanceof MethodModel method && method.methodName().equalsString("doWork") && - method.methodTypeSymbol() == ConstantDescs.MTD_void && + method.methodTypeSymbol() == MTD_int_int && method.flags().has(AccessFlag.PUBLIC) && !method.flags().has(AccessFlag.STATIC)) { builder.withMethodBody("doWork", - ConstantDescs.MTD_void, + MTD_int_int, ClassFile.ACC_PUBLIC, - cob -> cob.return_()); + cob -> cob.iload(0).ireturn()); } else { builder.with(element); // leaves the element in place } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/TEST.properties b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/TEST.properties new file mode 100644 index 0000000000000..5574838678fc0 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/TEST.properties @@ -0,0 +1 @@ +maxOutputSize = 1000000 \ No newline at end of file From 8c03f787818ead3977a6b5d4e67d5181394eeeca Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Thu, 16 Apr 2026 14:37:08 +0100 Subject: [PATCH 3/3] detect compiled training data for caller of transformed inline method --- .../cds/appcds/aotCache/JavaAgent2.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java index 47cf0d69fc46d..9f4bae34395b7 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent2.java @@ -80,10 +80,18 @@ public String[] vmArgs(RunMode runMode) { "-Xlog:aot,cds", "-XX:+AOTClassLinking", // "-XX:CompileCommand=dontinline ShouldNotBeTransformed::doWork", + "-XX:-TieredCompilation", "-XX:+PrintCompilation", "-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining", }; + case RunMode.ASSEMBLY: + return new String[] { + "-javaagent:" + agentJar, + "-Xlog:aot,cds", + "-XX:+AOTClassLinking", + "-XX:+AOTPrintTrainingInfo", + }; default: return new String[] { "-javaagent:" + agentJar, @@ -124,11 +132,17 @@ public void checkExecutionForAOTWorkflow(OutputAnalyzer out, RunMode runMode) th out.shouldContain("Skipping JavaAgentApp2$ShouldBeTransformed: From ClassFileLoadHook"); out.shouldContain("Skipping JavaAgentApp2$ShouldBeTransformed: Has been redefined"); out.shouldContain("Skipping JavaAgentRetransformer: Unsupported location"); - out.shouldMatch("[0-9]* *[0-9]* *2 *JavaAgentApp2\\$ShouldNotBeTransformed::doWork"); + // should see compilation of $ShouldNotBeTransformed::doWork + out.shouldMatch("^[0-9]* *[0-9]* *[0-9] *JavaAgentApp2\\$ShouldNotBeTransformed::doWork"); + // should see inlining of $ShouldBeTransformed::doWork out.shouldMatch("JavaAgentApp2\\$ShouldBeTransformed::doWork \\([0-9]* bytes\\) *inline"); break; case RunMode.ASSEMBLY: out.shouldContain("Disabled all JVMTI agents during -XX:AOTMode=create"); + // we should not be saving compiled training data for + // $ShouldBeTransformed.doWork as it depends on a + // method that was retransfromed + out.shouldNotMatch("^ C JavaAgentApp2\\$ShouldNotBeTransformed\\[.\\].doWork\\(\\)"); break; } @@ -139,11 +153,14 @@ public void checkExecutionForAOTWorkflow(OutputAnalyzer out, RunMode runMode) th class JavaAgentApp2 { public static void main(String[] args) { ShouldNotBeTransformed s = new ShouldNotBeTransformed(); + // Force profile/compile of ShouldNotBeTransformed::dowork. + // The compiler should inline ShouldBeTransformed::doWork for (int i = 0; i < 10_000; i++) { s.doWork(); } - // transform the class whose method shoudl be inlined + // transform ShouldBeTransformed::doWork to a simple return JavaAgentRetransformer.doRetransform(ShouldBeTransformed.class); + // Profile and compile again for (int i = 0; i < 10_000; i++) { s.doWork(); }