more Java8 fixes

This commit is contained in:
Andy Clement
2014-02-17 16:26:37 -08:00
parent 39be705aa5
commit 909fd20387
10 changed files with 305 additions and 111 deletions

View File

@@ -2,5 +2,6 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J180_b128"/>
<classpathentry kind="lib" path="lib/asm-5.0_BETA.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>j8code</name>
<name>springloaded-java8</name>
<comment></comment>
<projects>
</projects>

BIN
j8code/lib/asm-5.0_BETA.jar Normal file

Binary file not shown.

3
j8code/readme.txt Normal file
View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -6,6 +6,6 @@
<classpathentry kind="lib" path="lib/asm-tree-5.0_BETA.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry combineaccessrules="false" kind="src" path="/j8code"/>
<classpathentry combineaccessrules="false" kind="src" path="/springloaded-java8"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -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?
}

View File

@@ -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);

View File

@@ -1,2 +1 @@
cd src/main/java
find . -name "*.java" | javac
../gradlew build

Binary file not shown.