diff options
7 files changed, 296 insertions, 73 deletions
diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipseASTConverterTransformer.java b/src_eclipseagent/lombok/eclipse/agent/EclipseASTConverterTransformer.java index 1402138b..4245e246 100644 --- a/src_eclipseagent/lombok/eclipse/agent/EclipseASTConverterTransformer.java +++ b/src_eclipseagent/lombok/eclipse/agent/EclipseASTConverterTransformer.java @@ -21,6 +21,7 @@ */ package lombok.eclipse.agent; +import org.mangosdk.spi.ProviderFor; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -41,8 +42,9 @@ import org.objectweb.asm.Opcodes; * <li>The <code>retrieveStartingCatchPosition(int, int)</code> method is instrumented to return its first parameter * instead of the constant -1.</li></ul> */ -class EclipseASTConverterTransformer { - byte[] transform(byte[] classfileBuffer) { +@ProviderFor(EclipseTransformer.class) +public class EclipseASTConverterTransformer implements EclipseTransformer { + public byte[] transform(byte[] classfileBuffer) { ClassReader reader = new ClassReader(classfileBuffer); ClassWriter writer = new ClassWriter(reader, 0); @@ -51,17 +53,6 @@ class EclipseASTConverterTransformer { return writer.toByteArray(); } - static RuntimeException sneakyThrow(Throwable t) { - if ( t == null ) throw new NullPointerException("t"); - EclipseASTConverterTransformer.<RuntimeException>sneakyThrow0(t); - return null; - } - - @SuppressWarnings("unchecked") - private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { - throw (T)t; - } - private static class ASTConverterPatcherAdapter extends ClassAdapter { public ASTConverterPatcherAdapter(ClassVisitor cv) { super(cv); @@ -97,4 +88,8 @@ class EclipseASTConverterTransformer { } } } + + @Override public String getTargetClassName() { + return "org/eclipse/jdt/core/dom/ASTConverter"; + } } diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipseASTNodeTransformer.java b/src_eclipseagent/lombok/eclipse/agent/EclipseASTNodeTransformer.java new file mode 100644 index 00000000..e27aa08c --- /dev/null +++ b/src_eclipseagent/lombok/eclipse/agent/EclipseASTNodeTransformer.java @@ -0,0 +1,68 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.agent; + +import org.mangosdk.spi.ProviderFor; +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Transforms Eclipse's {@code org.eclipse.jdt.internal.compiler.ast.ASTNode} class, + * which is the super-class of all AST Node class for Eclipse. + * + * Transformations applied:<ul> + * <li>A field is added: 'public transient ASTNode $generatedBy = null;'. It is set to something other than {@code null} if + * this node is generated; the reference then points at the node that is responsible for its generation (example: a {@code @Data} annotation).</li></ul> + */ +@ProviderFor(EclipseTransformer.class) +public class EclipseASTNodeTransformer implements EclipseTransformer { + private static final String ASTNODE = "org/eclipse/jdt/internal/compiler/ast/ASTNode"; + + public byte[] transform(byte[] classfileBuffer) { + ClassReader reader = new ClassReader(classfileBuffer); + ClassWriter writer = new ClassWriter(reader, 0); + + ClassAdapter adapter = new ASTNodePatcherAdapter(writer); + reader.accept(adapter, 0); + return writer.toByteArray(); + } + + private static class ASTNodePatcherAdapter extends ClassAdapter { + ASTNodePatcherAdapter(ClassVisitor cv) { + super(cv); + } + + @Override public void visitEnd() { + FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_TRANSIENT, "$generatedBy", "L" + ASTNODE + ";", null, null); + fv.visitEnd(); + cv.visitEnd(); + } + } + + @Override public String getTargetClassName() { + return ASTNODE; + } +} diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipseCUDTransformer.java b/src_eclipseagent/lombok/eclipse/agent/EclipseCUDTransformer.java index 95689773..b18a1ff1 100644 --- a/src_eclipseagent/lombok/eclipse/agent/EclipseCUDTransformer.java +++ b/src_eclipseagent/lombok/eclipse/agent/EclipseCUDTransformer.java @@ -21,6 +21,7 @@ */ package lombok.eclipse.agent; +import org.mangosdk.spi.ProviderFor; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -29,15 +30,16 @@ import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Opcodes; /** - * Transforms Eclipse's <code>org.eclipse.jdt.internal.compiler.ast CompilationUnitDeclaration</code> class, + * Transforms Eclipse's <code>org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration</code> class, * which is the top-level AST Node class for Eclipse. * * Transformations applied:<ul> * <li>A field is added: 'public transient Object $lombokAST = null;'. We use it to cache our own AST object, * usually of type <code>lombok.eclipse.EclipseAST.</code></li></ul> */ -class EclipseCUDTransformer { - byte[] transform(byte[] classfileBuffer) { +@ProviderFor(EclipseTransformer.class) +public class EclipseCUDTransformer implements EclipseTransformer { + public byte[] transform(byte[] classfileBuffer) { ClassReader reader = new ClassReader(classfileBuffer); ClassWriter writer = new ClassWriter(reader, 0); @@ -57,4 +59,8 @@ class EclipseCUDTransformer { cv.visitEnd(); } } + + @Override public String getTargetClassName() { + return "org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration"; + } } diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipseLinkedNodeFinderTransformer.java b/src_eclipseagent/lombok/eclipse/agent/EclipseLinkedNodeFinderTransformer.java new file mode 100644 index 00000000..9a1ebbb2 --- /dev/null +++ b/src_eclipseagent/lombok/eclipse/agent/EclipseLinkedNodeFinderTransformer.java @@ -0,0 +1,169 @@ +package lombok.eclipse.agent; + +import org.mangosdk.spi.ProviderFor; +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import static org.objectweb.asm.Opcodes.*; + +@ProviderFor(EclipseTransformer.class) +public class EclipseLinkedNodeFinderTransformer implements EclipseTransformer { + private static final String LINKED_NODE_FINDER = "org/eclipse/jdt/core/internal/corext/dom/LinkedNodeFinder"; + + @Override public String getTargetClassName() { + return LINKED_NODE_FINDER; + } + + @Override public byte[] transform(byte[] in) { + ClassReader reader = new ClassReader(in); + ClassWriter writer = new ClassWriter(reader, 0); + + ClassAdapter adapter = new LinkedNodeFinderPatcherAdapter(writer); + reader.accept(adapter, 0); + return writer.toByteArray(); + } + + private static class LinkedNodeFinderPatcherAdapter extends ClassAdapter { + private int originalAccess; + private String originalDesc; + private String originalSignature; + private String[] originalExceptions; + + LinkedNodeFinderPatcherAdapter(ClassVisitor cv) { + super(cv); + } + + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if ( !name.equals("findByNode") ) return super.visitMethod(access, name, desc, signature, exceptions); + + originalAccess = access; + originalDesc = desc; + originalSignature = signature; + originalExceptions = exceptions; + + return super.visitMethod(0, "findByNode0", desc, signature, exceptions); + } + + private static final String SIMPLENAME = "org/eclipse/jdt/core/dom/SimpleName"; + private static final String SIMPLENAME_ARRAY = "[L" + SIMPLENAME + ";"; + private static final String ASTNODE = "Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;"; + + /* + * code generated by running ASMifier on: + * + public SimpleName[] findByNode(ASTNode a, SimpleName b) { + SimpleName[] ps = this.findByNode0(a, b); + int count = 0; + for (int i = 0; i < ps.length; i++) { + if ( ps[i] == null || ps[i].$generatedBy == null ) count++; + } + if (count == ps.length) return ps; + SimpleName[] newPs = new SimpleName[count]; + count = 0; + for (int i = 0; i < ps.length; i++) { + if ( ps[i] == null || ps[i].p == null ) newPs[count++] = ps[i]; + } + return newPs; + } + */ + @Override public void visitEnd() { + MethodVisitor mv = super.visitMethod(originalAccess, "findByNode", originalDesc, originalSignature, originalExceptions); + mv.visitCode(); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKESPECIAL, LINKED_NODE_FINDER, "findByNode0", originalDesc); + mv.visitVarInsn(ASTORE, 3); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ISTORE, 4); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ISTORE, 5); + Label l0 = new Label(); + mv.visitLabel(l0); + mv.visitFrame(F_APPEND,3, new Object[] {SIMPLENAME_ARRAY, INTEGER, INTEGER}, 0, null); + mv.visitVarInsn(ILOAD, 5); + mv.visitVarInsn(ALOAD, 3); + mv.visitInsn(ARRAYLENGTH); + Label l1 = new Label(); + mv.visitJumpInsn(IF_ICMPGE, l1); + mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ILOAD, 5); + mv.visitInsn(AALOAD); + Label l2 = new Label(); + mv.visitJumpInsn(IFNULL, l2); + mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ILOAD, 5); + mv.visitInsn(AALOAD); + mv.visitFieldInsn(GETFIELD, SIMPLENAME, "$generatedBy", ASTNODE); + Label l3 = new Label(); + mv.visitJumpInsn(IFNONNULL, l3); + mv.visitLabel(l2); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitIincInsn(4, 1); + mv.visitLabel(l3); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitIincInsn(5, 1); + mv.visitJumpInsn(GOTO, l0); + mv.visitLabel(l1); + mv.visitFrame(F_CHOP,1, null, 0, null); + mv.visitVarInsn(ILOAD, 4); + mv.visitVarInsn(ALOAD, 3); + mv.visitInsn(ARRAYLENGTH); + Label l4 = new Label(); + mv.visitJumpInsn(IF_ICMPNE, l4); + mv.visitVarInsn(ALOAD, 3); + mv.visitInsn(ARETURN); + mv.visitLabel(l4); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitVarInsn(ILOAD, 4); + mv.visitTypeInsn(ANEWARRAY, SIMPLENAME); + mv.visitVarInsn(ASTORE, 5); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ISTORE, 4); + mv.visitInsn(ICONST_0); + mv.visitVarInsn(ISTORE, 6); + Label l5 = new Label(); + mv.visitLabel(l5); + mv.visitFrame(F_APPEND,2, new Object[] {SIMPLENAME_ARRAY, INTEGER}, 0, null); + mv.visitVarInsn(ILOAD, 6); + mv.visitVarInsn(ALOAD, 3); + mv.visitInsn(ARRAYLENGTH); + Label l6 = new Label(); + mv.visitJumpInsn(IF_ICMPGE, l6); + mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ILOAD, 6); + mv.visitInsn(AALOAD); + Label l7 = new Label(); + mv.visitJumpInsn(IFNULL, l7); + mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ILOAD, 6); + mv.visitInsn(AALOAD); + mv.visitFieldInsn(GETFIELD, SIMPLENAME, "$generatedBy", ASTNODE); + Label l8 = new Label(); + mv.visitJumpInsn(IFNONNULL, l8); + mv.visitLabel(l7); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 5); + mv.visitVarInsn(ILOAD, 4); + mv.visitIincInsn(4, 1); + mv.visitVarInsn(ALOAD, 3); + mv.visitVarInsn(ILOAD, 6); + mv.visitInsn(AALOAD); + mv.visitInsn(AASTORE); + mv.visitLabel(l8); + mv.visitFrame(F_SAME, 0, null, 0, null); + mv.visitIincInsn(6, 1); + mv.visitJumpInsn(GOTO, l5); + mv.visitLabel(l6); + mv.visitFrame(F_CHOP,1, null, 0, null); + mv.visitVarInsn(ALOAD, 5); + mv.visitInsn(ARETURN); + mv.visitMaxs(4, 7); + super.visitEnd(); + } + } +} diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipseParserTransformer.java b/src_eclipseagent/lombok/eclipse/agent/EclipseParserTransformer.java index 6bcc7f77..38917332 100644 --- a/src_eclipseagent/lombok/eclipse/agent/EclipseParserTransformer.java +++ b/src_eclipseagent/lombok/eclipse/agent/EclipseParserTransformer.java @@ -27,6 +27,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import lombok.Lombok; + +import org.mangosdk.spi.ProviderFor; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -54,7 +57,8 @@ import org.objectweb.asm.Opcodes; * * Bit24 on the flags field on all ASTNode objects is used as a marker.</li></ul> */ -class EclipseParserTransformer { +@ProviderFor(EclipseTransformer.class) +public class EclipseParserTransformer implements EclipseTransformer { private static final String COMPILER_PKG = "Lorg/eclipse/jdt/internal/compiler/ast/"; private static final String TARGET_STATIC_CLASS = "java/lombok/eclipse/ClassLoaderWorkaround"; @@ -73,7 +77,7 @@ class EclipseParserTransformer { rewriters = Collections.unmodifiableMap(map); } - byte[] transform(byte[] classfileBuffer) { + public byte[] transform(byte[] classfileBuffer) { ClassReader reader = new ClassReader(classfileBuffer); ClassWriter writer = new ClassWriter(reader, 0); @@ -82,17 +86,6 @@ class EclipseParserTransformer { return writer.toByteArray(); } - static RuntimeException sneakyThrow(Throwable t) { - if ( t == null ) throw new NullPointerException("t"); - EclipseParserTransformer.<RuntimeException>sneakyThrow0(t); - return null; - } - - @SuppressWarnings("unchecked") - private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T { - throw (T)t; - } - private static class ParserPatcherAdapter extends ClassAdapter { public ParserPatcherAdapter(ClassVisitor cv) { super(cv); @@ -109,12 +102,12 @@ class EclipseParserTransformer { c.setAccessible(true); return c.newInstance(writerVisitor); } catch ( InvocationTargetException e ) { - throw sneakyThrow(e.getCause()); + throw Lombok.sneakyThrow(e.getCause()); } catch ( Exception e ) { //NoSuchMethodException: We know they exist. //IllegalAccessException: We called setAccessible. //InstantiationException: None of these classes are abstract. - throw sneakyThrow(e); + throw Lombok.sneakyThrow(e); } } } @@ -178,4 +171,8 @@ class EclipseParserTransformer { super.visitInsn(opcode); } } + + @Override public String getTargetClassName() { + return "org/eclipse/jdt/internal/compiler/parser/Parser"; + } } diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java b/src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java index 67371948..7659302c 100644 --- a/src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src_eclipseagent/lombok/eclipse/agent/EclipsePatcher.java @@ -26,17 +26,20 @@ import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.Charset; import java.security.ProtectionDomain; +import java.util.HashMap; +import java.util.Map; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.Lombok; +import lombok.core.SpiLoadUtil; + /** * This is a java-agent that patches some of eclipse's classes so AST Nodes are handed off to Lombok * for modification before Eclipse actually uses them to compile, render errors, show code outlines, @@ -47,57 +50,34 @@ import java.util.regex.Pattern; public class EclipsePatcher { private EclipsePatcher() {} + private static Map<String, EclipseTransformer> transformers = new HashMap<String, EclipseTransformer>(); + static { + try { + for ( EclipseTransformer transformer : SpiLoadUtil.findServices(EclipseTransformer.class) ) { + String targetClassName = transformer.getTargetClassName(); + transformers.put(targetClassName, transformer); + } + } catch ( Throwable t ) { + throw Lombok.sneakyThrow(t); + } + } + private static class Patcher implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { - if ( ECLIPSE_PARSER_CLASS_NAME.equals(className) ) { - try { - return runTransform("lombok.eclipse.agent.EclipseParserTransformer", classfileBuffer); - } catch ( Throwable t ) { - System.err.println("Wasn't able to patch eclipse's Parser class:"); - t.printStackTrace(); - } - } - - if ( ECLIPSE_CUD_CLASS_NAME.equals(className) ) { - try { - return runTransform("lombok.eclipse.agent.EclipseCUDTransformer", classfileBuffer); - } catch ( Throwable t ) { - System.err.println("Wasn't able to patch eclipse's CompilationUnitDeclaration class:"); - t.printStackTrace(); - } - } +// ClassLoader classLoader = Patcher.class.getClassLoader(); +// if ( classLoader == null ) classLoader = ClassLoader.getSystemClassLoader(); - if ( ECLIPSE_ASTCONVERTER_CLASS_NAME.equals(className) ) { - try { - return runTransform("lombok.eclipse.agent.EclipseASTConverterTransformer", classfileBuffer); - } catch ( Throwable t ) { - System.err.println("Wasn't able to patch eclipse's ASTConverter class:"); - t.printStackTrace(); - } - } + EclipseTransformer transformer = transformers.get(className); + if ( transformer != null ) return transformer.transform(classfileBuffer); return null; } } - private static byte[] runTransform(String className, byte[] classfileBuffer) throws Exception { - Class<?> transformerClass = Class.forName(className); - Constructor<?> constructor = transformerClass.getDeclaredConstructor(); - constructor.setAccessible(true); - Object instance = constructor.newInstance(); - Method m = transformerClass.getDeclaredMethod("transform", byte[].class); - m.setAccessible(true); - return (byte[])m.invoke(instance, classfileBuffer); - } - - static final String ECLIPSE_CUD_CLASS_NAME = "org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration"; - static final String ECLIPSE_PARSER_CLASS_NAME = "org/eclipse/jdt/internal/compiler/parser/Parser"; - static final String ECLIPSE_ASTCONVERTER_CLASS_NAME = "org/eclipse/jdt/core/dom/ASTConverter"; - public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Exception { registerPatcher(instrumentation, true); addLombokToSearchPaths(instrumentation); @@ -134,7 +114,7 @@ public class EclipsePatcher { instrumentation.addTransformer(new Patcher()/*, true*/); if ( transformExisting ) for ( Class<?> c : instrumentation.getAllLoadedClasses() ) { - if ( c.getName().equals(ECLIPSE_PARSER_CLASS_NAME) || c.getName().equals(ECLIPSE_CUD_CLASS_NAME) ) { + if ( transformers.containsKey(c.getName()) ) { try { //instrumentation.retransformClasses(c); - //not in java 1.5. Instrumentation.class.getMethod("retransformClasses", Class[].class).invoke(instrumentation, diff --git a/src_eclipseagent/lombok/eclipse/agent/EclipseTransformer.java b/src_eclipseagent/lombok/eclipse/agent/EclipseTransformer.java new file mode 100644 index 00000000..31f8413f --- /dev/null +++ b/src_eclipseagent/lombok/eclipse/agent/EclipseTransformer.java @@ -0,0 +1,8 @@ +package lombok.eclipse.agent; + +public interface EclipseTransformer { + /** slash and not dot separated */ + String getTargetClassName(); + + byte[] transform(byte[] in); +} |