Java8 support
This commit is contained in:
@@ -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 << {
|
||||
|
||||
@@ -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;i<os.length;i++) {
|
||||
if (i>0) 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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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<String,BsmInfo[]>();
|
||||
}
|
||||
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<String,BsmInfo[]> bsmmap;
|
||||
|
||||
static class BsmInfo {
|
||||
Handle bsm;
|
||||
|
||||
Reference in New Issue
Block a user