diff --git a/build.gradle b/build.gradle index 5826d79..63356da 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,9 @@ allprojects { repositories { mavenCentral() + maven { + url "http://maven.springframework.org/milestone" + } } } @@ -14,10 +17,10 @@ subprojects { apply plugin: 'eclipse' apply from: "$rootDir/gradle/publish-maven.gradle" - sourceCompatibility = 1.6 - targetCompatibility = 1.6 + sourceCompatibility = 1.6 + targetCompatibility = 1.6 } configure(subprojects.findAll { it.name.startsWith('testdata')}) { tasks.findByPath("artifactoryPublish")?.enabled = false -} \ No newline at end of file +} diff --git a/settings.gradle b/settings.gradle index 34851ef..047c69d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,7 @@ include "springloaded" include "testdata" +include "testdata-java8" +include "testdata-aspectj" include "testdata-groovy" include "testdata-plugin" include "testdata-subloader" diff --git a/springloaded/.classpath b/springloaded/.classpath index 18ff684..720d087 100644 --- a/springloaded/.classpath +++ b/springloaded/.classpath @@ -2,9 +2,9 @@ - - - + + + diff --git a/springloaded/build.gradle b/springloaded/build.gradle index dab5d37..984c690 100644 --- a/springloaded/build.gradle +++ b/springloaded/build.gradle @@ -34,14 +34,20 @@ task wrapper(type: Wrapper) { dependencies { tools 'com.googlecode.jarjar:jarjar:1.3' +/* compile 'asm:asm:3.2' compile 'asm:asm-tree:3.2' - +*/ + compile 'org.ow2.asm:asm:5.0_BETA' + compile 'org.ow2.asm:asm-tree:5.0_BETA' + testCompile 'junit:junit:4.11' - testCompileOnly files("../testdata-groovy/groovy-1.8.2.jar") + testCompileOnly files("../testdata-groovy/groovy-all-1.8.6.jar") testCompileOnly project(':testdata') + testCompileOnly project(':testdata-aspectj') testCompileOnly project(':testdata-groovy') + testCompileOnly project(':testdata-java8') testCompileOnly project(':testdata-plugin') testCompileOnly project(':testdata-subloader') testCompileOnly project(':testdata-superloader') diff --git a/springloaded/lib/asm-5.0_BETA.jar b/springloaded/lib/asm-5.0_BETA.jar new file mode 100644 index 0000000..2e26615 Binary files /dev/null and b/springloaded/lib/asm-5.0_BETA.jar differ diff --git a/springloaded/lib/asm-tree-5.0_BETA.jar b/springloaded/lib/asm-tree-5.0_BETA.jar new file mode 100644 index 0000000..e486d18 Binary files /dev/null and b/springloaded/lib/asm-tree-5.0_BETA.jar differ diff --git a/springloaded/notes.md b/springloaded/notes.md index b3aa627..a0aa06b 100644 --- a/springloaded/notes.md +++ b/springloaded/notes.md @@ -2,6 +2,7 @@ Implementation details: catchers +======== - A catcher is added to a reloadable type as it is being loaded for the first time. A reloadable type gets a catcher for each method it inherits from a parent but does not override, or for an abstract class each method it receives from an interface. They basically stand in for methods that could be added in later @@ -12,12 +13,36 @@ private, final or static methods since those cannot be overridden. If you modif type to make it non final and then override it, it will be handled in a different way than catchers. superdispatchers +================ - Where a method is protected in a non-reloadable type it is necessary to add a superdispatcher method to the subtype so that when the executor for a new version is running it can access that protected method. The superdispatcher is simply a public method on a type that calls super. +Generic dispatcher method __execute +=================================== + +The way in which we handle new methods appearing on types is that all reloadable types get a generic handler method added to +them when first loaded - this can forward it on to the new method that has appeared. The method is like this: + +__execute(Object[] params, Object target, String nameAndDescriptor) + +All interfaces also get this method. + +(#001) Lambdas introduce a problem here. The Lambda meta factory creates anonymous classes that forward to the lambda handling +method. It does this outside of our control using a more direct form of class defining which we don't see (can't instrument). This means +these generated classes don't get an __execute. This means if a new method is added to the SAM type, although we notice it +we can't generate an __execute in the meta factory created class. This means the standard redirection of INVOKEINTERFACE which says: +does this method exist on the original form of the type? yes, then call it. no, then call the __execute telling it what we'd like to run. +Well that will fail because of the missing __execute. There are two solutions: +- modify the InnerClassLambdaMetaFactory to ensure an __execute (and relevant marker interface) are added +- change how we handle the INVOKEINTERFACE rewrite. + +The second option is cheap to implement but performance will likely suck. The simplest way to do it is call the type registry to do the +suitable invoke via reflection - and it will recognize the lambda case and know what to do. + +---------- Helpful snippets when debugging tests: ClassPrinter.print(z.getLatestExecutorBytes()); Utils.dump("foo/SubControllerB", rtype.bytesLoaded); diff --git a/springloaded/springloaded tests (no generated tests) (1.8).launch b/springloaded/springloaded tests (no generated tests) (1.8).launch new file mode 100644 index 0000000..b09fecc --- /dev/null +++ b/springloaded/springloaded tests (no generated tests) (1.8).launch @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java index a51b3ef..921524e 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java +++ b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java @@ -18,11 +18,11 @@ package org.springsource.loaded; import java.util.HashMap; import java.util.Map; -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.MethodAdapter; +import org.objectweb.asm.Handle; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -54,7 +54,7 @@ public class ClassRenamer { return renamed; } - static class RenameAdapter extends ClassAdapter implements Opcodes { + static class RenameAdapter extends ClassVisitor implements Opcodes { private ClassWriter cw; private String oldname; @@ -62,7 +62,7 @@ public class ClassRenamer { private Map retargets = new HashMap(); public RenameAdapter(String newname, String[] retargets) { - super(new ClassWriter(0)); + super(ASM5,new ClassWriter(0)); cw = (ClassWriter) cv; this.newname = newname.replace('.', '/'); if (retargets != null) { @@ -111,12 +111,15 @@ public class ClassRenamer { } return string; } - + @Override public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { if (descriptor.indexOf(oldname) != -1) { descriptor = descriptor.replace(oldname, newname); } else { + if (descriptor.indexOf(oldname) != -1) { + descriptor = descriptor.replace(oldname, newname); + } for (String s : retargets.keySet()) { if (descriptor.indexOf(s) != -1) { descriptor = descriptor.replace(s, retargets.get(s)); @@ -141,13 +144,13 @@ public class ClassRenamer { return super.visitField(access, name, desc, signature, value); } - class RenameMethodAdapter extends MethodAdapter implements Opcodes { + class RenameMethodAdapter extends MethodVisitor implements Opcodes { String oldname; String newname; public RenameMethodAdapter(MethodVisitor mv, String oldname, String newname) { - super(mv); + super(ASM5,mv); this.oldname = oldname; this.newname = newname; } @@ -224,6 +227,50 @@ public class ClassRenamer { } } + private String toString(Handle bsm) { + return "["+bsm.getTag()+"]"+bsm.getOwner()+"."+bsm.getName()+bsm.getDesc(); + } + + private String toString(Object[] os) { + StringBuilder buf = new StringBuilder(); + if (os!=null) { + buf.append("["); + for (int i=0;i0) buf.append(","); + buf.append(os[i]); + } + buf.append("]"); + } + else { + return "null"; + } + return buf.toString(); + } + + private Handle retargetHandle(Handle oldHandle) { + int tag = oldHandle.getTag(); + String owner = oldHandle.getOwner(); + String name = oldHandle.getName(); + String desc = oldHandle.getDesc(); + owner = renameRetargetIfNecessary(owner); + Handle newHandle = new Handle(tag,owner,name,desc); + return newHandle; + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + // System.out.println("visitInvokeDynamicInsn(name="+name+",desc="+desc+",bsm="+toString(bsm)+",bsmArgs="+toString(bsmArgs)+")"); + // Example: + // visitInvokeDynamicInsn(name=m,desc=()Lbasic/LambdaA2$Foo;, + // bsm=[6]java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;, + // bsmArgs=[()I,basic/LambdaA2.lambda$run$1()I (6),()I]) + desc = renameRetargetIfNecessary(desc); + if (bsmArgs[1] instanceof Handle) { + bsmArgs[1] = retargetHandle((Handle)bsmArgs[1]); + } + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + public void visitMethodInsn(int opcode, String owner, String name, String desc) { if (owner.equals(oldname)) { owner = newname; diff --git a/springloaded/src/main/java/org/springsource/loaded/Constants.java b/springloaded/src/main/java/org/springsource/loaded/Constants.java index 24ea275..22e6c25 100644 --- a/springloaded/src/main/java/org/springsource/loaded/Constants.java +++ b/springloaded/src/main/java/org/springsource/loaded/Constants.java @@ -79,8 +79,10 @@ public interface Constants extends Opcodes { static String mChangedForInvocationName = "anyChanges"; static String mChangedForInvokeStaticName = "istcheck"; static String mChangedForInvokeInterfaceName = "iincheck"; + static String mChangedForInvokeDynamicName = "idycheck"; static String mChangedForInvokeVirtualName = "ivicheck"; static String mChangedForInvokeSpecialName = "ispcheck"; + static String mPerformInvokeDynamicName = "idyrun"; static String descriptorChangedForInvokeSpecialName = "(ILjava/lang/String;)Lorg/springsource/loaded/__DynamicallyDispatchable;"; static String mChangedForConstructorName = "ccheck"; @@ -103,6 +105,7 @@ public interface Constants extends Opcodes { static int ACC_PUBLIC_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED; static int ACC_PUBLIC_PRIVATE_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED; + static int ACC_PRIVATE_STATIC_SYNTHETIC = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC; static int ACC_PRIVATE_PROTECTED = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED; static int ACC_PRIVATE_STATIC_FINAL = ACC_FINAL | ACC_STATIC | ACC_PRIVATE; diff --git a/springloaded/src/main/java/org/springsource/loaded/ConstructorCopier.java b/springloaded/src/main/java/org/springsource/loaded/ConstructorCopier.java index 323c309..bb398c1 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ConstructorCopier.java +++ b/springloaded/src/main/java/org/springsource/loaded/ConstructorCopier.java @@ -16,14 +16,13 @@ package org.springsource.loaded; import org.objectweb.asm.Label; -import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; /** * @author Andy Clement * @since 0.5.0 */ -class ConstructorCopier extends MethodAdapter implements Constants { +class ConstructorCopier extends MethodVisitor implements Constants { private final static int preInvokeSpecial = 0; private final static int postInvokeSpecial = 1; @@ -39,7 +38,7 @@ class ConstructorCopier extends MethodAdapter implements Constants { private String classname; public ConstructorCopier(MethodVisitor mv, TypeDescriptor typeDescriptor, String suffix, String classname) { - super(mv); + super(ASM5,mv); this.typeDescriptor = typeDescriptor; this.suffix = suffix; this.classname = classname; diff --git a/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java b/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java index 045bd45..16a175d 100644 --- a/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java +++ b/springloaded/src/main/java/org/springsource/loaded/DispatcherBuilder.java @@ -57,7 +57,7 @@ public class DispatcherBuilder { /** * Whilst visiting the interface, the implementation is created. */ - static class DispatcherBuilderVisitor implements ClassVisitor, Opcodes, Constants { + static class DispatcherBuilderVisitor extends ClassVisitor implements Opcodes, Constants { private ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); @@ -68,6 +68,7 @@ public class DispatcherBuilder { private IncrementalTypeDescriptor typeDescriptor; public DispatcherBuilderVisitor(ReloadableType rtype, IncrementalTypeDescriptor typeDescriptor, String suffix) { + super(ASM5); this.classname = rtype.getSlashedName(); this.typeDescriptor = typeDescriptor; this.suffix = suffix; diff --git a/springloaded/src/main/java/org/springsource/loaded/EmptyClassVisitor.java b/springloaded/src/main/java/org/springsource/loaded/EmptyClassVisitor.java index a21d72c..d2024fa 100644 --- a/springloaded/src/main/java/org/springsource/loaded/EmptyClassVisitor.java +++ b/springloaded/src/main/java/org/springsource/loaded/EmptyClassVisitor.java @@ -27,7 +27,11 @@ import org.objectweb.asm.MethodVisitor; * @author Andy Clement * @since 0.7.3 */ -public class EmptyClassVisitor implements ClassVisitor, Constants { +public class EmptyClassVisitor extends ClassVisitor implements Constants { + + public EmptyClassVisitor() { + super(ASM5); + } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { } diff --git a/springloaded/src/main/java/org/springsource/loaded/ExecutorBuilder.java b/springloaded/src/main/java/org/springsource/loaded/ExecutorBuilder.java index 29737f5..8f1de84 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ExecutorBuilder.java +++ b/springloaded/src/main/java/org/springsource/loaded/ExecutorBuilder.java @@ -69,7 +69,7 @@ public class ExecutorBuilder { * ClassVisitor that constructs the executor by visiting the original class. The basic goal is to visit the original class and * 'copy' the methods into the executor, making adjustments as we go. */ - static class ExecutorBuilderVisitor implements ClassVisitor, Constants { + static class ExecutorBuilderVisitor extends ClassVisitor implements Constants { private ClassWriter cw = new ClassWriter(0); @@ -78,6 +78,7 @@ public class ExecutorBuilder { protected TypeDescriptor typeDescriptor; public ExecutorBuilderVisitor(String classname, String suffix, TypeDescriptor typeDescriptor) { + super(ASM5); this.classname = classname; this.suffix = suffix; this.typeDescriptor = typeDescriptor; @@ -128,7 +129,16 @@ public class ExecutorBuilder { if (name.charAt(1) != 'c') { // regular constructor // want to create the ___init___ handler for this constructor + + // With the JDT compiler the inner class constructor gets an extra first parameter that is the type of + // containing class. But with javac the inner class constructor gets an extra first parameter that is of + // a special anonymous type (inner class of the containing class) + // For example: class Foo { class Bar {}} + // JDT: ctor in Bar is (Foo) {} + // JAVAC: ctor in Bar is (Foo$1) {} + descriptor = Utils.insertExtraParameter(classname, descriptor); + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mInitializerName, descriptor, signature, exceptions); ConstructorCopier cc = new ConstructorCopier(mv, typeDescriptor, suffix, classname); @@ -145,11 +155,12 @@ public class ExecutorBuilder { cw.visitSource(sourcefile, debug); } - private static class CopyingAnnotationVisitor implements AnnotationVisitor { + private static class CopyingAnnotationVisitor extends AnnotationVisitor { private AnnotationVisitor av; public CopyingAnnotationVisitor(AnnotationVisitor av) { + super(ASM5); this.av = av; } diff --git a/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java b/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java index 769f305..f942a2d 100644 --- a/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java +++ b/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java @@ -362,4 +362,16 @@ public class GlobalConfiguration { } debugplugins = debugPlugins; } + + public final static boolean isJava18orHigher; + + static { + String version = System.getProperty("java.version"); + if (version.startsWith("1.8")) { + isJava18orHigher = true; + } + else { + isJava18orHigher = false; + } + } } diff --git a/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java b/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java index d00e467..e7d09a0 100644 --- a/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java +++ b/springloaded/src/main/java/org/springsource/loaded/InterfaceExtractor.java @@ -66,13 +66,14 @@ public class InterfaceExtractor { return extractorVisitor.getBytes(); } - class ExtractorVisitor implements ClassVisitor, Constants { + class ExtractorVisitor extends ClassVisitor implements Constants { private TypeDescriptor typeDescriptor; private ClassWriter interfaceWriter = new ClassWriter(0); private String slashedtypename; public ExtractorVisitor(TypeDescriptor typeDescriptor) { + super(ASM5); this.typeDescriptor = typeDescriptor; } diff --git a/springloaded/src/main/java/org/springsource/loaded/MethodCopier.java b/springloaded/src/main/java/org/springsource/loaded/MethodCopier.java index 3d048a2..6505103 100644 --- a/springloaded/src/main/java/org/springsource/loaded/MethodCopier.java +++ b/springloaded/src/main/java/org/springsource/loaded/MethodCopier.java @@ -16,7 +16,6 @@ package org.springsource.loaded; import org.objectweb.asm.Label; -import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import org.springsource.loaded.Utils.ReturnType; @@ -26,7 +25,7 @@ import org.springsource.loaded.Utils.ReturnType; * @author Andy Clement * @since 0.5.0 */ -class MethodCopier extends MethodAdapter implements Constants { +class MethodCopier extends MethodVisitor implements Constants { private boolean isInterface; private String descriptor; @@ -37,7 +36,7 @@ class MethodCopier extends MethodAdapter implements Constants { public MethodCopier(MethodVisitor mv, boolean isInterface, String descriptor, TypeDescriptor typeDescriptor, String classname, String suffix) { - super(mv); + super(ASM5,mv); this.isInterface = isInterface; this.descriptor = descriptor; this.typeDescriptor = typeDescriptor; diff --git a/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java index 1466783..72f245e 100644 --- a/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java +++ b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java @@ -28,15 +28,15 @@ import java.util.HashSet; import java.util.Map; import java.util.NoSuchElementException; import java.util.StringTokenizer; +import java.util.logging.Level; import java.util.logging.Logger; -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.Handle; import org.objectweb.asm.Label; -import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.springsource.loaded.ConstantPoolChecker2.References; @@ -436,7 +436,7 @@ public class MethodInvokerRewriter { static class DontRewriteException extends RuntimeException { } - static class RewriteClassAdaptor extends ClassAdapter implements Opcodes { + static class RewriteClassAdaptor extends ClassVisitor implements Opcodes { private ClassVisitor cw; @@ -667,14 +667,15 @@ public class MethodInvokerRewriter { return intercepted.contains(owner + "." + methodName); } - private TypeRegistry typeRegistry; + protected TypeRegistry typeRegistry; boolean isEnum = false; private boolean isGroovyClosure = false; int fieldcount = 0; + private ReloadableType rtype; // Can be null if rewriting in a non reloadable type public RewriteClassAdaptor(TypeRegistry typeRegistry, ClassVisitor classWriter) { // TODO should it also compute frames? - super(classWriter); + super(ASM5,classWriter); cw = cv; this.typeRegistry = typeRegistry; } @@ -691,7 +692,7 @@ public class MethodInvokerRewriter { public ClassVisitor getClassVisitor() { return cv; } - + @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); @@ -721,7 +722,7 @@ public class MethodInvokerRewriter { return new RewritingMethodAdapter(mv, name); } - class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants { + class RewritingMethodAdapter extends MethodVisitor implements Opcodes, Constants { // tracks max variable used in a method so we know what we can use // safely @@ -731,7 +732,7 @@ public class MethodInvokerRewriter { private boolean isClinitOrEnumInit = false; public RewritingMethodAdapter(MethodVisitor mv, String methodname) { - super(mv); + super(ASM5,mv); this.methodname = methodname; if (isEnum) { isClinitOrEnumInit = this.methodname.length() > 2 && this.methodname.charAt(0) == '<' @@ -868,6 +869,7 @@ public class MethodInvokerRewriter { } private void rewritePUTSTATIC(int opcode, String owner, String name, String desc) { + // TODO [perf] cache this information for 'us' so lookup not always necessary int classId = typeRegistry.getTypeIdFor(owner, true); mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); // Make a call to check if this field operation must be intercepted: @@ -950,6 +952,102 @@ public class MethodInvokerRewriter { super.visitTypeInsn(opcode, type); } + private String toString(Handle handle) { + return "handle(tag="+handle.getTag()+",name="+handle.getName()+",desc="+handle.getDesc()+",owner="+handle.getOwner(); + } + + private String toString(Object[] oa) { + StringBuilder buf = new StringBuilder(); + buf.append("["); + if (oa!=null) { + for (Object o:oa) { + buf.append(" "); + buf.append(o); + } + } + buf.append("]"); + return buf.toString(); + } + + boolean hasParams(String descriptor) { + return descriptor.charAt(1)!=')'; + } + + /** + * Generate bytecode to convert parameters on the stack into an array (based on the descriptor). If the + * descriptor shows there are no parameters then null is stacked. + * + * @param descriptor MethodType descriptor showing parameters and return value + */ + private void stackParameters(String descriptor) { + if (hasParams(descriptor)) { + Utils.collapseStackToArray(mv, descriptor); + } + else { + // no params + mv.visitInsn(ACONST_NULL); + } + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + // TODO *shudder* what about invoke dynamic calls that target reflective APIs + int classId = typeRegistry.getTypeIdFor(slashedclassname, false); + if (classId==-1) { + throw new IllegalStateException(); + } + // Initially only rewriting use of INVOKEDYNAMIC to support Lambda execution + // TODO support the more general invokedynamic usage + + // Example data at this point: + // name=m + // desc=()Lbasic/LambdaA2$Foo; + // bsm=handle(tag=6, + // name=metafactory, + // desc=(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;, + // owner=java/lang/invoke/LambdaMetafactory + // bsmArgs=[ ()I basic/LambdaA2.lambda$run$1()I (6) ()I] + if (bsm.getTag()==H_INVOKESTATIC && bsm.getName().equals("metafactory") && bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory")) { + // System.out.println("InvokeDynamic(name="+name+",desc="+desc+",bsm="+toString(bsm)+",bsmArgs="+toString(bsmArgs)); + // Only when the BSM is LambdaMetafactory.metafactory are we rewriting the invokedynamic. Since LambdaMetafactory will not + // be getting reloaded, we can avoid a bunch of complexity. When the bsm points to a reloadable type we'll have to + // do more hoop jumping. + + // Check on reloading having happened + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeDynamicName, "()Ljava/lang/Object;"); + + // mv.visitInsn(DUP); + + Label nochange = new Label(); + mv.visitJumpInsn(IFNULL, nochange); + +// // 9. do what we were going to do +// mv.visitLabel(l1); + + stackParameters(desc); + int bsmReferenceId = typeRegistry.recordBootstrapMethod(slashedclassname,bsm,bsmArgs); + // Method java/lang/invoke/MethodHandles.lookup:()Ljava/lang/invoke/MethodHandles$Lookup; + mv.visitLdcInsn(typeRegistry.getId()); + mv.visitLdcInsn(classId); + mv.visitMethodInsn(INVOKESTATIC,"java/lang/invoke/MethodHandles","lookup","()Ljava/lang/invoke/MethodHandles$Lookup;"); + mv.visitLdcInsn(name+desc); // Ljava/lang/String; + mv.visitLdcInsn(bsmReferenceId); // I + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mPerformInvokeDynamicName, "([Ljava/lang/Object;IILjava/lang/Object;Ljava/lang/String;I)Ljava/lang/Object;"); + + Label gotolabel = new Label(); + mv.visitJumpInsn(GOTO, gotolabel); + mv.visitLabel(nochange); + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + mv.visitLabel(gotolabel); + } + else { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.WARNING)) { + log.warning("[current limitation] not rewriting invokedynamic usage in type '"+slashedclassname+"'. InvokeDynamic(name="+name+",desc="+desc+",bsm="+toString(bsm)+",bsmArgs="+toString(bsmArgs)); + } + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + } + @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { if (GlobalConfiguration.interceptReflection && rewriteReflectiveCall(opcode, owner, name, desc)) { @@ -1138,8 +1236,15 @@ public class MethodInvokerRewriter { mv.visitLdcInsn(name + desc); // [targetInstance paramArray targetInstance nameAndDescriptor] - // calling __execute(params array, this, name+desc) - mv.visitMethodInsn(INVOKEINTERFACE, owner, mDynamicDispatchName, mDynamicDispatchDescriptor); + if (GlobalConfiguration.isJava18orHigher) { + // if the target is a generated lambda callsite object then calling __execute isn't going to work as those + // types don't have the method in them! + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, "iiIntercept", "(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"); + } + else { + // calling __execute(params array, this, name+desc) + mv.visitMethodInsn(INVOKEINTERFACE, owner, mDynamicDispatchName, mDynamicDispatchDescriptor); + } insertAppropriateReturn(returnType); Label gotolabel = new Label(); diff --git a/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java b/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java index 2fca034..a0ab008 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java +++ b/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java @@ -31,8 +31,8 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.springsource.loaded.MethodInvokerRewriter.DontRewriteException; import org.springsource.loaded.MethodInvokerRewriter.RewriteClassAdaptor; @@ -839,10 +839,10 @@ public class ReloadableType { } } - static class ChainedAdapters extends ClassAdapter implements Constants { + static class ChainedAdapters extends ClassVisitor implements Constants { public ChainedAdapters(ReloadableType rtype) { - super(new RewriteClassAdaptor(rtype.typeRegistry, new TypeRewriter.RewriteClassAdaptor(rtype, new ClassWriter( + super(ASM5,new RewriteClassAdaptor(rtype.typeRegistry, new TypeRewriter.RewriteClassAdaptor(rtype, new ClassWriter( ClassWriter.COMPUTE_MAXS)))); } diff --git a/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionInvestigator.java b/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionInvestigator.java index 415e8c0..7684c89 100644 --- a/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionInvestigator.java +++ b/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionInvestigator.java @@ -15,10 +15,9 @@ */ package org.springsource.loaded; -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; @@ -39,7 +38,7 @@ public class SystemClassReflectionInvestigator { return classAdaptor.hitCount; } - static class RewriteClassAdaptor extends ClassAdapter implements Constants { + static class RewriteClassAdaptor extends ClassVisitor implements Constants { int hitCount = 0; private ClassWriter cw; @@ -52,7 +51,7 @@ public class SystemClassReflectionInvestigator { public RewriteClassAdaptor() { // TODO should it also compute frames? - super(new ClassWriter(ClassWriter.COMPUTE_MAXS)); + super(ASM5,new ClassWriter(ClassWriter.COMPUTE_MAXS)); cw = (ClassWriter) cv; } @@ -77,10 +76,10 @@ public class SystemClassReflectionInvestigator { return new RewritingMethodAdapter(mv); } - class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants { + class RewritingMethodAdapter extends MethodVisitor implements Opcodes, Constants { public RewritingMethodAdapter(MethodVisitor mv) { - super(mv); + super(ASM5,mv); } private boolean interceptReflection(String owner, String name, String desc) { diff --git a/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionRewriter.java b/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionRewriter.java index 63c4285..127655a 100644 --- a/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionRewriter.java +++ b/springloaded/src/main/java/org/springsource/loaded/SystemClassReflectionRewriter.java @@ -18,12 +18,11 @@ package org.springsource.loaded; import java.util.logging.Level; import java.util.logging.Logger; -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.Label; -import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -105,7 +104,7 @@ public class SystemClassReflectionRewriter { } } - static class RewriteClassAdaptor extends ClassAdapter implements Constants { + static class RewriteClassAdaptor extends ClassVisitor implements Constants { private ClassWriter cw; int bits = 0x0000; @@ -122,7 +121,7 @@ public class SystemClassReflectionRewriter { public RewriteClassAdaptor() { // TODO should it also compute frames? - super(new ClassWriter(ClassWriter.COMPUTE_MAXS)); + super(ASM5,new ClassWriter(ClassWriter.COMPUTE_MAXS)); cw = (ClassWriter) cv; } @@ -189,10 +188,10 @@ public class SystemClassReflectionRewriter { } } - class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants { + class RewritingMethodAdapter extends MethodVisitor implements Opcodes, Constants { public RewritingMethodAdapter(MethodVisitor mv) { - super(mv); + super(ASM5,mv); } /** diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java b/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java index 2c37f53..3428cdc 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeDescriptorExtractor.java @@ -54,7 +54,7 @@ public class TypeDescriptorExtractor { /** * Visit a class and accumulate sufficient information to build a TypeDescriptor. */ - class ExtractionVisitor implements ClassVisitor, Opcodes { + class ExtractionVisitor extends ClassVisitor implements Opcodes { private boolean isReloadableType; private int flags; @@ -72,6 +72,7 @@ public class TypeDescriptorExtractor { private List finalInHierarchy = new ArrayList(); public ExtractionVisitor(boolean isReloadableType) { + super(ASM5); this.isReloadableType = isReloadableType; } diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeDiffComputer.java b/springloaded/src/main/java/org/springsource/loaded/TypeDiffComputer.java index 0839438..345d5e3 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeDiffComputer.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeDiffComputer.java @@ -625,7 +625,10 @@ public class TypeDiffComputer implements Opcodes { // td.setTypeVersionChange(oldClassNode.version, newClassNode.version); // } if (oldClassNode.access != newClassNode.access) { - td.setTypeAccessChange(oldClassNode.access, newClassNode.access); + // Is it only because of 0x20000 - that appears to represent Deprecated! + if ((oldClassNode.access & 0xffff) != (newClassNode.access&0xffff)) { + td.setTypeAccessChange(oldClassNode.access, newClassNode.access); + } } if (!oldClassNode.name.equals(newClassNode.name)) { td.setTypeNameChange(oldClassNode.name, newClassNode.name); diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index ef9753e..1c8e03b 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -42,11 +42,13 @@ import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import org.objectweb.asm.Handle; import org.springsource.loaded.agent.FileSystemWatcher; import org.springsource.loaded.agent.ReloadDecision; import org.springsource.loaded.agent.ReloadableFileChangeListener; import org.springsource.loaded.agent.SpringLoadedPreProcessor; import org.springsource.loaded.infra.UsedByGeneratedCode; +import org.springsource.loaded.support.Java8; // TODO debug: stepping into deleted methods - should delete line number table for deleted methods @@ -492,33 +494,41 @@ public class TypeRegistry { configuration = new Properties(GlobalConfiguration.globalConfigurationProperties); try { Set configurationFiles = new HashSet(); - Enumeration resources = classLoader.get().getResources("springloaded.properties"); - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - String configFile = url.toString(); - if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { - log.log(Level.INFO, this.toString() + ": processing config file: " + url.toString()); + ClassLoader classloader = classLoader.get(); + Enumeration resources = classloader==null?null:classloader.getResources("springloaded.properties"); + if (resources == null) { + if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) { + log.info("Unable to load springloaded.properties, cannot find it through classloader "+classloader); } - if (configurationFiles.contains(configFile)) { - continue; - } - configurationFiles.add(configFile); - InputStream is = url.openStream(); - - Properties p = new Properties(); - p.load(is); - is.close(); - Set keys = p.stringPropertyNames(); - for (String key : keys) { - if (!configuration.containsKey(key)) { - configuration.put(key, p.getProperty(key)); - } else { - // Extend our configuration - String valueSoFar = configuration.getProperty(key); - StringBuilder sb = new StringBuilder(valueSoFar); - sb.append(","); - sb.append(p.getProperty(key)); - configuration.put(key, sb.toString()); + } + else { + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + String configFile = url.toString(); + if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) { + log.log(Level.INFO, this.toString() + ": processing config file: " + url.toString()); + } + if (configurationFiles.contains(configFile)) { + continue; + } + configurationFiles.add(configFile); + InputStream is = url.openStream(); + + Properties p = new Properties(); + p.load(is); + is.close(); + Set keys = p.stringPropertyNames(); + for (String key : keys) { + if (!configuration.containsKey(key)) { + configuration.put(key, p.getProperty(key)); + } else { + // Extend our configuration + String valueSoFar = configuration.getProperty(key); + StringBuilder sb = new StringBuilder(valueSoFar); + sb.append(","); + sb.append(p.getProperty(key)); + configuration.put(key, sb.toString()); + } } } } @@ -1116,6 +1126,21 @@ public class TypeRegistry { directlyDefineTypes = should; } + /** + *Used to determine if the invokedynamic needs to be intercepted. + * + * @return null if nothing has been reloaded + */ + @UsedByGeneratedCode + public static Object idycheck() { + if (TypeRegistry.nothingReloaded) { + return null; + } + else { + return "reloading-happened"; + } + } + /** * Determine if something has changed in a particular type related to a particular descriptor and so the dispatcher interface * should be used. The type registry ID and class ID are merged in the 'ids' parameter. This method is for INVOKESTATIC rewrites @@ -1242,6 +1267,33 @@ public class TypeRegistry { return null; // let it fail anyway } + /** + * See notes.md#001 + * + */ + public static Object iiIntercept(Object instance, Object[] params, Object instance2, String nameAndDescriptor) { + Class clazz= instance.getClass(); + try { + if (clazz.getName().contains("$$Lambda")) { + // There will only be one method, the SAM method + Method[] ms = instance.getClass().getDeclaredMethods(); + Method m = ms[0]; + m.setAccessible(true); + Object o = m.invoke(instance, params); + return o; + } + else { + // Do what you were going to do... + Method m = instance.getClass().getDeclaredMethod("__execute",Object[].class,Object.class,String.class); + m.setAccessible(true); + return m.invoke(instance, params, instance, nameAndDescriptor); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + @UsedByGeneratedCode public static __DynamicallyDispatchable ispcheck(int ids, String nameAndDescriptor) { if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) { @@ -1459,7 +1511,17 @@ public class TypeRegistry { } return false; } - + + @UsedByGeneratedCode + public static Object idyrun(Object[] indyParams, int typeRegistryId, int classId, Object caller, String nameAndDescriptor, int bsmId) { + // Typical next line: lookup=basic.LambdaA nameAD=m()Lbasic/LambdaA$Foo; bsmId=0 + // System.err.println("idyrun("+caller+","+nameAndDescriptor+","+bsmId+")"); + // TODO Currently leaking entries in bsmmap with reloads (new ones get added, old ones not removed) + ReloadableType rtype = TypeRegistry.getReloadableType(typeRegistryId, classId); + BsmInfo bsmi = bsmmap.get(rtype.getSlashedName())[bsmId]; + return Java8.emulateInvokeDynamic(rtype.getLatestExecutorClass(),bsmi.bsm,bsmi.bsmArgs,caller,nameAndDescriptor, indyParams); + } + /** * Used in code the generated code replaces invokevirtual calls. Determine if the code can run as it was originally compiled. * @@ -1856,4 +1918,47 @@ public class TypeRegistry { public Set getJDKProxiesFor(String slashedInterfaceTypeName) { return jdkProxiesForInterface.get(slashedInterfaceTypeName); } + + + /** + * When an invokedynamic instruction is reached, we allocate an id that + * recognizes that bsm and the parameters to that bsm. The index can be + * used when rewriting that invokedynamic + * + * @return id that represents this bootstrap method usage + */ + public synchronized int recordBootstrapMethod(String slashedClassName, Handle bsm, Object[] bsmArgs) { + if (bsmmap == null) { + bsmmap = new HashMap(); + } + BsmInfo[] bsminfo = bsmmap.get(slashedClassName); + if (bsminfo== null) { + bsminfo = new BsmInfo[1]; + // TODO do we need BsmInfo or can we just use Handle directly? + bsminfo[0] = new BsmInfo(bsm, bsmArgs); + bsmmap.put(slashedClassName,bsminfo); + return 0; + } + else { + int len = bsminfo.length; + BsmInfo[] newarray = new BsmInfo[len+1]; + System.arraycopy(bsminfo, 0, newarray, 0, len); + bsminfo = newarray; + bsmmap.put(slashedClassName,bsminfo); + bsminfo[len] = new BsmInfo(bsm,bsmArgs); + return len; + } + // TODO [memory] search the existing bsmInfos for a matching one! Reuse! + } + + private static Map bsmmap; + + static class BsmInfo { + Handle bsm; + Object[] bsmArgs; + public BsmInfo(Handle bsm, Object[] bsmArgs) { + this.bsm = bsm; + this.bsmArgs = bsmArgs; + } + } } \ No newline at end of file diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java b/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java index 327df49..f8a7bd0 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java @@ -19,13 +19,11 @@ import java.lang.reflect.Modifier; import java.util.logging.Level; import java.util.logging.Logger; -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.Label; -import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.springsource.loaded.Utils.ReturnType; @@ -55,7 +53,7 @@ public class TypeRewriter implements Constants { return classAdaptor.getBytes(); } - static class RewriteClassAdaptor extends ClassAdapter implements Constants { + static class RewriteClassAdaptor extends ClassVisitor implements Constants { private ClassWriter cw; private String slashedname; @@ -72,7 +70,7 @@ public class TypeRewriter implements Constants { } public RewriteClassAdaptor(ReloadableType rtype, ClassWriter classWriter) { - super(classWriter); + super(ASM5,classWriter); this.rtype = rtype; this.slashedname = rtype.getSlashedName(); this.cw = (ClassWriter) cv; @@ -414,7 +412,7 @@ public class TypeRewriter implements Constants { @Override public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) { - MethodVisitor mv = super.visitMethod(promoteIfNecessary(flags), name, descriptor, signature, exceptions); + MethodVisitor mv = super.visitMethod(promoteIfNecessary(flags,name), name, descriptor, signature, exceptions); MethodVisitor newMethodVisitor = getMethodVisitor(name, descriptor, mv); return newMethodVisitor; } @@ -444,8 +442,8 @@ public class TypeRewriter implements Constants { } // Default visibility elements need promotion to public so that they can be seen from the executor - private int promoteIfNecessary(int flags) { - int newflags = Utils.promoteDefaultOrProtectedToPublic(flags, isEnum); + private int promoteIfNecessary(int flags,String name) { + int newflags = Utils.promoteDefaultOrProtectedToPublic(flags, isEnum, name); return newflags; } @@ -816,7 +814,7 @@ public class TypeRewriter implements Constants { /** * Rewrites a method to include the extra checks to verify it is the most up to date version. */ - class AugmentingMethodAdapter extends MethodAdapter implements Opcodes { + class AugmentingMethodAdapter extends MethodVisitor implements Opcodes { int methodId; String name; @@ -825,7 +823,7 @@ public class TypeRewriter implements Constants { ReturnType returnType; public AugmentingMethodAdapter(MethodVisitor mv, String name, String descriptor) { - super(mv); + super(ASM5,mv); this.name = name; this.method = rtype.getMethod(name, descriptor); this.methodId = method.getId(); @@ -916,7 +914,7 @@ public class TypeRewriter implements Constants { } - class AugmentingConstructorAdapter extends MethodAdapter implements Opcodes { + class AugmentingConstructorAdapter extends MethodVisitor implements Opcodes { int ctorId; String name; @@ -926,7 +924,7 @@ public class TypeRewriter implements Constants { boolean isTopMost; public AugmentingConstructorAdapter(MethodVisitor mv, String descriptor, String type, boolean isTopMost) { - super(mv); + super(ASM5,mv); this.descriptor = descriptor; this.type = type; this.isTopMost = isTopMost; @@ -1053,12 +1051,12 @@ public class TypeRewriter implements Constants { void prepend(); } - class MethodPrepender extends MethodAdapter implements Opcodes { + class MethodPrepender extends MethodVisitor implements Opcodes { Prepender appender; public MethodPrepender(MethodVisitor mv, Prepender appender) { - super(mv); + super(ASM5,mv); this.appender = appender; } diff --git a/springloaded/src/main/java/org/springsource/loaded/Utils.java b/springloaded/src/main/java/org/springsource/loaded/Utils.java index 2541ef6..ed35c0a 100644 --- a/springloaded/src/main/java/org/springsource/loaded/Utils.java +++ b/springloaded/src/main/java/org/springsource/loaded/Utils.java @@ -1369,7 +1369,7 @@ public class Utils implements Opcodes, Constants { return access; } - public static int promoteDefaultOrProtectedToPublic(int access, boolean isEnum) { + public static int promoteDefaultOrProtectedToPublic(int access, boolean isEnum, String name) { if ((access & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) { // is default return (access | Modifier.PUBLIC); @@ -1382,6 +1382,10 @@ public class Utils implements Opcodes, Constants { // was private, need to 'publicize' it return access - Constants.ACC_PRIVATE + Constants.ACC_PUBLIC; } + if ((access&Constants.ACC_PRIVATE_STATIC_SYNTHETIC)==ACC_PRIVATE_STATIC_SYNTHETIC && name.startsWith("lambda")) { + // Special case for lambda, may need to generalize for general invokedynamic support + return access - Constants.ACC_PRIVATE + Constants.ACC_PUBLIC; + } return access; } @@ -1733,7 +1737,11 @@ public class Utils implements Opcodes, Constants { // TODO [performance] speed up by throwing exception from first visit method? (but this isn't used in the mainline really) // TODO or just write a quicker bytecode parser that just looks at the interfaces then returns - private static class InterfaceCollectingClassVisitor implements ClassVisitor { + private static class InterfaceCollectingClassVisitor extends ClassVisitor { + + public InterfaceCollectingClassVisitor() { + super(ASM5); + } public String[] interfaces; diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/CglibPluginCapturing.java b/springloaded/src/main/java/org/springsource/loaded/agent/CglibPluginCapturing.java index cd9de43..41eb180 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/CglibPluginCapturing.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/CglibPluginCapturing.java @@ -19,10 +19,9 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; -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.springsource.loaded.Constants; @@ -33,7 +32,7 @@ import org.springsource.loaded.Constants; * @author Andy Clement * @since 0.8.3 */ -public class CglibPluginCapturing extends ClassAdapter implements Constants { +public class CglibPluginCapturing extends ClassVisitor implements Constants { public static Map, Object[]> clazzToGeneratorStrategyAndClassGeneratorMap = new HashMap, Object[]>(); public static Map, Object[]> clazzToGeneratorStrategyAndFastClassGeneratorMap = new HashMap, Object[]>(); @@ -48,7 +47,7 @@ public class CglibPluginCapturing extends ClassAdapter implements Constants { } private CglibPluginCapturing() { - super(new ClassWriter(0)); // TODO review 0 here + super(ASM5,new ClassWriter(0)); // TODO review 0 here } @Override @@ -74,10 +73,10 @@ public class CglibPluginCapturing extends ClassAdapter implements Constants { } } - class CreateMethodInterceptor extends MethodAdapter implements Constants { + class CreateMethodInterceptor extends MethodVisitor implements Constants { public CreateMethodInterceptor(MethodVisitor mv) { - super(mv); + super(ASM5,mv); } @Override diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/ClassVisitingConstructorAppender.java b/springloaded/src/main/java/org/springsource/loaded/agent/ClassVisitingConstructorAppender.java index dea4960..740ec8a 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/ClassVisitingConstructorAppender.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/ClassVisitingConstructorAppender.java @@ -15,9 +15,8 @@ */ package org.springsource.loaded.agent; -import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import org.springsource.loaded.Constants; @@ -27,7 +26,7 @@ import org.springsource.loaded.Constants; * @author Andy Clement * @since 0.5.0 */ -public class ClassVisitingConstructorAppender extends ClassAdapter implements Constants { +public class ClassVisitingConstructorAppender extends ClassVisitor implements Constants { private String calleeOwner; private String calleeName; @@ -41,7 +40,7 @@ public class ClassVisitingConstructorAppender extends ClassAdapter implements Co * @param name */ public ClassVisitingConstructorAppender(String owner, String name) { - super(new ClassWriter(0)); // TODO review 0 here + super(ASM5,new ClassWriter(0)); // TODO review 0 here this.calleeOwner = owner; this.calleeName = name; } @@ -63,10 +62,10 @@ public class ClassVisitingConstructorAppender extends ClassAdapter implements Co * This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit. It * recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN. */ - class ConstructorAppender extends MethodAdapter implements Constants { + class ConstructorAppender extends MethodVisitor implements Constants { public ConstructorAppender(MethodVisitor mv) { - super(mv); + super(ASM5,mv); } @Override diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/FalseReturner.java b/springloaded/src/main/java/org/springsource/loaded/agent/FalseReturner.java index b22029b..1cec1a7 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/FalseReturner.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/FalseReturner.java @@ -15,7 +15,7 @@ */ package org.springsource.loaded.agent; -import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.springsource.loaded.Constants; @@ -26,12 +26,12 @@ import org.springsource.loaded.Constants; * @author Andy Clement * @since 0.7.0 */ -public class FalseReturner extends ClassAdapter implements Constants { +public class FalseReturner extends ClassVisitor implements Constants { private String methodname; public FalseReturner(String methodname) { - super(new ClassWriter(0)); // TODO review 0 here + super(ASM5,new ClassWriter(0)); // TODO review 0 here this.methodname = methodname; } diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType.java b/springloaded/src/main/java/org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType.java index f155e29..baa4057 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType.java @@ -15,9 +15,8 @@ */ package org.springsource.loaded.agent; -import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.MethodVisitor; import org.springsource.loaded.Constants; import org.springsource.loaded.GlobalConfiguration; @@ -30,10 +29,10 @@ import org.springsource.loaded.TypeRegistry; * @author Andy Clement * @since 0.7.3 */ -public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassAdapter implements Constants { +public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassVisitor implements Constants { public ModifyDefineInClassLoaderForClassArtifactsType() { - super(new ClassWriter(0)); // TODO review 0 here + super(ASM5,new ClassWriter(0)); // TODO review 0 here } public byte[] getBytes() { @@ -49,10 +48,10 @@ public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassAdapter } } - class DefineClassModifierVisitor extends MethodAdapter implements Constants { + class DefineClassModifierVisitor extends MethodVisitor implements Constants { public DefineClassModifierVisitor(MethodVisitor mv) { - super(mv); + super(ASM5,mv); } @Override diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/NonFinalizer.java b/springloaded/src/main/java/org/springsource/loaded/agent/NonFinalizer.java index 24d6e15..0ede959 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/NonFinalizer.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/NonFinalizer.java @@ -17,7 +17,7 @@ package org.springsource.loaded.agent; import java.lang.reflect.Modifier; -import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.springsource.loaded.Constants; @@ -29,7 +29,7 @@ import org.springsource.loaded.Constants; * @author Andy Clement * @since 0.7.0 */ -public class NonFinalizer extends ClassAdapter implements Constants { +public class NonFinalizer extends ClassVisitor implements Constants { private String fieldname; @@ -42,7 +42,7 @@ public class NonFinalizer extends ClassAdapter implements Constants { * @param name */ public NonFinalizer(String fieldname) { - super(new ClassWriter(0)); // TODO review 0 here + super(ASM5,new ClassWriter(0)); // TODO review 0 here this.fieldname = fieldname; } diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java index 25471a8..c7e75fc 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java @@ -41,6 +41,7 @@ import org.springsource.loaded.TypeRegistry; import org.springsource.loaded.Utils; import org.springsource.loaded.SystemClassReflectionRewriter.RewriteResult; import org.springsource.loaded.ri.ReflectiveInterceptor; +import org.springsource.loaded.support.Java8; /** * The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be dealt with in @@ -148,6 +149,10 @@ public class SpringLoadedPreProcessor implements Constants { // return rr.bytes; // } } + else if (slashedClassName.equals("java/lang/invoke/InnerClassLambdaMetafactory")) { + bytes = Java8.enhanceInnerClassLambdaMetaFactory(bytes); + return bytes; + } } return bytes; } diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/SpringPlugin.java b/springloaded/src/main/java/org/springsource/loaded/agent/SpringPlugin.java index a006b9f..dcdcb20 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/SpringPlugin.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/SpringPlugin.java @@ -104,6 +104,15 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP } private static boolean debug = false; + + static { + try { + String debugString = System.getProperty("springloaded.plugins.spring.debug","false"); + debug = Boolean.valueOf(debugString); + } catch (Exception e) { + // likely security exception + } + } // called by the modified code public static void recordDefaultAnnotationHandlerMappingInstance(Object obj) { @@ -212,12 +221,12 @@ public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventP method_initHandlerMethods.setAccessible(true); method_initHandlerMethods.invoke(o); } catch (NoSuchFieldException nsfe) { - if (log.isLoggable(Level.WARNING)) { + if (debug) { if (nsfe.getMessage().equals("handlerMethods")) { - log.warning("problem resetting request mapping handlers - unable to find field 'handlerMethods' on type 'AbstractHandlerMethodMapping' - you probably are not on Spring 3.1"); + System.out.println("problem resetting request mapping handlers - unable to find field 'handlerMethods' on type 'AbstractHandlerMethodMapping' - you probably are not on Spring 3.1"); } else { - log.warning("problem resetting request mapping handlers - NoSuchFieldException: "+nsfe.getMessage()); + System.out.println("problem resetting request mapping handlers - NoSuchFieldException: "+nsfe.getMessage()); } } } catch (Exception e) { diff --git a/springloaded/src/main/java/org/springsource/loaded/pluginhelpers/EmptyCtor.java b/springloaded/src/main/java/org/springsource/loaded/pluginhelpers/EmptyCtor.java index 2a2542b..3328a08 100644 --- a/springloaded/src/main/java/org/springsource/loaded/pluginhelpers/EmptyCtor.java +++ b/springloaded/src/main/java/org/springsource/loaded/pluginhelpers/EmptyCtor.java @@ -17,8 +17,8 @@ package org.springsource.loaded.pluginhelpers; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; -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; @@ -32,7 +32,7 @@ import org.springsource.loaded.test.infra.ClassPrinter; * @author Andy Clement * @since 0.8.3 */ -public class EmptyCtor extends ClassAdapter implements Constants { +public class EmptyCtor extends ClassVisitor implements Constants { private String[] descriptors; @@ -52,7 +52,7 @@ public class EmptyCtor extends ClassAdapter implements Constants { } private EmptyCtor(String... descriptors) { - super(new ClassWriter(0)); // TODO review 0 here + super(ASM5,new ClassWriter(0)); // TODO review 0 here this.descriptors = descriptors; } @@ -80,11 +80,12 @@ public class EmptyCtor extends ClassAdapter implements Constants { } } - static class Emptier implements MethodVisitor, Constants { + static class Emptier extends MethodVisitor implements Constants { MethodVisitor mv; public Emptier(MethodVisitor mv) { + super(ASM5); this.mv = mv; } diff --git a/springloaded/src/main/java/org/springsource/loaded/support/Java8.java b/springloaded/src/main/java/org/springsource/loaded/support/Java8.java new file mode 100644 index 0000000..6b467c1 --- /dev/null +++ b/springloaded/src/main/java/org/springsource/loaded/support/Java8.java @@ -0,0 +1,154 @@ +/* + * Copyright 2014 Pivotal Software Inc. and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springsource.loaded.support; + +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * This class encapsulates dependencies on Java 8 APIs (e.g. LambdaMetafactory). + * + * @author Andy Clement + * @since 1.2 + */ +public class Java8 { + + /** + * Notes: + * + * Useful to have an example of how this code behaves. Here is a bit of code: + * + * class basic.LambdaA { + * interface Foo { int m(); } + * static int run() { + * Foo f = null; + * f = () -> 77; + * return f.m(); + * } + * } + * + * Here is a bootstrap method entry in the constant pool: + * + * 0: #31 invokestatic java/lang/invoke/LambdaMetafactory.metafactory: + * (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; + * Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; + * Method arguments: + * #32 ()I + * #33 invokestatic basic/LambdaA.lambda$run$0:()I + * #32 ()I + * + * At the invokedynamic site: + * bsmId = 0 + * nameAndDescriptor = m()Lbasic/LambdaA$Foo; + * + * When invoking the metafactory bootstrap method the first two parameters are stacked by the VM automatically, namely the MethodHandles$Lookup + * instance (caller) and the first String (invokedName). What the VM actually sees is this: + * + * metaFactory parameters: + * 0:MethodHandles$Lookup caller = basic.LambdaA + * 1:String invokedName = "m" + * 2:MethodType invokedType = "()Foo" + * 3:MethodType samMethodType = "()int" + * 4:MethodHandle implMethod = (actually a DirectMethodHandle where memberName is "basic.LambdaA.lambda$run$0()int/invokeStatic") + * 5:MethodType instantiatedMethodType = "()int" + * + * With all that information then the calls in this case are relatively straightforward: + * CallSite callsite = LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); + * callsite.dynamicInvoker().invokeWithArguments((Object[])null); + */ + + /** + * Programmatic emulation of INVOKEDYNAMIC so initialize the callsite via use of the bootstrap method then + * invoke the result. + * + * @param executorClass null if not yet reloaded + * @param handle + * @param bsmArgs + * @param lookup + * @return + */ + public static Object emulateInvokeDynamic(Class executorClass, Handle handle, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor, Object[] indyParams) { + try { + CallSite callsite = callLambdaMetaFactory(bsmArgs,lookup,indyNameAndDescriptor,executorClass); + return callsite.dynamicInvoker().invokeWithArguments(indyParams); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + // TODO [perf] How about a table of CallSites indexed by invokedynamic number through the class file. Computed on first reference but cleared on reload. Possibly extend this to all invoke types! + + // TODO [lambda] Need to handle altMetaFactory which is used when the lambdas are more 'complex' (e.g. Serializable) + public static CallSite callLambdaMetaFactory(Object[] bsmArgs, Object lookup, String indyNameAndDescriptor,Class executorClass) throws Exception { + MethodHandles.Lookup caller = (MethodHandles.Lookup)lookup; + + ClassLoader callerLoader = caller.lookupClass().getClassLoader(); + + int descriptorStart = indyNameAndDescriptor.indexOf('('); + String invokedName = indyNameAndDescriptor.substring(0,descriptorStart); + MethodType invokedType = MethodType.fromMethodDescriptorString(indyNameAndDescriptor.substring(descriptorStart), callerLoader); + + // Use bsmArgs to build the parameters + MethodType samMethodType = MethodType.fromMethodDescriptorString((String)(((Type)bsmArgs[0]).getDescriptor()), callerLoader); + + Handle bsmArgsHandle = (Handle)bsmArgs[1]; + String owner = bsmArgsHandle.getOwner(); + String name = bsmArgsHandle.getName(); + String descriptor = bsmArgsHandle.getDesc(); + MethodType implMethodType = MethodType.fromMethodDescriptorString(descriptor, callerLoader); + // Looking up the lambda$run method in the caller class (note the caller class is the executor, which gets us around the + // problem of having to hack into LambdaMetafactory to intercept reflection) + MethodHandle implMethod = null; + // TODO [lambda] need to handle invokevirtual, surely + switch (bsmArgsHandle.getTag()) { + case Opcodes.H_INVOKESTATIC: + implMethod = caller.findStatic(caller.lookupClass(), name, implMethodType); + break; + case Opcodes.H_INVOKESPECIAL: + // If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup: + if (executorClass == null) { + implMethod = caller.findSpecial(caller.lookupClass(), name, implMethodType, caller.lookupClass()); + } + else { + implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString("(L"+owner+";"+descriptor.substring(1),callerLoader)); + } + break; + default: + throw new IllegalStateException("nyi "+bsmArgsHandle.getTag()); + } + + MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString((String)(((Type)bsmArgs[2]).getDescriptor()), callerLoader); + + return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); + } + + /** + * The metafactory we are enhancing is responsible for generating the anonymous classes that will call the lambda methods in our type + * + * @param bytes + * @return + */ + public static byte[] enhanceInnerClassLambdaMetaFactory(byte[] bytes) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java b/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java index 204d2e3..e9de0a3 100644 --- a/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java +++ b/springloaded/src/main/java/org/springsource/loaded/test/infra/ClassPrinter.java @@ -32,7 +32,7 @@ import org.springsource.loaded.Utils; /** * @author Andy Clement */ -public class ClassPrinter implements ClassVisitor, Opcodes { +public class ClassPrinter extends ClassVisitor implements Opcodes { private PrintStream destination; private boolean includeBytecode; @@ -47,6 +47,7 @@ public class ClassPrinter implements ClassVisitor, Opcodes { } public ClassPrinter(PrintStream destination, boolean includeBytecode) { + super(ASM5); this.destination = destination; this.includeBytecode = includeBytecode; } @@ -188,7 +189,11 @@ public class ClassPrinter implements ClassVisitor, Opcodes { return new AnnotationVisitorPrinter(); } - class AnnotationVisitorPrinter implements AnnotationVisitor { + class AnnotationVisitorPrinter extends AnnotationVisitor { + + public AnnotationVisitorPrinter() { + super(ASM5); + } public void visit(String name, Object value) { destination.print(name + "=" + value + " "); diff --git a/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java b/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java index 04121be..d00cc9a 100644 --- a/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java +++ b/springloaded/src/main/java/org/springsource/loaded/test/infra/MethodPrinter.java @@ -21,17 +21,17 @@ import java.util.List; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; +import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.springsource.loaded.Utils; - /** * * @author Andy Clement */ -public class MethodPrinter implements MethodVisitor, Opcodes { +public class MethodPrinter extends MethodVisitor implements Opcodes { PrintStream to; @@ -47,12 +47,22 @@ public class MethodPrinter implements MethodVisitor, Opcodes { } public MethodPrinter(PrintStream destination) { + super(ASM5); this.to = destination; } public void visitCode() { to.print(" CODE\n"); } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + to.println(" INVOKEDYNAMIC " + name+"."+desc+" bsm="+toString(bsm)); + } + + private String toString(Handle bsm) { + return "#"+bsm.getTag()+" "+bsm.getOwner()+"."+bsm.getName()+bsm.getDesc(); + } public void visitMethodInsn(int opcode, String owner, String name, String desc) { if (opcode == Opcodes.INVOKESTATIC) { @@ -75,7 +85,11 @@ public class MethodPrinter implements MethodVisitor, Opcodes { return new AnnotationVisitorPrinter(); } - class AnnotationVisitorPrinter implements AnnotationVisitor { + class AnnotationVisitorPrinter extends AnnotationVisitor { + + public AnnotationVisitorPrinter() { + super(ASM5); + } public void visit(String name, Object value) { to.print(name + "=" + value + " "); diff --git a/springloaded/src/test/java/org/springsource/loaded/test/ExecutorBuilderTests.java b/springloaded/src/test/java/org/springsource/loaded/test/ExecutorBuilderTests.java index 7ec24d1..497864e 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/ExecutorBuilderTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/ExecutorBuilderTests.java @@ -17,6 +17,7 @@ package org.springsource.loaded.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -262,7 +263,10 @@ public class ExecutorBuilderTests extends SpringLoadedTests { s.add(anno.toString()); } Assert.assertTrue(s.remove("@common.Marker()")); - Assert.assertTrue(s.remove("@common.Anno(someValue=37, longValue=2, id=abc)")); + // Allow for alternate toString() variant + if (!s.remove("@common.Anno(someValue=37, longValue=2, id=abc)")) { + Assert.assertTrue(s.remove("@common.Anno(longValue=2, someValue=37, id=abc)")); + } Assert.assertEquals(0, s.size()); } @@ -296,8 +300,36 @@ public class ExecutorBuilderTests extends SpringLoadedTests { checkAnnotations(rtype.getLatestExecutorBytes(), "m2(Lexecutor/I;)V", "@common.Marker()", "@common.Anno(id=abc)"); Method m = rtype.getLatestExecutorClass().getDeclaredMethod("m2", rtype.getClazz()); assertEquals("@common.Marker()", m.getAnnotations()[0].toString()); - assertEquals("@common.Anno(someValue=37, longValue=2, id=abc)", m.getAnnotations()[1].toString()); + assertIsOneOfThese(printAnnotation(m.getAnnotations()[1]),"@common.Anno(someValue=37, longValue=2, id=abc)", "@common.Anno(longValue=2, someValue=37, id=abc)"); } + + /** + * Check the actual value is one of the possible options. + */ + private void assertIsOneOfThese(String actual, String... possibleValues) { + StringBuilder buf = new StringBuilder(); + for (int i=0;i clazz = a.annotationType();a.toString() +// clazz.getDeclaredFields()[0].get +// System.out.println(a.annotationType()); +// +// } @Test public void methodLevelAnnotationsOnInterfaces2() throws Exception { diff --git a/springloaded/src/test/java/org/springsource/loaded/test/FieldReloadingTests.java b/springloaded/src/test/java/org/springsource/loaded/test/FieldReloadingTests.java index fc7b80e..a4e55e8 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/FieldReloadingTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/FieldReloadingTests.java @@ -564,7 +564,11 @@ public class FieldReloadingTests extends SpringLoadedTests { } catch (ResultException re) { assertTrue(re.getCause() instanceof InvocationTargetException); assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError); - assertEquals("Expected static field fields.Yb.j", re.getCause().getCause().getMessage()); + // When compiled with AspectJ vs Eclipse JDT the GETSTATIC actually varies. + // With AspectJ it is: PUTSTATIC fields/Yb.j : I + // With JDT (4.3) it is: PUTSTATIC fields/Zb.j : I + // hence the error is different + assertEquals("Expected static field fields.Zb.j", re.getCause().getCause().getMessage()); } // Now should be an IncompatibleClassChangeError @@ -574,7 +578,11 @@ public class FieldReloadingTests extends SpringLoadedTests { } catch (ResultException re) { assertTrue(re.getCause() instanceof InvocationTargetException); assertTrue(re.getCause().getCause() instanceof IncompatibleClassChangeError); - assertEquals("Expected static field fields.Yb.j", re.getCause().getCause().getMessage()); + // When compiled with AspectJ vs Eclipse JDT the GETSTATIC actually varies. + // With AspectJ it is: GETSTATIC fields/Yb.j : I + // With JDT (4.3) it is: GETSTATIC fields/Zb.j : I + // hence the error is different + assertEquals("Expected static field fields.Zb.j", re.getCause().getCause().getMessage()); } } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/InnerClassesTests.java b/springloaded/src/test/java/org/springsource/loaded/test/InnerClassesTests.java index caddd68..df64de5 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/InnerClassesTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/InnerClassesTests.java @@ -18,6 +18,7 @@ package org.springsource.loaded.test; import org.junit.Test; import org.springsource.loaded.ReloadableType; import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.ClassPrinter; /** @@ -68,11 +69,15 @@ public class InnerClassesTests extends SpringLoadedTests { public void reloadPrivateVisInner() throws Exception { String tclass = "inners.Three"; TypeRegistry typeRegistry = getTypeRegistry("inners..*"); - typeRegistry.addType("inners.Three$Inner", retrieveRename("inners.Three$Inner", "inners.Three2$Inner")); + + ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); runUnguarded(rtype.getClazz(), "runner"); - rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2", "inners.Three2$Inner:inners.Three$Inner")); +// ReloadableType rtypeInner = + typeRegistry.addType("inners.Three$Inner", retrieveRename("inners.Three$Inner", "inners.Three2$Inner","inners.Three2:inners.Three")); + + rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2", "inners.Three2$Inner:inners.Three$Inner","inners.Three2:inners.Three")); runUnguarded(rtype.getClazz(), "runner"); } @@ -84,11 +89,11 @@ public class InnerClassesTests extends SpringLoadedTests { public void reloadProtectedVisInner() throws Exception { String tclass = "inners.Four"; TypeRegistry typeRegistry = getTypeRegistry("inners..*"); - typeRegistry.addType("inners.Four$Inner", retrieveRename("inners.Four$Inner", "inners.Four2$Inner")); + typeRegistry.addType("inners.Four$Inner", retrieveRename("inners.Four$Inner", "inners.Four2$Inner","inners.Four2:inners.Four")); ReloadableType rtype = typeRegistry.addType(tclass, loadBytesForClass(tclass)); runUnguarded(rtype.getClazz(), "runner"); - rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2", "inners.Four2$Inner:inners.Four$Inner")); + rtype.loadNewVersion("2", retrieveRename(tclass, tclass + "2", "inners.Four2$Inner:inners.Four$Inner","inners.Four2:inners.Four")); runUnguarded(rtype.getClazz(), "runner"); } } \ No newline at end of file diff --git a/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java b/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java new file mode 100644 index 0000000..570a239 --- /dev/null +++ b/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java @@ -0,0 +1,285 @@ +/* + * Copyright 2014 Pivotal Software Inc. and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springsource.loaded.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Ignore; +import org.junit.Test; +import org.springsource.loaded.ReloadableType; +import org.springsource.loaded.TypeRegistry; +import org.springsource.loaded.test.infra.Result; + +/** + * Test reloading of Java 8. + * + * @author Andy Clement + * @since 1.2 + */ +public class Java8Tests extends SpringLoadedTests { + + @Test + public void theBasics() { + String t = "basic.FirstClass"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = new ReloadableType(t, sc, 1, typeRegistry, null); + + assertEquals(1, rtype.getId()); + assertEquals(t, rtype.getName()); + assertEquals(slashed(t), rtype.getSlashedName()); + assertNotNull(rtype.getTypeDescriptor()); + assertEquals(typeRegistry, rtype.getTypeRegistry()); + } + + @Test + public void callBasicType() throws Exception { + String t = "basic.FirstClass"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(8, r.returnValue); + + rtype.loadNewVersion("002", rtype.bytesInitial); + + r = runUnguarded(simpleClass, "run"); + assertEquals(8, r.returnValue); + } + + @Test + public void lambdaA() throws Exception { + String t = "basic.LambdaA"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(77, r.returnValue); + + rtype.loadNewVersion("002", rtype.bytesInitial); + r = runUnguarded(simpleClass, "run"); + assertEquals(77, r.returnValue); + } + + @Test + public void changingALambda() throws Exception { + String t = "basic.LambdaA"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(77, r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Foo:"+t+"$Foo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals(88, r.returnValue); + } + + @Test + public void lambdaWithParameter() throws Exception { + String t = "basic.LambdaB"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(99L, r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Foo:"+t+"$Foo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals(176L, r.returnValue); + } + + + @Test + public void lambdaWithTwoParameters() throws Exception { + String t = "basic.LambdaC"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(6L, r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals(5L, r.returnValue); + } + + @Test + public void lambdaWithThreeMixedTypeParameters() throws Exception { + String t = "basic.LambdaD"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals("true342abc", r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals("def264true", r.returnValue); + } + + @Test + public void lambdaWithCapturedVariable() throws Exception { + String t = "basic.LambdaE"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals("aaaa", r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals("aaaaaaaa", r.returnValue); + } + + @Test + public void lambdaWithThis() throws Exception { + String t = "basic.LambdaF"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals("aaaaaaa", r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + + r = runUnguarded(simpleClass, "run"); + assertEquals("a:a:a:", r.returnValue); + } + + @Test + public void lambdaWithNonPublicInnerInterface() throws Exception { + String t = "basic.LambdaG"; + TypeRegistry typeRegistry = getTypeRegistry("basic..*"); + + // Since Boo needs promoting to public, have to ensure it is directly loaded: + typeRegistry.addType(t+"$Boo", loadBytesForClass(t+"$Boo")); + + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(99, r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals(44, r.returnValue); + } + + @Test + public void multipleLambdasInOneMethod() throws Exception { + String t = "basic.LambdaH"; + TypeRegistry typeRegistry = getTypeRegistry("basic..*"); + + // Since Foo needs promoting to public, have to ensure it is directly loaded: + typeRegistry.addType(t+"$Foo", loadBytesForClass(t+"$Foo")); + + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(56, r.returnValue); + + rtype.loadNewVersion("002", rtype.bytesInitial); + r = runUnguarded(simpleClass, "run"); + assertEquals(56, r.returnValue); + } + + @Test + public void lambdaSignatureChange() throws Exception { + String t = "basic.LambdaI"; + TypeRegistry typeRegistry = getTypeRegistry("basic..*"); + + // Since Foo needs promoting to public, have to ensure it is directly loaded: + ReloadableType itype = typeRegistry.addType(t+"$Foo", loadBytesForClass(t+"$Foo")); + + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals("a", r.returnValue); + + itype.loadNewVersion("002", retrieveRename(t+"$Foo",t+"2$Foo")); + rtype.loadNewVersion("002", retrieveRename(t,t+"2",t+"2$Foo:"+t+"$Foo")); + + r = runUnguarded(simpleClass, "run"); + assertEquals("ab", r.returnValue); + } + + @Ignore + @Test + public void lambdaWithVirtualMethodUse() throws Exception { + // not yet written + } + + @Ignore + @Test + public void altMetaFactoryUsage() throws Exception { + // not yet written + } + + // TODO catchers and lambda methods (non static ones) + +} diff --git a/springloaded/src/test/java/org/springsource/loaded/test/JavaMicroBenchmarkTests.java b/springloaded/src/test/java/org/springsource/loaded/test/JavaMicroBenchmarkTests.java index 5a6ebbf..546c569 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/JavaMicroBenchmarkTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/JavaMicroBenchmarkTests.java @@ -53,14 +53,6 @@ public class JavaMicroBenchmarkTests extends SpringLoadedTests { average(rtype, 5); } - private void pause(int seconds) { - System.out.println("waiting..."); - try { - Thread.sleep(seconds * 1000); - } catch (Exception e) { - } - } - // TODO fibonacci private void average(ReloadableType rtype, int count) throws Exception { diff --git a/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java b/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java index 6608cb8..d0bc365 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java @@ -205,7 +205,7 @@ public class ReloadableTypeTests extends SpringLoadedTests { @Test public void invokeStaticReloading_gh4_2() throws Exception { TypeRegistry tr = getTypeRegistry("invokestatic..*"); - ReloadableType AA = tr.addType("invokestatic.issue4.AA", loadBytesForClass("invokestatic.issue4.AA")); + tr.addType("invokestatic.issue4.AA", loadBytesForClass("invokestatic.issue4.AA")); ReloadableType BB = tr.addType("invokestatic.issue4.BB", loadBytesForClass("invokestatic.issue4.BB")); Result r = runUnguarded(BB.getClazz(), "getMessage"); @@ -272,7 +272,7 @@ public class ReloadableTypeTests extends SpringLoadedTests { @Test public void invokeStaticReloading_gh4_6() throws Exception { TypeRegistry tr = getTypeRegistry("invokestatic.issue4..*"); - ReloadableType AB = tr.addType("invokestatic.issue4.AB", loadBytesForClass("invokestatic.issue4.AB")); + tr.addType("invokestatic.issue4.AB", loadBytesForClass("invokestatic.issue4.AB")); ReloadableType B = tr.addType("invokestatic.issue4.BBBBB", loadBytesForClass("invokestatic.issue4.BBBBB")); Result r = runUnguarded(B.getClazz(), "getMessage"); diff --git a/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java b/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java index 525580c..5324de6 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java @@ -236,6 +236,25 @@ public class ReloadingJVM { } Utils.write(new File(testdataDirectory,classfile),data); } + + public void clearTestdataDirectory() { + File[] fs = testdataDirectory.listFiles(); + for (File f: fs) { + delete(f); + } + } + + private void delete(File toDelete) { + if (toDelete.isDirectory()) { + File[] fs = toDelete.listFiles(); + for (File f: fs) { + delete(f); + } + } + else { + toDelete.delete(); + } + } public JVMOutput newInstance(String instanceName, String classname) { copyToTestdataDirectory(classname); diff --git a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java index 2cc8df1..e608925 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java @@ -47,12 +47,7 @@ import java.util.StringTokenizer; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; @@ -61,7 +56,6 @@ import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; import org.springsource.loaded.ClassRenamer; import org.springsource.loaded.Constants; -import org.springsource.loaded.GlobalConfiguration; import org.springsource.loaded.ISMgr; import org.springsource.loaded.MethodMember; import org.springsource.loaded.NameRegistry; @@ -92,12 +86,13 @@ public abstract class SpringLoadedTests implements Constants { protected ClassLoader binLoader; protected String TestDataPath = TestUtils.getPathToClasses("../testdata"); + protected String TestDataAspectJPath = TestUtils.getPathToClasses("../testdata-aspectj"); protected String GroovyTestDataPath = TestUtils.getPathToClasses("../testdata-groovy"); protected String AspectjrtJar = "../testdata/aspectjrt.jar"; protected String CodeJar = "../testdata/code.jar"; // TODO [java8] replace this with project dependency when Java8 is out protected String Java8CodeJar = "../testdata-java8/build/libs/testdata-java8.jar"; - protected String GroovyrtJar = "../testdata-groovy/groovy-1.8.2.jar"; + protected String GroovyrtJar = "../testdata-groovy/groovy-all-1.8.6.jar"; protected Result result; protected TypeRegistry registry; @@ -105,7 +100,7 @@ public abstract class SpringLoadedTests implements Constants { public void setup() throws Exception { SpringLoadedPreProcessor.disabled = true; NameRegistry.reset(); - binLoader = new TestClassLoader(toURLs(TestDataPath, AspectjrtJar, CodeJar, Java8CodeJar), this.getClass().getClassLoader()); + binLoader = new TestClassLoader(toURLs(TestDataPath, TestDataAspectJPath, AspectjrtJar, CodeJar, Java8CodeJar), this.getClass().getClassLoader()); } @After @@ -1260,5 +1255,16 @@ public abstract class SpringLoadedTests implements Constants { m.invoke(null); return captureOff(); } + + protected String slashed(String dotted) { + return dotted.replaceAll("\\.", "/"); + } + + protected final static void pause(int seconds) { + try { + Thread.sleep(seconds*1000); + } catch (Exception e) {} + } + } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java index bcecbd3..d653bd1 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java @@ -17,12 +17,15 @@ package org.springsource.loaded.test; import static org.junit.Assert.fail; +import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.springsource.loaded.test.ReloadingJVM.JVMOutput; +import sun.misc.Cleaner; + /** * These tests use a harness that forks a JVM with the agent attached, closely simulating a real environment. The * forked process is running a special class that can be sent commands. @@ -43,6 +46,12 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { public static void stopJVM() { jvm.shutdown(); } + + @After + public void teardown() throws Exception { + super.teardown(); + jvm.clearTestdataDirectory(); + } @Test public void testEcho() throws Exception { @@ -70,6 +79,7 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { @Test public void githubIssue34_2() throws Exception { jvm.copyToTestdataDirectory("issue34.InnerEnum$sorters"); + jvm.copyToTestdataDirectory("issue34.InnerEnum$MyComparator"); jvm.copyToTestdataDirectory("issue34.InnerEnum$sorters$1"); JVMOutput output = jvm.run("issue34.InnerEnum"); assertStdout("Hello World!\n", output); @@ -81,6 +91,9 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { assertStdout("jvmtwo.Runner.run1() running", jvm.call("a", "run1")); } + /* careful with this test - when the forked JVM takes a while the output can interfere with later tests, needs fixing up + * + */ @Ignore @Test public void reloadedPerformance() throws Exception { @@ -93,7 +106,7 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { pause(2); // In Perf2 the static method is gone, why does it give us a NSME? jo = jvm.call("a","time"); // 150ms - System.out.println(jo); + pause(5); } private final static void debug() { @@ -105,23 +118,14 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { jvm = ReloadingJVM.launch(options,true); } - private final static void pause(int seconds) { - try { - Thread.sleep(seconds*1000); - } catch (Exception e) {} - } - - @Test public void testReloadingInOtherVM() throws Exception { - jvm.newInstance("a", "remote.One"); - assertStdout("first", jvm.call("a", "run")); + jvm.newInstance("b", "remote.One"); + assertStdout("first", jvm.call("b", "run")); + pause(1); jvm.updateClass("remote.One",retrieveRename("remote.One","remote.One2")); - try { - Thread.sleep(2000); - } catch (Exception e) { - } - assertStdoutContains("second", jvm.call("a", "run")); + pause(2); + assertStdoutContains("second", jvm.call("b", "run")); } // TODO tidyup test data area after each test? // TODO flush/replace classloader in forked VM to clear it out after each test? @@ -137,11 +141,14 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { String subtype="foo.Controller"; jvm.copyToTestdataDirectory(supertype); jvm.copyToTestdataDirectory(subtype); - jvm.newInstance("a",subtype); - assertStdout("Top.foo() running\nController.foo() running\n", jvm.call("a", "foo")); + JVMOutput jo = jvm.newInstance("bb",subtype); + System.out.println(jo); + pause(1); + assertStdout("Top.foo() running\nController.foo() running\n", jvm.call("bb", "foo")); + pause(1); jvm.updateClass(subtype,retrieveRename(subtype,subtype+"2")); waitForReloadToOccur(); - JVMOutput jo = jvm.call("a", "foo"); + jo = jvm.call("bb", "foo"); assertStdoutContains("Top.foo() running\nController.foo() running again!\n", jo); } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/TestInfrastructureTests.java b/springloaded/src/test/java/org/springsource/loaded/test/TestInfrastructureTests.java index bea6154..403ac26 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/TestInfrastructureTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/TestInfrastructureTests.java @@ -47,7 +47,7 @@ public class TestInfrastructureTests extends SpringLoadedTests { TestClassLoader tcl = new TestClassLoader(toURLs(TestDataPath), this.getClass().getClassLoader()); byte[] classdata = Utils.loadDottedClassAsBytes(tcl, "data.SimpleClass"); Assert.assertNotNull(classdata); - Assert.assertEquals(394, classdata.length); + Assert.assertEquals(331, classdata.length); } } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/infra/FakeMethodVisitor.java b/springloaded/src/test/java/org/springsource/loaded/test/infra/FakeMethodVisitor.java index 3d0e2bc..8cc0258 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/infra/FakeMethodVisitor.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/infra/FakeMethodVisitor.java @@ -19,13 +19,18 @@ import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; +import org.springsource.loaded.Constants; import org.springsource.loaded.Utils; /** * MethodVisitor that records events - very useful for testing */ -public class FakeMethodVisitor implements MethodVisitor { +public class FakeMethodVisitor extends MethodVisitor implements Constants { + + public FakeMethodVisitor() { + super(ASM5); + } StringBuilder events = new StringBuilder(); diff --git a/springloaded/src/test/java/org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass.java b/springloaded/src/test/java/org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass.java index 4cef98a..9be7813 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/infra/RewriteReflectUtilsDefineClass.java @@ -15,10 +15,9 @@ */ package org.springsource.loaded.test.infra; -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.springsource.loaded.Constants; import org.springsource.loaded.ReloadableType; @@ -32,7 +31,7 @@ import org.springsource.loaded.TypeRegistry; * @author Andy Clement * @version 0.8.3 */ -public class RewriteReflectUtilsDefineClass extends ClassAdapter implements Constants { +public class RewriteReflectUtilsDefineClass extends ClassVisitor implements Constants { public static byte[] rewriteReflectUtilsDefineClass(byte[] data) { ClassReader cr = new ClassReader(data); @@ -43,7 +42,7 @@ public class RewriteReflectUtilsDefineClass extends ClassAdapter implements Cons } private RewriteReflectUtilsDefineClass() { - super(new ClassWriter(0)); // TODO review 0 here + super(ASM5,new ClassWriter(0)); // TODO review 0 here } public byte[] getBytes() { @@ -74,10 +73,10 @@ public class RewriteReflectUtilsDefineClass extends ClassAdapter implements Cons } } - class DefineClassInterceptor extends MethodAdapter implements Constants { + class DefineClassInterceptor extends MethodVisitor implements Constants { public DefineClassInterceptor(MethodVisitor mv) { - super(mv); + super(ASM5,mv); } @Override diff --git a/springloaded/src/test/java/org/springsource/loaded/test/infra/SubLoader.java b/springloaded/src/test/java/org/springsource/loaded/test/infra/SubLoader.java index d2bd283..ce04187 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/infra/SubLoader.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/infra/SubLoader.java @@ -47,7 +47,7 @@ public class SubLoader extends ClassLoader { TestUtils.getPathToClasses("../testdata-subloader") }; static String[] jars = new String[] { - "../testdata-groovy/groovy-1.8.2.jar" + "../testdata-groovy/groovy-all-1.8.6.jar" }; // @formatter:on @@ -140,8 +140,8 @@ public class SubLoader extends ClassLoader { c = defineClass(name, data, 0, data.length); break; } + zipfile.close(); } - // zipfile.close(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Problem defining class", e); diff --git a/springloaded/src/test/java/org/springsource/loaded/test/infra/SuperLoader.java b/springloaded/src/test/java/org/springsource/loaded/test/infra/SuperLoader.java index b2af228..992f95f 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/infra/SuperLoader.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/infra/SuperLoader.java @@ -45,12 +45,12 @@ public class SuperLoader extends ClassLoader { TestUtils.getPathToClasses("../testdata-superloader") }; static String[] jars = new String[] { - "../testdata-groovy/groovy-1.8.2.jar" + "../testdata-groovy/groovy-all-1.8.6.jar" }; // @formatter:on public SuperLoader() { - jars = new String[] { "../testdata-groovy/groovy-1.8.2.jar" }; + jars = new String[] { "../testdata-groovy/groovy-all-1.8.6.jar" }; } public SuperLoader(String... jars) { @@ -132,8 +132,8 @@ public class SuperLoader extends ClassLoader { c = defineClass(name, data, 0, data.length); break; } + zipfile.close(); } - // zipfile.close(); } catch (Exception e) { e.printStackTrace(); } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/infra/TestClassloaderWithRewriting.java b/springloaded/src/test/java/org/springsource/loaded/test/infra/TestClassloaderWithRewriting.java index 29a5a2b..ba9facb 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/infra/TestClassloaderWithRewriting.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/infra/TestClassloaderWithRewriting.java @@ -51,12 +51,12 @@ public class TestClassloaderWithRewriting extends ClassLoader { TestUtils.getPathToClasses("../testdata-groovy") }; static String[] jars = new String[] { - "../testdata-groovy/groovy-1.8.2.jar" + "../testdata-groovy/groovy-all-1.8.6.jar" }; // @formatter:on public TestClassloaderWithRewriting() { - jars = new String[] { "../testdata-groovy/groovy-1.8.2.jar" }; + jars = new String[] { "../testdata-groovy/groovy-all-1.8.6.jar" }; } public TestClassloaderWithRewriting(String metainfFolder) { @@ -64,7 +64,7 @@ public class TestClassloaderWithRewriting extends ClassLoader { newFolders[0] = folders[0]; newFolders[1] = "../testdata/" + metainfFolder; folders = newFolders; - jars = new String[] { "../testdata-groovy/groovy-1.8.2.jar" }; + jars = new String[] { "../testdata-groovy/groovy-all-1.8.6.jar" }; } public TestClassloaderWithRewriting(String metainfFolder, boolean b) { @@ -74,7 +74,7 @@ public class TestClassloaderWithRewriting extends ClassLoader { newFolders[2] = TestUtils.getPathToClasses("../testdata"); newFolders[3] = TestUtils.getPathToClasses("../testdata-plugin"); folders = newFolders; - jars = new String[] { "../testdata-groovy/groovy-1.8.2.jar" }; + jars = new String[] { "../testdata-groovy/groovy-all-1.8.6.jar" }; } public TestClassloaderWithRewriting(String metainfFolder, boolean b, boolean useRegistry, URLClassLoader classLoader) { @@ -85,7 +85,7 @@ public class TestClassloaderWithRewriting extends ClassLoader { newFolders[2] = TestUtils.getPathToClasses("../testdata"); folders = newFolders; this.useRegistry = useRegistry; - jars = new String[] { "../testdata-groovy/groovy-1.8.2.jar" }; + jars = new String[] { "../testdata-groovy/groovy-all-1.8.6.jar" }; } public TestClassloaderWithRewriting(boolean b, boolean useRegistry, boolean addCglib) { diff --git a/testdata-aspectj/.classpath b/testdata-aspectj/.classpath new file mode 100644 index 0000000..7ca2130 --- /dev/null +++ b/testdata-aspectj/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/testdata-aspectj/.project b/testdata-aspectj/.project new file mode 100644 index 0000000..9941b1e --- /dev/null +++ b/testdata-aspectj/.project @@ -0,0 +1,18 @@ + + + testdata-aspectj + + + + + + org.eclipse.ajdt.core.ajbuilder + + + + + + org.eclipse.ajdt.ui.ajnature + org.eclipse.jdt.core.javanature + + diff --git a/testdata-aspectj/build.gradle b/testdata-aspectj/build.gradle new file mode 100644 index 0000000..fa94bf2 --- /dev/null +++ b/testdata-aspectj/build.gradle @@ -0,0 +1,40 @@ +def aspectjVersion = "1.8.0.M1" + +configurations { + aspects + ajInpath +} + +dependencies { + tools "org.aspectj:aspectjtools:$aspectjVersion" + compile "org.aspectj:aspectjrt:$aspectjVersion" + compile("cglib:cglib:2.2.2") { exclude group: 'asm' } // cglib 2.2.2 depends on asm 3.3 + compile 'org.ow2.asm:asm:5.0_BETA' + compile 'org.ow2.asm:asm-tree:5.0_BETA' + compile files("code.jar") +} + +compileJava.deleteAllActions() + +task aspectJ(dependsOn: JavaPlugin.PROCESS_RESOURCES_TASK_NAME) { + dependsOn configurations.tools.getTaskDependencyFromProjectDependency(true, "compileJava") + def srcDirs = sourceSets.main.java.srcDirs + srcDirs.each { inputs.dir it } + def destDir = sourceSets.main.output.classesDir + outputs.dir destDir + doLast { + ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.tools.asPath) + + ant.iajc(source:sourceCompatibility, target:targetCompatibility, destDir: destDir.absolutePath, maxmem:"512m", fork:"true", + aspectPath: configurations.aspects.asPath, inpath:configurations.ajInpath.asPath, sourceRootCopyFilter:"**/.svn/*,**/*.java",classpath:configurations.compile.asPath ){ + sourceroots { + srcDirs.each { + if (it.exists()) pathelement location: it.absolutePath + } + } + } + } +} + +compileJava.dependsOn aspectJ + diff --git a/testdata/src/main/java/data/AnAspect.java b/testdata-aspectj/src/main/java/data/AnAspect.java similarity index 100% rename from testdata/src/main/java/data/AnAspect.java rename to testdata-aspectj/src/main/java/data/AnAspect.java diff --git a/testdata/src/main/java/data/AspectReceiver.java b/testdata-aspectj/src/main/java/data/AspectReceiver.java similarity index 100% rename from testdata/src/main/java/data/AspectReceiver.java rename to testdata-aspectj/src/main/java/data/AspectReceiver.java diff --git a/testdata-groovy/.classpath b/testdata-groovy/.classpath index b81d5bc..89435f1 100644 --- a/testdata-groovy/.classpath +++ b/testdata-groovy/.classpath @@ -3,5 +3,6 @@ + diff --git a/testdata-groovy/groovy-all-1.8.6.jar b/testdata-groovy/groovy-all-1.8.6.jar new file mode 100644 index 0000000..6dc0128 Binary files /dev/null and b/testdata-groovy/groovy-all-1.8.6.jar differ diff --git a/testdata-java8/.classpath b/testdata-java8/.classpath new file mode 100644 index 0000000..7461090 --- /dev/null +++ b/testdata-java8/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/testdata-java8/.gradle/1.7/taskArtifacts/cache.properties b/testdata-java8/.gradle/1.7/taskArtifacts/cache.properties new file mode 100644 index 0000000..be198ef --- /dev/null +++ b/testdata-java8/.gradle/1.7/taskArtifacts/cache.properties @@ -0,0 +1 @@ +#Thu Feb 06 13:01:33 PST 2014 diff --git a/testdata-java8/.gradle/1.7/taskArtifacts/cache.properties.lock b/testdata-java8/.gradle/1.7/taskArtifacts/cache.properties.lock new file mode 100644 index 0000000..40fdece --- /dev/null +++ b/testdata-java8/.gradle/1.7/taskArtifacts/cache.properties.lock @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/testdata-java8/.gradle/1.7/taskArtifacts/fileHashes.bin b/testdata-java8/.gradle/1.7/taskArtifacts/fileHashes.bin new file mode 100644 index 0000000..b6c8cc9 Binary files /dev/null and b/testdata-java8/.gradle/1.7/taskArtifacts/fileHashes.bin differ diff --git a/testdata-java8/.gradle/1.7/taskArtifacts/fileSnapshots.bin b/testdata-java8/.gradle/1.7/taskArtifacts/fileSnapshots.bin new file mode 100644 index 0000000..755b7b8 Binary files /dev/null and b/testdata-java8/.gradle/1.7/taskArtifacts/fileSnapshots.bin differ diff --git a/testdata-java8/.gradle/1.7/taskArtifacts/outputFileStates.bin b/testdata-java8/.gradle/1.7/taskArtifacts/outputFileStates.bin new file mode 100644 index 0000000..f8edce7 Binary files /dev/null and b/testdata-java8/.gradle/1.7/taskArtifacts/outputFileStates.bin differ diff --git a/testdata-java8/.gradle/1.7/taskArtifacts/taskArtifacts.bin b/testdata-java8/.gradle/1.7/taskArtifacts/taskArtifacts.bin new file mode 100644 index 0000000..4a4eb10 Binary files /dev/null and b/testdata-java8/.gradle/1.7/taskArtifacts/taskArtifacts.bin differ diff --git a/testdata-java8/.project b/testdata-java8/.project new file mode 100644 index 0000000..c51edec --- /dev/null +++ b/testdata-java8/.project @@ -0,0 +1,17 @@ + + + testdata-java8 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/testdata-java8/.settings/org.eclipse.jdt.core.prefs b/testdata-java8/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..7341ab1 --- /dev/null +++ b/testdata-java8/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/testdata-java8/build.gradle b/testdata-java8/build.gradle new file mode 100644 index 0000000..0c9263d --- /dev/null +++ b/testdata-java8/build.gradle @@ -0,0 +1,4 @@ + +apply plugin: 'java' +sourceCompatibility = 1.8 +targetCompatibility = 1.8 diff --git a/testdata-java8/build.sh b/testdata-java8/build.sh new file mode 100755 index 0000000..70a6758 --- /dev/null +++ b/testdata-java8/build.sh @@ -0,0 +1 @@ +../gradlew build diff --git a/testdata-java8/src/main/java/basic/FirstClass.class b/testdata-java8/src/main/java/basic/FirstClass.class new file mode 100644 index 0000000..7755e76 Binary files /dev/null and b/testdata-java8/src/main/java/basic/FirstClass.class differ diff --git a/testdata-java8/src/main/java/basic/FirstClass.java b/testdata-java8/src/main/java/basic/FirstClass.java new file mode 100644 index 0000000..846f4ea --- /dev/null +++ b/testdata-java8/src/main/java/basic/FirstClass.java @@ -0,0 +1,12 @@ +package basic; + +public class FirstClass { + + public static void main(String[] args) { + System.out.println("This is Java8"); + } + + public static int run() { + return 8; + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaA.java b/testdata-java8/src/main/java/basic/LambdaA.java new file mode 100644 index 0000000..b276dc6 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaA.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaA { + + public interface Foo { int m(); } + + public static void main(String[] args) { + run(); + } + + public static int run() { + Foo f = null; + f = () -> 77; + return f.m(); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaA2.java b/testdata-java8/src/main/java/basic/LambdaA2.java new file mode 100644 index 0000000..7e7de68 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaA2.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaA2 { + + public interface Foo { int m(); } + + public static void main(String[] args) { + run(); + } + + public static int run() { + Foo f = null; + f = () -> 88; + return f.m(); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaB.java b/testdata-java8/src/main/java/basic/LambdaB.java new file mode 100644 index 0000000..1388812 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaB.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaB { + + public interface Foo { long m(int i); } + + public static void main(String[] args) { + run(); + } + + public static long run() { + Foo f = null; + f = (i) -> i*33; + return f.m(3); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaB2.java b/testdata-java8/src/main/java/basic/LambdaB2.java new file mode 100644 index 0000000..ae90e8b --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaB2.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaB2 { + + public interface Foo { long m(int i); } + + public static void main(String[] args) { + run(); + } + + public static long run() { + Foo f = null; + f = (i) -> i*44; + return f.m(4); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaC.java b/testdata-java8/src/main/java/basic/LambdaC.java new file mode 100644 index 0000000..14d415e --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaC.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaC { + + public interface Boo { long m(int i,int j); } + + public static void main(String[] args) { + run(); + } + + public static long run() { + Boo f = null; + f = (i,j) -> i*j; + return f.m(3,2); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaC2.java b/testdata-java8/src/main/java/basic/LambdaC2.java new file mode 100644 index 0000000..c091501 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaC2.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaC2 { + + public interface Boo { long m(int i,int j); } + + public static void main(String[] args) { + run(); + } + + public static long run() { + Boo f = null; + f = (i,j) -> i+j; + return f.m(3,2); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaD.java b/testdata-java8/src/main/java/basic/LambdaD.java new file mode 100644 index 0000000..72451fc --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaD.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaD { + + public interface Boo { String m(int i,String s, int j, boolean b); } + + public static void main(String[] args) { + run(); + } + + public static String run() { + Boo f = null; + f = (i,j,k,l) -> ""+l+i+k+j; + return f.m(3,"abc",42,true); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaD2.java b/testdata-java8/src/main/java/basic/LambdaD2.java new file mode 100644 index 0000000..586a3f4 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaD2.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaD2 { + + public interface Boo { String m(int i,String s, int j, boolean b); } + + public static void main(String[] args) { + run(); + } + + public static String run() { + Boo f = null; + f = (i,j,k,l) -> j+(i*k)+l; + return f.m(3,"def",88,true); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaE.java b/testdata-java8/src/main/java/basic/LambdaE.java new file mode 100644 index 0000000..47e962a --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaE.java @@ -0,0 +1,23 @@ +package basic; + +public class LambdaE { + + public interface Boo { String m(char s); } + + public static void main(String[] args) { + run(); + } + + public static String run() { + int i = 4; + Boo f = null; + f = (c) -> { + StringBuilder buf = new StringBuilder(); + for (int j=0;j { + StringBuilder buf = new StringBuilder(); + for (int j=0;j { + return concatenator(c,fieldOne); + }; + return f.m('a'); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaF2.java b/testdata-java8/src/main/java/basic/LambdaF2.java new file mode 100644 index 0000000..9b6cf02 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaF2.java @@ -0,0 +1,34 @@ +package basic; + +public class LambdaF2 { + + public int fieldOne = 3; + + public String concatenator(char ch, int number) { + StringBuilder buf = new StringBuilder(); + for (int j=0;j { + return concatenator(c,fieldOne); + }; + return f.m('a'); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaG.java b/testdata-java8/src/main/java/basic/LambdaG.java new file mode 100644 index 0000000..a0885ed --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaG.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaG { + + interface Boo { int m(); } + + public static void main(String[] args) { + run(); + } + + public static int run() { + Boo f = null; + f = () -> 99; + return f.m(); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaG2.java b/testdata-java8/src/main/java/basic/LambdaG2.java new file mode 100644 index 0000000..4b2b2da --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaG2.java @@ -0,0 +1,16 @@ +package basic; + +public class LambdaG2 { + + interface Boo { int m(); } + + public static void main(String[] args) { + run(); + } + + public static int run() { + Boo f = null; + f = () -> 44; + return f.m(); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaH.java b/testdata-java8/src/main/java/basic/LambdaH.java new file mode 100644 index 0000000..3c12398 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaH.java @@ -0,0 +1,34 @@ +package basic; + +public class LambdaH { + + public interface Foo { int m(); } + + public int fieldOne = 7; + + public int concatenator(int a, int b) { + return a*b; + } + + + public static void main(String[] args) { + run(); + } + + public static int run() { + int count = 0; + count += ((Foo)()->7).m(); + count += new LambdaH().x(); + count += ((Foo)()->21).m(); + return count; + } + + public int x() { + int i = 4; + Foo f = null; + f = () -> { + return concatenator(i,fieldOne); + }; + return f.m(); + } +} diff --git a/testdata-java8/src/main/java/basic/LambdaI.java b/testdata-java8/src/main/java/basic/LambdaI.java new file mode 100644 index 0000000..bd3d128 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaI.java @@ -0,0 +1,17 @@ +package basic; + +public class LambdaI { + + public interface Foo { String m(String in); } + + + public static void main(String[] args) { + run(); + } + + public static String run() { + Foo f = (s) -> s; + return f.m("a"); + } + +} diff --git a/testdata-java8/src/main/java/basic/LambdaI2.java b/testdata-java8/src/main/java/basic/LambdaI2.java new file mode 100644 index 0000000..b22a255 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaI2.java @@ -0,0 +1,17 @@ +package basic; + +public class LambdaI2 { + + public interface Foo { String m(String in, String in2); } + + + public static void main(String[] args) { + run(); + } + + public static String run() { + Foo f = (s,t) -> s+t; + return f.m("a", "b"); + } + +} diff --git a/testdata/.classpath b/testdata/.classpath index a886ae5..0fb1905 100644 --- a/testdata/.classpath +++ b/testdata/.classpath @@ -1,11 +1,10 @@ - - + diff --git a/testdata/.project b/testdata/.project index e9d6a17..def9b8b 100644 --- a/testdata/.project +++ b/testdata/.project @@ -6,13 +6,12 @@ - org.eclipse.ajdt.core.ajbuilder + org.eclipse.jdt.core.javabuilder - org.eclipse.ajdt.ui.ajnature org.eclipse.jdt.core.javanature diff --git a/testdata/build.gradle b/testdata/build.gradle index b230c06..1c4ec5b 100644 --- a/testdata/build.gradle +++ b/testdata/build.gradle @@ -1,40 +1,17 @@ -def aspectjVersion = "1.7.1" - -configurations { - aspects - ajInpath -} - dependencies { +/* tools "org.aspectj:aspectjtools:$aspectjVersion" compile "org.aspectj:aspectjrt:$aspectjVersion" +*/ compile("cglib:cglib:2.2.2") { exclude group: 'asm' } // cglib 2.2.2 depends on asm 3.3 - compile 'asm:asm:3.2' - compile 'asm:asm-tree:3.2' + compile 'org.ow2.asm:asm:5.0_BETA' + compile 'org.ow2.asm:asm-tree:5.0_BETA' compile files("code.jar") } - -compileJava.deleteAllActions() - -task aspectJ(dependsOn: JavaPlugin.PROCESS_RESOURCES_TASK_NAME) { - dependsOn configurations.tools.getTaskDependencyFromProjectDependency(true, "compileJava") - def srcDirs = sourceSets.main.java.srcDirs - srcDirs.each { inputs.dir it } - def destDir = sourceSets.main.output.classesDir - outputs.dir destDir - doLast { - ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.tools.asPath) - - ant.iajc(source:sourceCompatibility, target:targetCompatibility, destDir: destDir.absolutePath, maxmem:"512m", fork:"true", - aspectPath: configurations.aspects.asPath, inpath:configurations.ajInpath.asPath, sourceRootCopyFilter:"**/.svn/*,**/*.java",classpath:configurations.compile.asPath ){ - sourceroots { - srcDirs.each { - if (it.exists()) pathelement location: it.absolutePath - } - } - } - } +sourceSets { + main { + java { + srcDir 'src' + } + } } - -compileJava.dependsOn aspectJ - diff --git a/testdata/src/main/java/issue34/InnerEnum.java b/testdata/src/main/java/issue34/InnerEnum.java index c745261..e7b7e26 100644 --- a/testdata/src/main/java/issue34/InnerEnum.java +++ b/testdata/src/main/java/issue34/InnerEnum.java @@ -13,10 +13,17 @@ public class InnerEnum { @SuppressWarnings("unused") public static void main(String[] args) { System.out.println("Hello World!"); - Map map = new TreeMap(sorters.string); + Object o = sorters.string; +// Map map = new TreeMap(sorters.string); + } + + // May be able to switch back to using Comparator (and the TreeMap line above) once AspectJ 1.8.0 is out + interface MyComparator { + int compare(T a,T b); + boolean equals(Object o); } - private static enum sorters implements Comparator { + private static enum sorters implements MyComparator { string { private static final long serialVersionUID = 1L; @@ -24,6 +31,7 @@ public class InnerEnum { public int compare(String o1, String o2) { return o1.compareTo(o2); } + } } } \ No newline at end of file diff --git a/testdata/src/main/java/reflection/generics/GenericClass002.java b/testdata/src/main/java/reflection/generics/GenericClass002.java index eb031b8..427527e 100644 --- a/testdata/src/main/java/reflection/generics/GenericClass002.java +++ b/testdata/src/main/java/reflection/generics/GenericClass002.java @@ -6,7 +6,7 @@ public class GenericClass002> implements GenericInterfac //what we need in this v002 class... - // Same as in original class, but also with methods added (fore these cases) + // Same as in original class, but also with methods added (for these cases) public Iterator iterator() { return null;