diff --git a/springloaded/build.gradle b/springloaded/build.gradle index 71e6990..40525f4 100644 --- a/springloaded/build.gradle +++ b/springloaded/build.gradle @@ -1,7 +1,8 @@ apply plugin: 'maven' group = "org.springsource.loaded" -version = '1.1.5.BUILD-SNAPSHOT' +//version = '1.1.5.RELEASE' +version = '1.2.0.BUILD-SNAPSHOT' jar.baseName = 'springloaded' task writeNewPom << { diff --git a/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java index 7d6102e..3673d8a 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java +++ b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java @@ -22,6 +22,7 @@ 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.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -223,6 +224,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, org.objectweb.asm.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/MethodInvokerRewriter.java b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java index b46d851..04a02ea 100644 --- a/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java +++ b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java @@ -670,6 +670,7 @@ public class MethodInvokerRewriter { 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? @@ -867,6 +868,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: @@ -967,6 +969,10 @@ public class MethodInvokerRewriter { @Override public void visitInvokeDynamicInsn(String name, String desc, org.objectweb.asm.Handle bsm, Object... bsmArgs) { + 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? @@ -988,12 +994,26 @@ public class MethodInvokerRewriter { // int classId = typeRegistry.getTypeIdFor(owner, true); // Call type registry to determine 'can we do what we were going to do?' - int bsmReferenceId = typeRegistry.recordBootstrapMethod(bsm,bsmArgs); + // Stack parameters at callsite into object array + // The name and descriptor (desc) show what the parameters are on the stack + + if (desc.charAt(1)==')') { + // no params + mv.visitInsn(ACONST_NULL); + } + else { + Utils.collapseStackToArray(mv, 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;Ljava/lang/String;I)Ljava/lang/Object;"); + 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)); diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index 9b51810..a0018e1 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -15,8 +15,6 @@ */ package org.springsource.loaded; -import j8code.J8Helper; - import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -50,6 +48,7 @@ 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 @@ -1464,21 +1463,13 @@ public class TypeRegistry { } @UsedByGeneratedCode - public static Object idyrun(Object methodHandles_lookup, String nameAndDescriptor, int bsmId) { - // bsmId = 0 - // nameAndDescriptor = m()Lbasic/LambdaA$Foo; - -// 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 - - - System.out.println("idyrun("+methodHandles_lookup+","+nameAndDescriptor+","+bsmId+")"); - return J8Helper.simulateInvokeDynamic(methodHandles_lookup); + 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); } /** @@ -1886,20 +1877,31 @@ public class TypeRegistry { * * @return id that represents this bootstrap method usage */ - public synchronized int recordBootstrapMethod(Handle bsm, Object[] bsmArgs) { - // TODO [memory] search the existing bsmInfos for a matching one! Reuse! - if (nextBsmIndex >= bsmInfo.length) { - BsmInfo[] newarray = new BsmInfo[bsmInfo.length+4]; - System.arraycopy(bsmInfo, 0, newarray, 0, bsmInfo.length); - bsmInfo = newarray; + public synchronized int recordBootstrapMethod(String slashedClassName, Handle bsm, Object[] bsmArgs) { + if (bsmmap == null) { + bsmmap = new HashMap(); } - bsmInfo[nextBsmIndex] = new BsmInfo(bsm,bsmArgs); - nextBsmIndex++; - return nextBsmIndex-1; + 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 int nextBsmIndex = 0; - private BsmInfo[] bsmInfo = new BsmInfo[4]; + private static Map bsmmap; static class BsmInfo { Handle bsm;