diff --git a/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java index 93a2ba2..bb1ee92 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java +++ b/springloaded/src/main/java/org/springsource/loaded/ClassRenamer.java @@ -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;, diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index 98d6213..e522cd8 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -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); } /** diff --git a/springloaded/src/main/java/org/springsource/loaded/support/Java8.java b/springloaded/src/main/java/org/springsource/loaded/support/Java8.java index 55590d9..7486bf4 100644 --- a/springloaded/src/main/java/org/springsource/loaded/support/Java8.java +++ b/springloaded/src/main/java/org/springsource/loaded/support/Java8.java @@ -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()); } 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 0640004..02f7c1d 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java @@ -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 diff --git a/testdata-java8/src/main/java/basic/StreamA.java b/testdata-java8/src/main/java/basic/StreamA.java new file mode 100644 index 0000000..21c07c1 --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamA.java @@ -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 integers = Arrays.asList(1, 2, 3); + List mapped = integers.stream().map(n -> foo.m(n)).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamA2.java b/testdata-java8/src/main/java/basic/StreamA2.java new file mode 100644 index 0000000..e3c8938 --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamA2.java @@ -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 integers = Arrays.asList(1, 2, 3, 4); // Array is longer! + List mapped = integers.stream().map(n -> foo.m(n)).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamB.java b/testdata-java8/src/main/java/basic/StreamB.java new file mode 100644 index 0000000..c8c1f69 --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamB.java @@ -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 integers = Arrays.asList(1, 2, 3); + List mapped = integers.stream().map(foo::m).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamB2.java b/testdata-java8/src/main/java/basic/StreamB2.java new file mode 100644 index 0000000..8a5dcec --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamB2.java @@ -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 integers = Arrays.asList(1, 2, 3, 4); + List mapped = integers.stream().map(foo::m).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamBB.java b/testdata-java8/src/main/java/basic/StreamBB.java new file mode 100644 index 0000000..cbbfa3a --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamBB.java @@ -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 integers = Arrays.asList(1, 2, 3); + List mapped = integers.stream().map(foo::m).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamBB2.java b/testdata-java8/src/main/java/basic/StreamBB2.java new file mode 100644 index 0000000..c0bba73 --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamBB2.java @@ -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 integers = Arrays.asList(1, 2, 3, 4); + List mapped = integers.stream().map(foo::m).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamBBFoo.java b/testdata-java8/src/main/java/basic/StreamBBFoo.java new file mode 100644 index 0000000..69d90fa --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamBBFoo.java @@ -0,0 +1,5 @@ +package basic; + +public interface StreamBBFoo { + int m(int n); +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamC.java b/testdata-java8/src/main/java/basic/StreamC.java new file mode 100644 index 0000000..4d01386 --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamC.java @@ -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 integers = Arrays.asList(1, 2, 3); + List mapped = integers.stream().map(n -> n).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata-java8/src/main/java/basic/StreamC2.java b/testdata-java8/src/main/java/basic/StreamC2.java new file mode 100644 index 0000000..8225f50 --- /dev/null +++ b/testdata-java8/src/main/java/basic/StreamC2.java @@ -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 integers = Arrays.asList(1, 2, 3, 4); + List mapped = integers.stream().map(n -> n).collect(Collectors.toList()); + return mapped.size(); + } + +} \ No newline at end of file diff --git a/testdata/src/main/java/basic/Basic005.java b/testdata/src/main/java/basic/Basic005.java new file mode 100644 index 0000000..958782e --- /dev/null +++ b/testdata/src/main/java/basic/Basic005.java @@ -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); + } +}