/* * 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; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.springsource.loaded.CurrentLiveVersion; import org.springsource.loaded.MethodMember; import org.springsource.loaded.ReloadableType; import org.springsource.loaded.TypeRegistry; /** * 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 the executor that will contain the lambda function, null if not yet reloaded * @param handle bootstrap method handle * @param bsmArgs bootstrap method arguments * @param lookup The MethodHandles.Lookup object that can be used to find types * @param indyNameAndDescriptor Method name and descriptor at invokedynamic site * @param indyParams parameters when the invokedynamic call is made * @return the result of the invokedynamic call */ public static Object emulateInvokeDynamic(ReloadableType rtype, Class<?> executorClass, Handle handle, Object[] bsmArgs, Object lookup, String indyNameAndDescriptor, Object[] indyParams) { try { CallSite callsite = callLambdaMetaFactory(rtype, bsmArgs, lookup, indyNameAndDescriptor, executorClass); return callsite.dynamicInvoker().invokeWithArguments(indyParams); } catch (Throwable t) { throw new RuntimeException(t); } } // 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! // TODO [lambda] Need to handle altMetaFactory which is used when the lambdas are more 'complex' (e.g. Serializable) public static CallSite callLambdaMetaFactory(ReloadableType rtype, 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); // Use bsmArgs to build the parameters MethodType samMethodType = MethodType.fromMethodDescriptorString( (((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; switch (bsmArgsHandle.getTag()) { case Opcodes.H_INVOKESTATIC: implMethod = caller.findStatic(caller.lookupClass(), name, implMethodType); break; case Opcodes.H_INVOKESPECIAL: // If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method // will be static with a new leading parameter. if (executorClass == null) { // TODO is final parameter here correct? 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; case Opcodes.H_INVOKEVIRTUAL: // There is a possibility to 'shortcut' here. Basically we are trying to resolve a callsite reference // to the method that satisfies it. The easiest option is to just find the method on the originally // loaded version of the target class and return that. A more optimal shortcut could return the // method on the executor class if the target has been reloaded (effectively bypassing the method // on the originally loaded version since we know that it will be acting as a pass through). But this // opens up a can of worms related to visibility. The executor is loaded into the child classloader, // and if the caller has not been reloaded it will not be able to 'see' the executor (since it is in // a child classloader). So, basically keep this dumb (but reliable) for now. TypeRegistry typeRegistry = rtype.getTypeRegistry(); ReloadableType ownerRType = typeRegistry.getReloadableType(owner); if (null == ownerRType || !ownerRType.hasBeenReloaded()) { // target containing the reference/lambdaMethod has not been reloaded, no need to get over // complicated. Class<?> clazz = callerLoader.loadClass(owner.replace("/", ".")); implMethod = caller.findVirtual(clazz, name, implMethodType); } else { MethodMember targetReferenceMethodMember = ownerRType.getCurrentMethod(name, descriptor); String targetReferenceDescriptor = targetReferenceMethodMember.getDescriptor(); MethodType targetReferenceMethodType = MethodType.fromMethodDescriptorString( targetReferenceDescriptor, callerLoader); Class<?> targetReferenceClass = ownerRType.getClazz(); MethodMember currentMethod = ownerRType.getCurrentMethod(name, descriptor); if (currentMethod.original == null) { // null means this method did not exist on the original version of the target. // Assert that the caller must have been reloaded, otherwise how would it // have a reference to something that did not exist on the first version of the type. In that // case we know we can return the method on the executor class because both the reloaded // caller and reloaded target are in the same child classloader (no visibility problem). if (!rtype.hasBeenReloaded()) { throw new IllegalStateException( "Assertion violated: When a method added on reload is being referenced" + "in target type '" + ownerRType.getName() + "', expected the caller to also have been reloaded: '" + rtype.getName() + "'"); } CurrentLiveVersion ownerLiveVersion = ownerRType.getLiveVersion(); Class<?> ownerExecutorClass = ownerLiveVersion.getExecutorClass(); Method executorMethod = ownerLiveVersion.getExecutorMethod(currentMethod); String methodDescriptor = Type.getType(executorMethod).getDescriptor(); MethodType type = MethodType.fromMethodDescriptorString(methodDescriptor, callerLoader); implMethod = caller.findStatic(ownerExecutorClass, name, type); } else { // This finds the reference method on the originally loaded class. It will pass through // to the actual code on the reloaded version. implMethod = caller.findVirtual(targetReferenceClass, name, targetReferenceMethodType); } } break; case Opcodes.H_NEWINVOKESPECIAL: Class<?> clazz = callerLoader.loadClass(owner.replace("/", ".")); implMethod = caller.findConstructor(clazz, implMethodType); break; case Opcodes.H_INVOKEINTERFACE: Handle h = (Handle) bsmArgs[1]; String interfaceOwner = h.getOwner(); // TODO Should there not be a more direct way to this than classloading? // TODO What about when this is a method added to the interface on a reload? It won't really exist, should we point // to the executor? or something else? (maybe just directly the real method that will satisfy the interface - if it can be worked out) Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo implMethod = caller.findVirtual(interfaceClass, name, implMethodType); break; default: throw new IllegalStateException("nyi " + bsmArgsHandle.getTag()); } MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString( (((Type) bsmArgs[2]).getDescriptor()), callerLoader); 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 the class bytes for the InnerClassLambdaMetaFactory that is going to be modified * @return the class bytes for the modified InnerClassLambdaMetaFactory */ public static byte[] enhanceInnerClassLambdaMetaFactory(byte[] bytes) { // TODO Auto-generated method stub return null; } }