From 2b181dc3b0509c4b0b002197b9b13de57540f9c4 Mon Sep 17 00:00:00 2001 From: Dmitri Gabbasov Date: Sat, 17 Nov 2018 14:30:17 +0200 Subject: [PATCH] Make Java agent work with JDK 9+ Starting with JDK 9, class file transformers are not invoked for generated lambda classes anymore [1]. This commit makes Retrolambda patch JDK's InnerClassLambdaMetafactory instead, getting the bytes directly from its spinInnerClass method. This way, one can run Retrolambda with both Java 8 as well as Java 11 (input classes must still be compiled to Java 8 of course). [1] http://mail.openjdk.java.net/pipermail/core-libs-dev/2016-January/038199.html --- ...rg_apache_commons_commons_lang3_3_8_1.xml} | 8 +-- .../libraries/Maven__org_ow2_asm_asm_7_0.xml | 13 ++++ .../Maven__org_ow2_asm_asm_debug_all_5_2.xml | 13 ---- .../Maven__org_ow2_asm_asm_tree_7_0.xml | 13 ++++ end-to-end-tests/end-to-end-tests.iml | 3 +- end-to-end-tests/pom.xml | 8 ++- parent/pom.xml | 12 +++- retrolambda-maven-plugin/pom.xml | 2 +- .../retrolambda-maven-plugin.iml | 5 +- retrolambda/pom.xml | 8 ++- retrolambda/retrolambda.iml | 3 +- .../java/net/orfjackal/retrolambda/Agent.java | 36 +++++++++++ .../net/orfjackal/retrolambda/PreMain.java | 22 +++---- .../orfjackal/retrolambda/Retrolambda.java | 6 +- ...nnerClassLambdaMetafactoryTransformer.java | 61 +++++++++++++++++++ .../lambdas/LambdaClassSaverAgent.java | 34 ----------- 16 files changed, 172 insertions(+), 75 deletions(-) rename .idea/libraries/{Maven__org_apache_commons_commons_lang3_3_5.xml => Maven__org_apache_commons_commons_lang3_3_8_1.xml} (60%) create mode 100644 .idea/libraries/Maven__org_ow2_asm_asm_7_0.xml delete mode 100644 .idea/libraries/Maven__org_ow2_asm_asm_debug_all_5_2.xml create mode 100644 .idea/libraries/Maven__org_ow2_asm_asm_tree_7_0.xml create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/Agent.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/InnerClassLambdaMetafactoryTransformer.java delete mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/LambdaClassSaverAgent.java diff --git a/.idea/libraries/Maven__org_apache_commons_commons_lang3_3_5.xml b/.idea/libraries/Maven__org_apache_commons_commons_lang3_3_8_1.xml similarity index 60% rename from .idea/libraries/Maven__org_apache_commons_commons_lang3_3_5.xml rename to .idea/libraries/Maven__org_apache_commons_commons_lang3_3_8_1.xml index 666266cc..33b78e93 100644 --- a/.idea/libraries/Maven__org_apache_commons_commons_lang3_3_5.xml +++ b/.idea/libraries/Maven__org_apache_commons_commons_lang3_3_8_1.xml @@ -1,13 +1,13 @@ - + - + - + - + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_ow2_asm_asm_7_0.xml b/.idea/libraries/Maven__org_ow2_asm_asm_7_0.xml new file mode 100644 index 00000000..085e9d1a --- /dev/null +++ b/.idea/libraries/Maven__org_ow2_asm_asm_7_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_ow2_asm_asm_debug_all_5_2.xml b/.idea/libraries/Maven__org_ow2_asm_asm_debug_all_5_2.xml deleted file mode 100644 index db75e8b0..00000000 --- a/.idea/libraries/Maven__org_ow2_asm_asm_debug_all_5_2.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_ow2_asm_asm_tree_7_0.xml b/.idea/libraries/Maven__org_ow2_asm_asm_tree_7_0.xml new file mode 100644 index 00000000..03049fed --- /dev/null +++ b/.idea/libraries/Maven__org_ow2_asm_asm_tree_7_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/end-to-end-tests/end-to-end-tests.iml b/end-to-end-tests/end-to-end-tests.iml index 40dc4b05..a3d1fe13 100644 --- a/end-to-end-tests/end-to-end-tests.iml +++ b/end-to-end-tests/end-to-end-tests.iml @@ -14,7 +14,8 @@ - + + diff --git a/end-to-end-tests/pom.xml b/end-to-end-tests/pom.xml index 408f954a..f5d15539 100644 --- a/end-to-end-tests/pom.xml +++ b/end-to-end-tests/pom.xml @@ -30,7 +30,13 @@ org.ow2.asm - asm-debug-all + asm + test + + + + org.ow2.asm + asm-tree test diff --git a/parent/pom.xml b/parent/pom.xml index 185e026f..11470df9 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -85,8 +85,14 @@ org.ow2.asm - asm-debug-all - 5.2 + asm + 7.0 + + + + org.ow2.asm + asm-tree + 7.0 @@ -312,7 +318,7 @@ maven-plugin-plugin - 3.4 + 3.6.0 diff --git a/retrolambda-maven-plugin/pom.xml b/retrolambda-maven-plugin/pom.xml index a3bb1f7d..e1714108 100644 --- a/retrolambda-maven-plugin/pom.xml +++ b/retrolambda-maven-plugin/pom.xml @@ -61,7 +61,7 @@ org.apache.commons commons-lang3 - 3.5 + 3.8.1 diff --git a/retrolambda-maven-plugin/retrolambda-maven-plugin.iml b/retrolambda-maven-plugin/retrolambda-maven-plugin.iml index 0ec9480c..7ef880fb 100644 --- a/retrolambda-maven-plugin/retrolambda-maven-plugin.iml +++ b/retrolambda-maven-plugin/retrolambda-maven-plugin.iml @@ -13,7 +13,8 @@ - + + @@ -41,7 +42,7 @@ - + diff --git a/retrolambda/pom.xml b/retrolambda/pom.xml index 38a0984f..e44b5fff 100644 --- a/retrolambda/pom.xml +++ b/retrolambda/pom.xml @@ -23,7 +23,12 @@ org.ow2.asm - asm-debug-all + asm + + + + org.ow2.asm + asm-tree @@ -50,6 +55,7 @@ net.orfjackal.retrolambda.Main + true net.orfjackal.retrolambda.PreMain diff --git a/retrolambda/retrolambda.iml b/retrolambda/retrolambda.iml index f58fb626..39d07de4 100644 --- a/retrolambda/retrolambda.iml +++ b/retrolambda/retrolambda.iml @@ -11,7 +11,8 @@ - + + diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Agent.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Agent.java new file mode 100644 index 00000000..b963b20a --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Agent.java @@ -0,0 +1,36 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// This software is released under the Apache License 2.0. +// The license text is at http://www.apache.org/licenses/LICENSE-2.0 + +package net.orfjackal.retrolambda; + +import net.orfjackal.retrolambda.ext.ow2asm.EnhancedClassReader; +import net.orfjackal.retrolambda.lambdas.LambdaClassSaver; +import org.objectweb.asm.ClassReader; + +public class Agent { + + private static boolean enabled = false; + private static LambdaClassSaver lambdaClassSaver; + private static boolean isJavacHacksEnabled; + + public static void enable() { + enabled = true; + } + + public static boolean isEnabled() { + return enabled; + } + + public static void setLambdaClassSaver(LambdaClassSaver lambdaClassSaver, boolean isJavacHacksEnabled) { + Agent.lambdaClassSaver = lambdaClassSaver; + Agent.isJavacHacksEnabled = isJavacHacksEnabled; + } + + public static void saveLambda(byte[] bytes) { + if (lambdaClassSaver != null) { + ClassReader reader = EnhancedClassReader.create(bytes, isJavacHacksEnabled); + lambdaClassSaver.saveIfLambda(reader.getClassName(), bytes); + } + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/PreMain.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/PreMain.java index bd8d228e..62adff9a 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/PreMain.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/PreMain.java @@ -6,23 +6,23 @@ import net.orfjackal.retrolambda.lambdas.*; +import java.io.File; import java.lang.instrument.Instrumentation; +import java.net.URISyntaxException; +import java.util.jar.JarFile; public class PreMain { - private static final LambdaClassSaverAgent agent = new LambdaClassSaverAgent(); - private static boolean agentLoaded = false; + public static void premain(String agentArgs, Instrumentation inst) throws Exception { + // Append the agent JAR to the bootstrap search path so that the instrumented InnerClassLambdaMetaFactory + // could refer to Agent. + inst.appendToBootstrapClassLoaderSearch(new JarFile(getAgentJarFile())); - public static void premain(String agentArgs, Instrumentation inst) { - inst.addTransformer(agent); - agentLoaded = true; + inst.addTransformer(new InnerClassLambdaMetafactoryTransformer(), true); + inst.retransformClasses(Class.forName("java.lang.invoke.InnerClassLambdaMetafactory")); } - public static boolean isAgentLoaded() { - return agentLoaded; - } - - public static void setLambdaClassSaver(LambdaClassSaver lambdaClassSaver, boolean isJavacHacksEnabled) { - agent.setLambdaClassSaver(lambdaClassSaver, isJavacHacksEnabled); + private static File getAgentJarFile() throws URISyntaxException { + return new File(PreMain.class.getProtectionDomain().getCodeSource().getLocation().toURI()); } } diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java index d6d49579..07d292f0 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java @@ -45,7 +45,7 @@ public static void run(Config config) throws Throwable { Log.info("Classpath: " + classpath); Log.info("Included files: " + (includedFiles != null ? includedFiles.size() : "all")); Log.info("JVM version: " + System.getProperty("java.version")); - Log.info("Agent enabled: " + PreMain.isAgentLoaded()); + Log.info("Agent enabled: " + Agent.isEnabled()); Log.info("javac hacks: " + isJavacHacksEnabled); if (!Files.isDirectory(inputDir)) { @@ -61,8 +61,8 @@ public static void run(Config config) throws Throwable { LambdaClassSaver lambdaClassSaver = new LambdaClassSaver(outputDirectory, transformers, isJavacHacksEnabled); try (LambdaClassDumper dumper = new LambdaClassDumper(lambdaClassSaver)) { - if (PreMain.isAgentLoaded()) { - PreMain.setLambdaClassSaver(lambdaClassSaver, isJavacHacksEnabled); + if (Agent.isEnabled()) { + Agent.setLambdaClassSaver(lambdaClassSaver, isJavacHacksEnabled); } else { dumper.install(); } diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/InnerClassLambdaMetafactoryTransformer.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/InnerClassLambdaMetafactoryTransformer.java new file mode 100644 index 00000000..2697b4c8 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/InnerClassLambdaMetafactoryTransformer.java @@ -0,0 +1,61 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// This software is released under the Apache License 2.0. +// The license text is at http://www.apache.org/licenses/LICENSE-2.0 + +package net.orfjackal.retrolambda.lambdas; + +import com.esotericsoftware.minlog.Log; +import net.orfjackal.retrolambda.Agent; +import org.objectweb.asm.*; + +import java.lang.instrument.*; +import java.security.ProtectionDomain; + +public class InnerClassLambdaMetafactoryTransformer implements ClassFileTransformer { + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException { + if (!"java/lang/invoke/InnerClassLambdaMetafactory".equals(className)) { + return null; + } + + try { + byte[] transformed = transformMetafactory(bytes); + Agent.enable(); + return transformed; + } catch (Throwable e) { + Log.error("Failed to transform " + className, e); + return null; + } + } + + private byte[] transformMetafactory(byte[] bytes) { + ClassReader cr = new ClassReader(bytes); + ClassWriter cw = new ClassWriter(cr, 0); + ClassVisitor cv = new ClassVisitor(Opcodes.ASM7, cw) { + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + if (name.equals("spinInnerClass")) { + mv = new MethodVisitor(Opcodes.ASM7, mv) { + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + if (name.equals("toByteArray")) { + mv.visitInsn(Opcodes.DUP); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Agent.class), "saveLambda", "([B)V", false); + } + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + super.visitMaxs(maxStack + 1, maxLocals); + } + }; + } + return mv; + } + }; + cr.accept(cv, 0); + return cw.toByteArray(); + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/LambdaClassSaverAgent.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/LambdaClassSaverAgent.java deleted file mode 100644 index d08a3d1c..00000000 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/LambdaClassSaverAgent.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors -// This software is released under the Apache License 2.0. -// The license text is at http://www.apache.org/licenses/LICENSE-2.0 - -package net.orfjackal.retrolambda.lambdas; - -import net.orfjackal.retrolambda.ext.ow2asm.EnhancedClassReader; - -import java.lang.instrument.*; -import java.security.ProtectionDomain; - -public class LambdaClassSaverAgent implements ClassFileTransformer { - - private LambdaClassSaver lambdaClassSaver; - private boolean isJavacHacksEnabled; - - public void setLambdaClassSaver(LambdaClassSaver lambdaClassSaver, boolean isJavacHacksEnabled) { - this.lambdaClassSaver = lambdaClassSaver; - this.isJavacHacksEnabled = isJavacHacksEnabled; - } - - @Override - public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { - if (className == null) { - // Since JDK 8 build b121 or so, lambda classes have a null class name, - // but we can read it from the bytecode where the name still exists. - className = EnhancedClassReader.create(classfileBuffer, isJavacHacksEnabled).getClassName(); - } - if (lambdaClassSaver != null) { - lambdaClassSaver.saveIfLambda(className, classfileBuffer); - } - return null; - } -}