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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
17
testdata-java8/src/main/java/basic/LambdaI.java
Normal file
17
testdata-java8/src/main/java/basic/LambdaI.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
17
testdata-java8/src/main/java/basic/LambdaI2.java
Normal file
17
testdata-java8/src/main/java/basic/LambdaI2.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user