diff --git a/j8code/.classpath b/j8code/.classpath index 4ad1cf5..0fe6ce4 100644 --- a/j8code/.classpath +++ b/j8code/.classpath @@ -2,5 +2,6 @@ + diff --git a/j8code/.project b/j8code/.project index 2970a0c..835cd67 100644 --- a/j8code/.project +++ b/j8code/.project @@ -1,6 +1,6 @@ - j8code + springloaded-java8 diff --git a/j8code/lib/asm-5.0_BETA.jar b/j8code/lib/asm-5.0_BETA.jar new file mode 100644 index 0000000..2e26615 Binary files /dev/null and b/j8code/lib/asm-5.0_BETA.jar differ diff --git a/j8code/readme.txt b/j8code/readme.txt new file mode 100644 index 0000000..ea23a76 --- /dev/null +++ b/j8code/readme.txt @@ -0,0 +1,3 @@ +The main springloaded codebase is not yet using Java8. The Java8 'stuff' is captured here in this +project and included in the distributed agent. It should only be loaded if we find ourselves on a +Java8 VM. This code is not using Java8 source constructs, it is calling Java8 APIs. \ No newline at end of file diff --git a/j8code/src/org/springsource/loaded/support/Java8.java b/j8code/src/org/springsource/loaded/support/Java8.java index 4e3c942..028e481 100644 --- a/j8code/src/org/springsource/loaded/support/Java8.java +++ b/j8code/src/org/springsource/loaded/support/Java8.java @@ -1,4 +1,19 @@ -package j8code; +/* + * Copyright 2014 Pivotal Software Inc. and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springsource.loaded.support; import java.lang.invoke.CallSite; import java.lang.invoke.LambdaMetafactory; @@ -6,64 +21,122 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -public class J8Helper { +import org.objectweb.asm.Handle; +import org.objectweb.asm.Type; - public static Object simulateInvokeDynamic(Object lookup) { +/** + * This class encapsulates dependencies on Java 8 APIs (e.g. LambdaMetafactory). + * + * @author Andy Clement + * @since 1.2 + */ +public class Java8 { + + /** + * Notes: + * + * Useful to have an example of how this code behaves. Here is a bit of code: + * + * class basic.LambdaA { + * interface Foo { int m(); } + * static int run() { + * Foo f = null; + * f = () -> 77; + * return f.m(); + * } + * } + * + * Here is a bootstrap method entry in the constant pool: + * + * 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 + * + * At the invokedynamic site: + * bsmId = 0 + * nameAndDescriptor = m()Lbasic/LambdaA$Foo; + * + * When invoking the metafactory bootstrap method the first two parameters are stacked by the VM automatically, namely the MethodHandles$Lookup + * instance (caller) and the first String (invokedName). What the VM actually sees is this: + * + * metaFactory parameters: + * 0:MethodHandles$Lookup caller = basic.LambdaA + * 1:String invokedName = "m" + * 2:MethodType invokedType = "()Foo" + * 3:MethodType samMethodType = "()int" + * 4:MethodHandle implMethod = (actually a DirectMethodHandle where memberName is "basic.LambdaA.lambda$run$0()int/invokeStatic") + * 5:MethodType instantiatedMethodType = "()int" + * + * With all that information then the calls in this case are relatively straightforward: + * CallSite callsite = LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); + * callsite.dynamicInvoker().invokeWithArguments((Object[])null); + */ + + /** + * Programmatic emulation of INVOKEDYNAMIC so initialize the callsite via use of the bootstrap method then + * invoke the result. + * + * @param executorClass null if not yet reloaded + * @param handle + * @param bsmArgs + * @param lookup + * @return + */ + public static Object emulateInvokeDynamic(Class executorClass, Handle handle, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor, Object[] indyParams) { try { - CallSite callsite = callLambdaMetaFactory(lookup); - // java.lang.invoke.ConstantCallSite@1e965684 - // nameAndDescriptor at invokedynamic: m()Lbasic/LambdaA$Foo; - MethodHandles.Lookup caller = (MethodHandles.Lookup)lookup; - return callsite.dynamicInvoker().invokeWithArguments((Object[])null);//asType(MethodType.methodType())invoke(new Object[]{"m", MethodType.methodType(Class.forName("basic.LambdaA$Foo",false,caller.lookupClass().getClassLoader()))}); + CallSite callsite = callLambdaMetaFactory(bsmArgs,lookup,indyNameAndDescriptor,executorClass); + return callsite.dynamicInvoker().invokeWithArguments(indyParams); } catch (Throwable t) { throw new RuntimeException(t); } } - - - public static CallSite callLambdaMetaFactory(Object lookup) throws Exception { - // At invokedynamic: - // bsmId = 0 - // nameAndDescriptor = m()Lbasic/LambdaA$Foo; + // 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! -// 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 + // 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 { + MethodHandles.Lookup caller = (MethodHandles.Lookup)lookup; + + ClassLoader callerLoader = caller.lookupClass().getClassLoader(); + + int descriptorStart = indyNameAndDescriptor.indexOf('('); + String invokedName = indyNameAndDescriptor.substring(0,descriptorStart); + MethodType invokedType = MethodType.fromMethodDescriptorString(indyNameAndDescriptor.substring(descriptorStart), callerLoader); - // First two stacked by VM when used with invokedynamic + // Use bsmArgs to build the parameters + MethodType samMethodType = MethodType.fromMethodDescriptorString((String)(((Type)bsmArgs[0]).getDescriptor()), callerLoader); + + Handle bsmArgsHandle = (Handle)bsmArgs[1]; + String owner = bsmArgsHandle.getOwner(); + String name = bsmArgsHandle.getName(); + String descriptor = bsmArgsHandle.getDesc(); + MethodType implMethodType = MethodType.fromMethodDescriptorString(descriptor, callerLoader); + // Looking up the lambda$run method in the caller class (note the caller class is the executor, which gets us around the + // problem of having to hack into LambdaMetafactory to intercept reflection) + MethodHandle implMethod = null; + // TODO [lambda] need to handle invokevirtual, surely + switch (bsmArgsHandle.getTag()) { + case 6: // INVOKESSTATIC + implMethod = caller.findStatic(caller.lookupClass(), name, implMethodType); + break; + case 7: // 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()); + } + else { + implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString("(L"+owner+";"+descriptor.substring(1),callerLoader)); + } + break; + default: + throw new IllegalStateException("nyi "+bsmArgsHandle.getTag()); + } + + MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString((String)(((Type)bsmArgs[2]).getDescriptor()), callerLoader); -// 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 - - // caller = basic.LambdaA (MethodHandles$Lookup) - - // invokedName = m (String) - // invokedType = ()Foo (MethodType) - - // samMethodType=()int (MethodType) - // implMethod=MethodHandle()int (DirectMethodHandle - membername in this object is "basic.LambdaA.lambda$run$0()int/invokeStatic") - // form members is: -// -// DMH.invokeStatic__I=Lambda(a0:L)=>{ -// t1:L=DirectMethodHandle.internalMemberName(a0:L); -// t2:I=MethodHandle.linkToStatic(t1:L);t2:I} - // instantiatedMethodType=()int (MethodType) - MethodHandles.Lookup caller = (MethodHandles.Lookup)lookup; - MethodType invokedType = MethodType.methodType(Class.forName("basic.LambdaA$Foo",false,caller.lookupClass().getClassLoader())); - - MethodType samMethodType = MethodType.methodType(Integer.TYPE); - // Cheating here by changing first param to pretend the original type is looking for it rather than the executor - MethodHandle implMethod = caller.findStatic(caller.lookupClass(), "lambda$run$0",MethodType.methodType(Integer.TYPE)); - MethodType instantiatedMethodType = MethodType.methodType(Integer.TYPE); - return LambdaMetafactory.metafactory(caller, "m", invokedType, samMethodType, implMethod, instantiatedMethodType); + return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); } } diff --git a/springloaded/.classpath b/springloaded/.classpath index ea09e62..b5dde87 100644 --- a/springloaded/.classpath +++ b/springloaded/.classpath @@ -6,6 +6,6 @@ - + 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 dea5524..95aa5e8 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java @@ -1,8 +1,24 @@ +/* + * Copyright 2014 Pivotal Software Inc. and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springsource.loaded.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import org.junit.Ignore; import org.junit.Test; import org.springsource.loaded.ReloadableType; import org.springsource.loaded.TypeRegistry; @@ -13,7 +29,7 @@ import org.springsource.loaded.test.infra.Result; * Test reloading of Java 8. * * @author Andy Clement - * @since 1.1.5 + * @since 1.2 */ public class Java8Tests extends SpringLoadedTests { @@ -77,74 +93,172 @@ public class Java8Tests extends SpringLoadedTests { ReloadableType rtype = typeRegistry.addType(t, sc); Class simpleClass = rtype.getClazz(); - ClassPrinter.print(rtype.bytesLoaded); Result r = runUnguarded(simpleClass, "run"); r = runUnguarded(simpleClass, "run"); assertEquals(77, r.returnValue); - rtype.loadNewVersion("002", retrieveRename(t,t+"2")); + byte[] renamed = retrieveRename(t,t+"2",t+"2$Foo:"+t+"$Foo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals(88, r.returnValue); + } + + @Test + public void lambdaWithParameter() throws Exception { + String t = "basic.LambdaB"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(99L, r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Foo:"+t+"$Foo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals(176L, r.returnValue); + } + + + @Test + public void lambdaWithTwoParameters() throws Exception { + String t = "basic.LambdaC"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(6L, r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals(5L, r.returnValue); + } + + @Test + public void lambdaWithThreeMixedTypeParameters() throws Exception { + String t = "basic.LambdaD"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals("true342abc", r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals("def264true", r.returnValue); + } + + @Test + public void lambdaWithCapturedVariable() throws Exception { + String t = "basic.LambdaE"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals("aaaa", r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); + r = runUnguarded(simpleClass, "run"); + assertEquals("aaaaaaaa", r.returnValue); + } + + @Test + public void lambdaWithThis() throws Exception { + String t = "basic.LambdaF"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals("aaaaaaa", r.returnValue); + + byte[] renamed = retrieveRename(t,t+"2",t+"2$Boo:"+t+"$Boo"); + rtype.loadNewVersion("002", renamed); ClassPrinter.print(rtype.getLatestExecutorBytes()); r = runUnguarded(simpleClass, "run"); - assertEquals(77, r.returnValue); + assertEquals("a:a:a:", r.returnValue); } - // TODO changing a lambda body - // TODO changing a lambda signature - // TODO adding a lambda that wasn't there before - // TODO deleting a lambda - // TODO make that inner interface non-public in LambdaA - seems to break things. + @Test + public void lambdaWithNonPublicInnerInterface() throws Exception { + String t = "basic.LambdaG"; + TypeRegistry typeRegistry = getTypeRegistry("basic..*"); + + // Since Boo needs promoting to public, have to ensure it is directly loaded: + typeRegistry.addType(t+"$Boo", loadBytesForClass(t+"$Boo")); - // Bytecode for LambdaA - /* - BootstrapMethods: - 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 + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); - public static int run(); - descriptor: ()I - flags: ACC_PUBLIC, ACC_STATIC - Code: - stack=1, locals=1, args_size=0 - 0: aconst_null - 1: astore_0 - 2: invokedynamic #3, 0 // InvokeDynamic #0:m:()Lbasic/LambdaA$Foo; - 7: astore_0 - 8: aload_0 - 9: invokeinterface #4, 1 // InterfaceMethod basic/LambdaA$Foo.m:()I - 14: ireturn - */ - /* - static void test() throws Throwable { - // THE FOLLOWING LINE IS PSEUDOCODE FOR A JVM INSTRUCTION - InvokeDynamic[#bootstrapDynamic].baz("baz arg", 2, 3.14); - } - private static void printArgs(Object... args) { - System.out.println(java.util.Arrays.deepToString(args)); - } - private static final MethodHandle printArgs; - static { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - Class thisClass = lookup.lookupClass(); // (who am I?) - printArgs = lookup.findStatic(thisClass, - "printArgs", MethodType.methodType(void.class, Object[].class)); - } - private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) { - // ignore caller and name, but match the type: - return new ConstantCallSite(printArgs.asType(type)); - } - } -*/ + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + 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); + r = runUnguarded(simpleClass, "run"); + assertEquals(44, r.returnValue); + } + @Test + public void multipleLambdasInOneMethod() throws Exception { + String t = "basic.LambdaH"; + TypeRegistry typeRegistry = getTypeRegistry("basic..*"); + + // Since Foo needs promoting to public, have to ensure it is directly loaded: + typeRegistry.addType(t+"$Foo", loadBytesForClass(t+"$Foo")); - - // -- - - private String slashed(String dotted) { - return dotted.replaceAll("\\.", "/"); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class simpleClass = rtype.getClazz(); + Result r = runUnguarded(simpleClass, "run"); + + r = runUnguarded(simpleClass, "run"); + assertEquals(56, r.returnValue); + + rtype.loadNewVersion("002", rtype.bytesInitial); + r = runUnguarded(simpleClass, "run"); + assertEquals(56, r.returnValue); + } + + @Ignore + @Test + public void lambdaWithVirtualMethodUse() throws Exception { + } + + // 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? + + } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java index 9b14528..84e8a57 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java @@ -1261,6 +1261,10 @@ public abstract class SpringLoadedTests implements Constants { return captureOff(); } + protected String slashed(String dotted) { + return dotted.replaceAll("\\.", "/"); + } + protected final static void pause(int seconds) { try { Thread.sleep(seconds*1000); diff --git a/testdata-java8/build.sh b/testdata-java8/build.sh index 9157ee6..70a6758 100755 --- a/testdata-java8/build.sh +++ b/testdata-java8/build.sh @@ -1,2 +1 @@ -cd src/main/java -find . -name "*.java" | javac +../gradlew build diff --git a/testdata-java8/code.jar b/testdata-java8/code.jar deleted file mode 100644 index 36dd8e9..0000000 Binary files a/testdata-java8/code.jar and /dev/null differ