All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springsource.loaded.support.Java8 Maven / Gradle / Ivy

The newest version!
/*
 * 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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy