diff --git a/springloaded/notes.md b/springloaded/notes.md index b3aa627..a0aa06b 100644 --- a/springloaded/notes.md +++ b/springloaded/notes.md @@ -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); diff --git a/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java b/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java index 769f305..f942a2d 100644 --- a/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java +++ b/springloaded/src/main/java/org/springsource/loaded/GlobalConfiguration.java @@ -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; + } + } } diff --git a/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java index 8dc4bac..72f245e 100644 --- a/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java +++ b/springloaded/src/main/java/org/springsource/loaded/MethodInvokerRewriter.java @@ -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(); diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index f0d8c46..c417b9b 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -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)) { diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java index 25471a8..c7e75fc 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java @@ -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; } diff --git a/springloaded/src/main/java/org/springsource/loaded/support/Java8.java b/springloaded/src/main/java/org/springsource/loaded/support/Java8.java index 35e78f9..6b467c1 100644 --- a/springloaded/src/main/java/org/springsource/loaded/support/Java8.java +++ b/springloaded/src/main/java/org/springsource/loaded/support/Java8.java @@ -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; + } } 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 5736c9c..570a239 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/Java8Tests.java @@ -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 diff --git a/testdata-java8/src/main/java/basic/LambdaI.java b/testdata-java8/src/main/java/basic/LambdaI.java new file mode 100644 index 0000000..bd3d128 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaI.java @@ -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"); + } + +} diff --git a/testdata-java8/src/main/java/basic/LambdaI2.java b/testdata-java8/src/main/java/basic/LambdaI2.java new file mode 100644 index 0000000..b22a255 --- /dev/null +++ b/testdata-java8/src/main/java/basic/LambdaI2.java @@ -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"); + } + +}