diff options
6 files changed, 247 insertions, 3 deletions
diff --git a/src/core/lombok/core/debug/DebugSnapshot.java b/src/core/lombok/core/debug/DebugSnapshot.java new file mode 100644 index 00000000..d94798e8 --- /dev/null +++ b/src/core/lombok/core/debug/DebugSnapshot.java @@ -0,0 +1,62 @@ +package lombok.core.debug; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; + +public class DebugSnapshot implements Comparable<DebugSnapshot> { + private static AtomicLong counter = new AtomicLong(); + + private final long when, id = counter.getAndIncrement(); + private final List<StackTraceElement> trace; + private final String threadName; + private final String message; + private final Object[] params; + private final WeakReference<CompilationUnitDeclaration> owner; + + public DebugSnapshot(CompilationUnitDeclaration owner, int stackHiding, String message, Object... params) { + this.when = System.currentTimeMillis(); + StackTraceElement[] stackTrace = new Throwable().getStackTrace(); + this.trace = new ArrayList<StackTraceElement>(Math.max(0, stackTrace.length - stackHiding - 1)); + for (int i = 1 + stackHiding; i < stackTrace.length; i++) trace.add(stackTrace[i]); + this.threadName = Thread.currentThread().getName(); + this.message = message; + this.params = params == null ? new Object[0] : params; + this.owner = new WeakReference<CompilationUnitDeclaration>(owner); + } + + private String ownerName() { + CompilationUnitDeclaration node = owner.get(); + if (node == null) return "--GCed--"; + char[] tn = node.getMainTypeName(); + char[] fs = node.getFileName(); + if (tn == null || tn.length == 0) { + return (fs == null || fs.length == 0) ? "--UNKNOWN--" : new String(fs); + } + + return new String(tn); + } + + public String shortToString() { + StringBuilder out = new StringBuilder(); + out.append(String.format("WHEN: %14d THREAD: %s AST: %s", when, threadName, ownerName())); + if (message != null) out.append(" ").append(String.format(message, params)); + return out.toString(); + } + + @Override public String toString() { + StringBuilder out = new StringBuilder(); + out.append(shortToString()).append("\n"); + for (StackTraceElement elem : trace) { + out.append(" ").append(elem.toString()).append("\n"); + } + return out.toString(); + } + + @Override public int compareTo(DebugSnapshot o) { + return Long.valueOf(id).compareTo(o.id); + } +} diff --git a/src/core/lombok/core/debug/DebugSnapshotStore.java b/src/core/lombok/core/debug/DebugSnapshotStore.java new file mode 100644 index 00000000..7abfc07c --- /dev/null +++ b/src/core/lombok/core/debug/DebugSnapshotStore.java @@ -0,0 +1,70 @@ +package lombok.core.debug; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; + +public class DebugSnapshotStore { + public static final DebugSnapshotStore INSTANCE = new DebugSnapshotStore(); + + private final Map<CompilationUnitDeclaration, List<DebugSnapshot>> map = + new WeakHashMap<CompilationUnitDeclaration, List<DebugSnapshot>>(); + + public void snapshot(CompilationUnitDeclaration owner, String message, Object... params) { + DebugSnapshot snapshot = new DebugSnapshot(owner, 1, message, params); + List<DebugSnapshot> list; + + synchronized (map) { + list = map.get(owner); + if (list == null) { + list = new ArrayList<DebugSnapshot>(); + map.put(owner, list); + } + list.add(snapshot); + } + } + + public String print(CompilationUnitDeclaration owner, String message, Object... params) { + List<DebugSnapshot> list; + + synchronized (map) { + snapshot(owner, message == null ? "Printing" : message, params); + list = new ArrayList<DebugSnapshot>(); + list.addAll(map.get(owner)); + } + + Collections.sort(list); + int idx = 1; + StringBuilder out = new StringBuilder(); + out.append("---------------------------\n"); + for (DebugSnapshot snapshot : list) { + out.append(String.format("%3d: %s\n", idx++, snapshot.shortToString())); + } + out.append("******\n"); + idx = 1; + for (DebugSnapshot snapshot : list) { + out.append(String.format("%3d: %s", idx++, snapshot.toString())); + } + + try { + File logFile = new File(System.getProperty("user.home", "."), String.format("lombok164-%d.err", System.currentTimeMillis())); + OutputStream stream = new FileOutputStream(logFile); + try { + stream.write(out.toString().getBytes("UTF-8")); + } finally { + stream.close(); + } + return logFile.getAbsolutePath(); + } catch (Exception e) { + System.err.println(out); + return "(can't write log file - emitted to system err)"; + } + } +} diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index 7ef06fca..b362f399 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -23,6 +23,7 @@ package lombok.eclipse; import java.lang.reflect.Field; +import lombok.core.debug.DebugSnapshotStore; import lombok.patcher.Symbols; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; @@ -126,9 +127,12 @@ public class TransformEclipseAST { // Do NOT abort if (ast.bits & ASTNode.HasAllMethodBodies) != 0 - that doesn't work. try { + DebugSnapshotStore.INSTANCE.snapshot(ast, "transform entry"); EclipseAST existing = getAST(ast, false); new TransformEclipseAST(existing).go(); + DebugSnapshotStore.INSTANCE.snapshot(ast, "transform exit"); } catch (Throwable t) { + DebugSnapshotStore.INSTANCE.snapshot(ast, "transform error: %s", t.getClass().getSimpleName()); try { String message = "Lombok can't parse this source: " + t.toString(); diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 627acecb..45d58df3 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -25,6 +25,7 @@ import static lombok.eclipse.Eclipse.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; @@ -37,6 +38,7 @@ import lombok.Getter; import lombok.Lombok; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; +import lombok.core.debug.DebugSnapshotStore; import lombok.core.handlers.TransformationsUtil; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseNode; @@ -464,9 +466,9 @@ public class EclipseHandlerUtil { } if (report) { - Eclipse.warning("We believe you may have just stumbled on lombok issue #164. Please " + - "report the stack trace associated with this message at:\n" + - "http://code.google.com/p/projectlombok/issues/detail?id=164", new Throwable()); + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) type.top().get(); + String logFileLocation = DebugSnapshotStore.INSTANCE.print(cud, "Printing: injecting whilst scope is already built."); + Eclipse.warning("We believe you may have stumbled on issue 164. Please upload file " + logFileLocation + " to: http://code.google.com/p/projectlombok/issues/detail?id=164", new Throwable()); } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index c49346bc..f67bd4e3 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -21,11 +21,15 @@ */ package lombok.eclipse.agent; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; import java.util.Collection; import java.util.Collections; import java.util.List; +import lombok.bytecode.ClassFileMetaData; import lombok.core.Agent; import lombok.patcher.Hook; import lombok.patcher.MethodTarget; @@ -68,6 +72,15 @@ public class EclipsePatcher extends Agent { else ecj = injected; registerPatchScripts(instrumentation, injected, ecj); + instrumentation.addTransformer(new ClassFileTransformer() { + @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + ClassFileMetaData meta = new ClassFileMetaData(classfileBuffer); + if (meta.usesField("org/eclipse/jdt/internal/compiler/ast/TypeDeclaration", "scope")) { + return Issue164Fixer.fix(classfileBuffer); + } + return null; + } + }, true); } private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly) { diff --git a/src/eclipseAgent/lombok/eclipse/agent/Issue164Fixer.java b/src/eclipseAgent/lombok/eclipse/agent/Issue164Fixer.java new file mode 100644 index 00000000..65e04e44 --- /dev/null +++ b/src/eclipseAgent/lombok/eclipse/agent/Issue164Fixer.java @@ -0,0 +1,93 @@ +package lombok.eclipse.agent; + +import lombok.core.debug.DebugSnapshotStore; + +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope; +import org.eclipse.jdt.internal.compiler.lookup.Scope; +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodAdapter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.JSRInlinerAdapter; + +public class Issue164Fixer { + public static byte[] fix(byte[] classfileBuffer) { + return runAsm(classfileBuffer, true); + } + + static class FixedClassWriter extends ClassWriter { + FixedClassWriter(ClassReader classReader, int flags) { + super(classReader, flags); + } + + @Override protected String getCommonSuperClass(String type1, String type2) { + //By default, ASM will attempt to live-load the class types, which will fail if meddling with classes in an + //environment with custom classloaders, such as Equinox. It's just an optimization; returning Object is always legal. + try { + return super.getCommonSuperClass(type1, type2); + } catch (Exception e) { + return "java/lang/Object"; + } + } + } + + public static void catchScopeSet(TypeDeclaration typeDeclaration, ClassScope scope) { + typeDeclaration.scope = scope; + Scope sc = scope; + while (sc != null && !(sc instanceof CompilationUnitScope)) { + sc = sc.parent; + } + + if (sc instanceof CompilationUnitScope) { + CompilationUnitDeclaration cud = ((CompilationUnitScope) sc).referenceContext; + DebugSnapshotStore.INSTANCE.snapshot(cud, "Scope is being set"); + } + } + + /** + * Runs ASM on the provider byteCode, chaining a reader to a writer and using the {@code ClassVisitor} you yourself provide + * via the {@see #createClassVisitor(ClassWriter)} method as the filter. + */ + protected static byte[] runAsm(byte[] byteCode, boolean computeFrames) { + byte[] fixedByteCode = fixJSRInlining(byteCode); + + ClassReader reader = new ClassReader(fixedByteCode); + ClassWriter writer = new FixedClassWriter(reader, computeFrames ? ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES : 0); + + ClassVisitor visitor = new ClassAdapter(writer) { + @Override public MethodVisitor visitMethod(int access, final String mName, String desc, String signature, String[] exceptions) { + return new MethodAdapter(super.visitMethod(access, mName, desc, signature, exceptions)) { + @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (opcode == Opcodes.PUTFIELD && "org/eclipse/jdt/internal/compiler/ast/TypeDeclaration".equals(owner) && "scope".equals(name) && !"catchScopeSet".equals(mName)) { + super.visitMethodInsn(Opcodes.INVOKESTATIC, "lombok/eclipse/agent/Issue164Fixer", "catchScopeSet", "(Lorg/eclipse/jdt/internal/compiler/ast/TypeDeclaration;Lorg/eclipse/jdt/internal/compiler/lookup/ClassScope;)V"); + } else { + super.visitFieldInsn(opcode, owner, name, desc); + } + } + }; + } + }; + reader.accept(visitor, 0); + return writer.toByteArray(); + } + + protected static byte[] fixJSRInlining(byte[] byteCode) { + ClassReader reader = new ClassReader(byteCode); + ClassWriter writer = new FixedClassWriter(reader, 0); + + ClassVisitor visitor = new ClassAdapter(writer) { + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new JSRInlinerAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc, signature, exceptions); + } + }; + + reader.accept(visitor, 0); + return writer.toByteArray(); + } +} |