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