diff --git a/springloaded-java8/src/org/springsource/loaded/support/Java8.java b/springloaded-java8/src/org/springsource/loaded/support/Java8.java index 028e481..57ab1f2 100644 --- a/springloaded-java8/src/org/springsource/loaded/support/Java8.java +++ b/springloaded-java8/src/org/springsource/loaded/support/Java8.java @@ -22,6 +22,7 @@ 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; /** @@ -30,7 +31,7 @@ import org.objectweb.asm.Type; * @author Andy Clement * @since 1.2 */ -public class Java8 { +public class Java8 { /** * Notes: @@ -119,10 +120,10 @@ public class Java8 { MethodHandle implMethod = null; // TODO [lambda] need to handle invokevirtual, surely switch (bsmArgsHandle.getTag()) { - case 6: // INVOKESSTATIC + case Opcodes.H_INVOKESTATIC: implMethod = caller.findStatic(caller.lookupClass(), name, implMethodType); break; - case 7: // INVOKESPECIAL + 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()); diff --git a/springloaded/src/main/java/org/springsource/loaded/Constants.java b/springloaded/src/main/java/org/springsource/loaded/Constants.java index 22b49c1..22e6c25 100644 --- a/springloaded/src/main/java/org/springsource/loaded/Constants.java +++ b/springloaded/src/main/java/org/springsource/loaded/Constants.java @@ -79,6 +79,7 @@ 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"; diff --git a/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java index 04a02ea..53583ef 100644 --- a/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java +++ b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java @@ -954,6 +954,7 @@ public class MethodInvokerRewriter { 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("["); @@ -967,16 +968,37 @@ public class MethodInvokerRewriter { 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, org.objectweb.asm.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(); } - // TODO *shudder* what about invoke dynamic calls that target reflective APIs - boolean handled = false; - // TODO Perhaps (for sake of my sanity initially) make a distinction here between the general invokedynamic case and the special lambda support case? - + // 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, @@ -984,28 +1006,24 @@ public class MethodInvokerRewriter { // 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) { -// InvokeDynamic(name=m,desc=()Lbasic/LambdaA$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/LambdaA.lambda$run$0()I (6) ()I] - System.out.println("InvokeDynamic(name="+name+",desc="+desc+",bsm="+toString(bsm)+",bsmArgs="+toString(bsmArgs)); - // The other invokes use the 'owner' of the target method to determine which type registry should be part of this - // check. Here the 'owner' is wrapped up in the bootstrap method - as version 1 we can assume the owner is the lambdametafactory - // which *wont* be getting reloaded - so we already know we don't need to do some jiggery pokery. + 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. -// int classId = typeRegistry.getTypeIdFor(owner, true); - // Call type registry to determine 'can we do what we were going to do?' - - // Stack parameters at callsite into object array - // The name and descriptor (desc) show what the parameters are on the stack + // Check on reloading having happened + mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeDynamicName, "()Ljava/lang/Object;"); - if (desc.charAt(1)==')') { - // no params - mv.visitInsn(ACONST_NULL); - } - else { - Utils.collapseStackToArray(mv, desc); - } + // 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()); @@ -1014,75 +1032,15 @@ public class MethodInvokerRewriter { 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;"); - handled=true; - // TODO handle return type -// mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(),classId)); -// mv.visitLdcInsn(name+desc); -// mv.visitLdcInsn(BSM_NUMBER); -// mv.visitLdcInsn(bsmArgs); - - - -// // What can we check to see whether it is necessary to intercept this call? Is it the return type of the descriptor? (For when -// // the bsm is recognizable as for lambda support) -// mv.visitLdcInsn(Utils.toCombined(typeRegistry.getId(), classId)); -// mv.visitLdcInsn(name + desc); -// mv.visitMethodInsn(INVOKESTATIC, tRegistryType, mChangedForInvokeVirtualName, "(ILjava/lang/String;)Z"); -// // Return value is the extracted interface to call if there is a -// // change and it can't be called directly -// -// // 2. preserve a copy of the return value (new target) -// // mv.visitInsn(DUP); -// -// // 3. Was it null? -// Label l1 = new Label(); -// mv.visitJumpInsn(IFEQ, l1); -// -// // 4. Not false -// -// // 5. Store the target implementation of the interface that we -// // will invoke later -// // mv.visitVarInsn(ASTORE, max + 1); -// -// // 6. Package up any parameters -// if (hasParams) { -// Utils.collapseStackToArray(mv, desc); -// } -// -// // Prepare for the invocation: -// if (!hasParams) { -// // [targetInstance] -// mv.visitInsn(DUP); -// mv.visitInsn(ACONST_NULL); // no parameters -// mv.visitInsn(SWAP); // [targetInstance NULL targetInstance] -// } else { -// // [targetInstance paramArray] -// mv.visitInsn(SWAP); -// mv.visitInsn(DUP_X1); // [targetInstance paramArray -// // targetInstance] -// } -// -// mv.visitLdcInsn(name + desc); -// -// // calling __execute(params array,this,name+desc) -// mv.visitMethodInsn(INVOKEVIRTUAL, owner, mDynamicDispatchName, mDynamicDispatchDescriptor); -// -// insertAppropriateReturn(returnType); -// Label gotolabel = new Label(); -// mv.visitJumpInsn(GOTO, gotolabel); -// mv.visitLabel(l1); -// // mv.visitInsn(POP); -// // Here is where we end up if the test for changes failed (ie. -// // there were no changes - just 'do what you were going to do' -// super.visitMethodInsn(opcode, owner, name, desc); -// mv.visitLabel(gotolabel); + Label gotolabel = new Label(); + mv.visitJumpInsn(GOTO, gotolabel); + mv.visitLabel(nochange); + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + mv.visitLabel(gotolabel); } else { - // TODO handle it! - } - if (!handled) { - super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); } } diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index a0018e1..b7d4786 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -1118,6 +1118,21 @@ public class TypeRegistry { directlyDefineTypes = should; } + /** + *Used to determine if the invokedynamic needs to be intercepted. Currently very basic. + * + * @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 diff --git a/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java b/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java index 95aa5e8..5736c9c 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java @@ -22,7 +22,6 @@ import org.junit.Ignore; import org.junit.Test; import org.springsource.loaded.ReloadableType; import org.springsource.loaded.TypeRegistry; -import org.springsource.loaded.test.infra.ClassPrinter; import org.springsource.loaded.test.infra.Result; /** @@ -78,7 +77,6 @@ public class Java8Tests extends SpringLoadedTests { r = runUnguarded(simpleClass, "run"); assertEquals(77, r.returnValue); - ClassPrinter.print(rtype.bytesLoaded); rtype.loadNewVersion("002", rtype.bytesInitial); r = runUnguarded(simpleClass, "run"); @@ -196,7 +194,7 @@ public class Java8Tests extends SpringLoadedTests { byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); rtype.loadNewVersion("002", renamed); - ClassPrinter.print(rtype.getLatestExecutorBytes()); + r = runUnguarded(simpleClass, "run"); assertEquals("a:a:a:", r.returnValue); } @@ -217,7 +215,6 @@ public class Java8Tests extends SpringLoadedTests { r = runUnguarded(simpleClass, "run"); assertEquals(99, r.returnValue); - ClassPrinter.print(rtype.bytesLoaded); byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); rtype.loadNewVersion("002", renamed); @@ -250,15 +247,15 @@ public class Java8Tests extends SpringLoadedTests { @Ignore @Test public void lambdaWithVirtualMethodUse() throws Exception { - + // not yet written } - // TODO before commit - // copyrights - // tidyup up invokedynamic rewriting to only intercept metafactory usages - // decide about altmetafactory handling (marker interfaces on multicasts) - // Guard idyrun on whether anything reloaded - // Cache result of idyrun for reuse? + @Ignore + @Test + public void altMetaFactoryUsage() throws Exception { + // not yet written + } + // TODO catchers and lambda methods (non static ones) }