lambda reloading, invokeinterface support - fixes #82

This commit is contained in:
Andy Clement
2015-03-07 22:11:49 -08:00
parent ad81779bff
commit 1eb033f7e6
14 changed files with 292 additions and 5 deletions

View File

@@ -252,14 +252,17 @@ public class ClassRenamer {
String owner = oldHandle.getOwner();
String name = oldHandle.getName();
String desc = oldHandle.getDesc();
// System.out.println("handle: owner: "+owner);
// System.out.println("handle: name: "+name);
// System.out.println("handle: desc: "+desc);
owner = renameRetargetIfNecessary(owner);
desc = renameRetargetIfNecessary(desc);
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;,

View File

@@ -1571,7 +1571,7 @@ public class TypeRegistry {
// 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);
return Java8.emulateInvokeDynamic(rtype,rtype.getLatestExecutorClass(),bsmi.bsm,bsmi.bsmArgs,caller,nameAndDescriptor, indyParams);
}
/**

View File

@@ -20,10 +20,12 @@ import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.springsource.loaded.ReloadableType;
/**
* This class encapsulates dependencies on Java 8 APIs (e.g. LambdaMetafactory).
@@ -89,9 +91,9 @@ public class Java8 {
* @param indyParams parameters when the invokedynamic call is made
* @return the result of the invokedynamic call
*/
public static Object emulateInvokeDynamic(Class<?> executorClass, Handle handle, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor, Object[] indyParams) {
public static Object emulateInvokeDynamic(ReloadableType rtype, Class<?> executorClass, Handle handle, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor, Object[] indyParams) {
try {
CallSite callsite = callLambdaMetaFactory(bsmArgs,lookup,indyNameAndDescriptor,executorClass);
CallSite callsite = callLambdaMetaFactory(rtype, bsmArgs,lookup,indyNameAndDescriptor,executorClass);
return callsite.dynamicInvoker().invokeWithArguments(indyParams);
} catch (Throwable t) {
throw new RuntimeException(t);
@@ -100,7 +102,7 @@ public class Java8 {
// 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 {
public static CallSite callLambdaMetaFactory(ReloadableType rtype, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor,Class<?> executorClass) throws Exception {
MethodHandles.Lookup caller = (MethodHandles.Lookup)lookup;
ClassLoader callerLoader = caller.lookupClass().getClassLoader();
@@ -146,6 +148,15 @@ public class Java8 {
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString("(L"+owner+";"+descriptor.substring(1),callerLoader));
}
break;
case Opcodes.H_INVOKEINTERFACE:
Handle h = (Handle)bsmArgs[1];
String interfaceOwner = h.getOwner();
// TODO Should there not be a more direct way to this than classloading?
// TODO What about when this is a method added to the interface on a reload? It won't really exist, should we point
// to the executor? or something else? (maybe just directly the real method that will satisfy the interface - if it can be worked out)
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/','.')); // interface type, eg StreamB$Foo
implMethod = caller.findVirtual(interfaceClass, name, implMethodType);
break;
default:
throw new IllegalStateException("nyi "+bsmArgsHandle.getTag());
}

View File

@@ -22,6 +22,7 @@ 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;
/**
@@ -292,6 +293,79 @@ public class Java8Tests extends SpringLoadedTests {
r = runUnguarded(simpleClass, "run");
assertEquals("fooab", r.returnValue);
}
@Test
public void streamWithLambda() throws Exception {
String t = "basic.StreamA";
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass(t);
ReloadableType rtype = typeRegistry.addType(t, sc);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
assertEquals(3, r.returnValue);
byte[] renamed = retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo");
rtype.loadNewVersion("002", renamed);
r = runUnguarded(simpleClass, "run");
assertEquals(4, r.returnValue);
}
// inner interface (for the invokeinterface BSM)
@Test
public void streamWithLambdaInvokedVirtually() throws Exception {
String t = "basic.StreamB";
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass(t);
ReloadableType rtype = typeRegistry.addType(t, sc);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
assertEquals(3, r.returnValue);
byte[] renamed = retrieveRename(t, t + "2", t + "2$Foo:" + t + "$Foo");
rtype.loadNewVersion("002", renamed);
r = runUnguarded(simpleClass, "run");
assertEquals(4, r.returnValue);
}
// not an inner interface this time (for the invokeinterface BSM)
@Test
public void streamWithLambdaInvokedVirtually2() throws Exception {
String t = "basic.StreamBB";
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass(t);
ReloadableType rtype = typeRegistry.addType(t, sc);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
assertEquals(3, r.returnValue);
byte[] renamed = retrieveRename(t, t + "2");
rtype.loadNewVersion("002", renamed);
r = runUnguarded(simpleClass, "run");
assertEquals(4, r.returnValue);
}
@Test
public void streamWithoutLambda() throws Exception {
String t = "basic.StreamC";
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
byte[] sc = loadBytesForClass(t);
ReloadableType rtype = typeRegistry.addType(t, sc);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
assertEquals(3, r.returnValue);
byte[] renamed = retrieveRename(t, t + "2");
rtype.loadNewVersion("002", renamed);
r = runUnguarded(simpleClass, "run");
assertEquals(4, r.returnValue);
}
@Ignore
@Test

View File

@@ -0,0 +1,24 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamA {
public interface Foo {
int m(int n);
}
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
Foo foo = (n) -> n + 1;
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Integer> mapped = integers.stream().map(n -> foo.m(n)).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,24 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamA2 {
public interface Foo {
int m(int n);
}
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
Foo foo = (n) -> n + 1;
List<Integer> integers = Arrays.asList(1, 2, 3, 4); // Array is longer!
List<Integer> mapped = integers.stream().map(n -> foo.m(n)).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,24 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamB {
public interface Foo {
int m(int n);
}
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
Foo foo = (n) -> n + 1;
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Integer> mapped = integers.stream().map(foo::m).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,24 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamB2 {
public interface Foo {
int m(int n);
}
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
Foo foo = (n) -> n + 1;
List<Integer> integers = Arrays.asList(1, 2, 3, 4);
List<Integer> mapped = integers.stream().map(foo::m).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,20 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamBB {
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
StreamBBFoo foo = (n) -> n + 1;
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Integer> mapped = integers.stream().map(foo::m).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,24 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
//interface StreamBBFoo {
// int m(int n);
//}
public class StreamBB2 {
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
StreamBBFoo foo = (n) -> n + 1;
List<Integer> integers = Arrays.asList(1, 2, 3, 4);
List<Integer> mapped = integers.stream().map(foo::m).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,5 @@
package basic;
public interface StreamBBFoo {
int m(int n);
}

View File

@@ -0,0 +1,19 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamC {
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Integer> mapped = integers.stream().map(n -> n).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,19 @@
package basic;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamC2 {
public static void main(String[] args) {
System.out.println("This is Java8");
}
public static int run() {
List<Integer> integers = Arrays.asList(1, 2, 3, 4);
List<Integer> mapped = integers.stream().map(n -> n).collect(Collectors.toList());
return mapped.size();
}
}

View File

@@ -0,0 +1,16 @@
package basic;
public class Basic005 {
public static void foo() {
}
public int getValue() {
return 8;
}
public static int barstatic(String s) {
return Integer.valueOf(s);
}
}