Allow changing of lambda SAM types (alter method descriptor)

This is currently the 'slow' implementation and actually
slows down invokeinterface too - need to get the fast version
done...
This commit is contained in:
Andy Clement
2014-02-21 09:15:27 -08:00
parent e400e2da63
commit ad6a825688
9 changed files with 147 additions and 2 deletions

View File

@@ -2,6 +2,7 @@
Implementation details:
catchers
========
- A catcher is added to a reloadable type as it is being loaded for the first time. A reloadable type gets
a catcher for each method it inherits from a parent but does not override, or for an abstract class each
method it receives from an interface. They basically stand in for methods that could be added in later
@@ -12,12 +13,36 @@ private, final or static methods since those cannot be overridden. If you modif
type to make it non final and then override it, it will be handled in a different way than catchers.
superdispatchers
================
- Where a method is protected in a non-reloadable type it is necessary to add a superdispatcher method to the
subtype so that when the executor for a new version is running it can access that protected method. The
superdispatcher is simply a public method on a type that calls super.
Generic dispatcher method __execute
===================================
The way in which we handle new methods appearing on types is that all reloadable types get a generic handler method added to
them when first loaded - this can forward it on to the new method that has appeared. The method is like this:
__execute(Object[] params, Object target, String nameAndDescriptor)
All interfaces also get this method.
(#001) Lambdas introduce a problem here. The Lambda meta factory creates anonymous classes that forward to the lambda handling
method. It does this outside of our control using a more direct form of class defining which we don't see (can't instrument). This means
these generated classes don't get an __execute. This means if a new method is added to the SAM type, although we notice it
we can't generate an __execute in the meta factory created class. This means the standard redirection of INVOKEINTERFACE which says:
does this method exist on the original form of the type? yes, then call it. no, then call the __execute telling it what we'd like to run.
Well that will fail because of the missing __execute. There are two solutions:
- modify the InnerClassLambdaMetaFactory to ensure an __execute (and relevant marker interface) are added
- change how we handle the INVOKEINTERFACE rewrite.
The second option is cheap to implement but performance will likely suck. The simplest way to do it is call the type registry to do the
suitable invoke via reflection - and it will recognize the lambda case and know what to do.
----------
Helpful snippets when debugging tests:
ClassPrinter.print(z.getLatestExecutorBytes());
Utils.dump("foo/SubControllerB", rtype.bytesLoaded);

View File

@@ -362,4 +362,16 @@ public class GlobalConfiguration {
}
debugplugins = debugPlugins;
}
public final static boolean isJava18orHigher;
static {
String version = System.getProperty("java.version");
if (version.startsWith("1.8")) {
isJava18orHigher = true;
}
else {
isJava18orHigher = false;
}
}
}

View File

@@ -1236,8 +1236,15 @@ public class MethodInvokerRewriter {
mv.visitLdcInsn(name + desc); // [targetInstance paramArray targetInstance nameAndDescriptor]
// calling __execute(params array, this, name+desc)
mv.visitMethodInsn(INVOKEINTERFACE, owner, mDynamicDispatchName, mDynamicDispatchDescriptor);
if (GlobalConfiguration.isJava18orHigher) {
// if the target is a generated lambda callsite object then calling __execute isn't going to work as those
// types don't have the method in them!
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, "iiIntercept", "(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;");
}
else {
// calling __execute(params array, this, name+desc)
mv.visitMethodInsn(INVOKEINTERFACE, owner, mDynamicDispatchName, mDynamicDispatchDescriptor);
}
insertAppropriateReturn(returnType);
Label gotolabel = new Label();

View File

@@ -1259,6 +1259,33 @@ public class TypeRegistry {
return null; // let it fail anyway
}
/**
* See notes.md#001
*
*/
public static Object iiIntercept(Object instance, Object[] params, Object instance2, String nameAndDescriptor) {
Class<?> clazz= instance.getClass();
try {
if (clazz.getName().contains("$$Lambda")) {
// There will only be one method, the SAM method
Method[] ms = instance.getClass().getDeclaredMethods();
Method m = ms[0];
m.setAccessible(true);
Object o = m.invoke(instance, params);
return o;
}
else {
// Do what you were going to do...
Method m = instance.getClass().getDeclaredMethod("__execute",Object[].class,Object.class,String.class);
m.setAccessible(true);
return m.invoke(instance, params, instance, nameAndDescriptor);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@UsedByGeneratedCode
public static __DynamicallyDispatchable ispcheck(int ids, String nameAndDescriptor) {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {

View File

@@ -41,6 +41,7 @@ import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.Utils;
import org.springsource.loaded.SystemClassReflectionRewriter.RewriteResult;
import org.springsource.loaded.ri.ReflectiveInterceptor;
import org.springsource.loaded.support.Java8;
/**
* The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be dealt with in
@@ -148,6 +149,10 @@ public class SpringLoadedPreProcessor implements Constants {
// return rr.bytes;
// }
}
else if (slashedClassName.equals("java/lang/invoke/InnerClassLambdaMetafactory")) {
bytes = Java8.enhanceInnerClassLambdaMetaFactory(bytes);
return bytes;
}
}
return bytes;
}

View File

@@ -140,4 +140,15 @@ public class Java8 {
return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType);
}
/**
* The metafactory we are enhancing is responsible for generating the anonymous classes that will call the lambda methods in our type
*
* @param bytes
* @return
*/
public static byte[] enhanceInnerClassLambdaMetaFactory(byte[] bytes) {
// TODO Auto-generated method stub
return null;
}
}

View File

@@ -243,6 +243,30 @@ public class Java8Tests extends SpringLoadedTests {
r = runUnguarded(simpleClass, "run");
assertEquals(56, r.returnValue);
}
@Test
public void lambdaSignatureChange() throws Exception {
String t = "basic.LambdaI";
TypeRegistry typeRegistry = getTypeRegistry("basic..*");
// Since Foo needs promoting to public, have to ensure it is directly loaded:
ReloadableType itype = typeRegistry.addType(t+"$Foo", loadBytesForClass(t+"$Foo"));
byte[] sc = loadBytesForClass(t);
ReloadableType rtype = typeRegistry.addType(t, sc);
Class<?> simpleClass = rtype.getClazz();
Result r = runUnguarded(simpleClass, "run");
r = runUnguarded(simpleClass, "run");
assertEquals("a", r.returnValue);
itype.loadNewVersion("002", retrieveRename(t+"$Foo",t+"2$Foo"));
rtype.loadNewVersion("002", retrieveRename(t,t+"2",t+"2$Foo:"+t+"$Foo"));
r = runUnguarded(simpleClass, "run");
assertEquals("ab", r.returnValue);
}
@Ignore
@Test

View File

@@ -0,0 +1,17 @@
package basic;
public class LambdaI {
public interface Foo { String m(String in); }
public static void main(String[] args) {
run();
}
public static String run() {
Foo f = (s) -> s;
return f.m("a");
}
}

View File

@@ -0,0 +1,17 @@
package basic;
public class LambdaI2 {
public interface Foo { String m(String in, String in2); }
public static void main(String[] args) {
run();
}
public static String run() {
Foo f = (s,t) -> s+t;
return f.m("a", "b");
}
}