diff -r 000000000000 -r c880a8a8803b rt/emul/compact/src/main/java/java/lang/invoke/InnerClassLambdaMetafactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/invoke/InnerClassLambdaMetafactory.java Sat Aug 09 11:11:13 2014 +0200 @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.invoke; + +import jdk.internal.org.objectweb.asm.*; +import sun.invoke.util.BytecodeDescriptor; +import sun.misc.Unsafe; +import sun.security.action.GetPropertyAction; + +import java.io.FilePermission; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.LinkedHashSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.PropertyPermission; +import java.util.Set; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +/** + * Lambda metafactory implementation which dynamically creates an + * inner-class-like class per lambda callsite. + * + * @see LambdaMetafactory + */ +/* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final int CLASSFILE_VERSION = 52; + private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); + private static final String JAVA_LANG_OBJECT = "java/lang/Object"; + private static final String NAME_CTOR = ""; + private static final String NAME_FACTORY = "get$Lambda"; + + //Serialization support + private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda"; + private static final String NAME_NOT_SERIALIZABLE_EXCEPTION = "java/io/NotSerializableException"; + private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; + private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V"; + private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V"; + private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; + private static final String NAME_METHOD_READ_OBJECT = "readObject"; + private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; + private static final String DESCR_CTOR_SERIALIZED_LAMBDA + = MethodType.methodType(void.class, + Class.class, + String.class, String.class, String.class, + int.class, String.class, String.class, String.class, + String.class, + Object[].class).toMethodDescriptorString(); + private static final String DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION + = MethodType.methodType(void.class, String.class).toMethodDescriptorString(); + private static final String[] SER_HOSTILE_EXCEPTIONS = new String[] {NAME_NOT_SERIALIZABLE_EXCEPTION}; + + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + // Used to ensure that each spun class name is unique + private static final AtomicInteger counter = new AtomicInteger(0); + + // For dumping generated classes to disk, for debugging purposes + private static final ProxyClassesDumper dumper; + + static { + final String key = "jdk.internal.lambda.dumpProxyClasses"; + String path = AccessController.doPrivileged( + new GetPropertyAction(key), null, + new PropertyPermission(key , "read")); + dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path); + } + + // See context values in AbstractValidatingLambdaMetafactory + private final String implMethodClassName; // Name of type containing implementation "CC" + private final String implMethodName; // Name of implementation method "impl" + private final String implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" + private final Class implMethodReturnClass; // class for implementaion method return type "Ljava/lang/String;" + private final MethodType constructorType; // Generated class constructor type "(CC)void" + private final ClassWriter cw; // ASM class writer + private final String[] argNames; // Generated names for the constructor arguments + private final String[] argDescs; // Type descriptors for the constructor arguments + private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" + + /** + * General meta-factory constructor, supporting both standard cases and + * allowing for uncommon options such as serialization or bridging. + * + * @param caller Stacked automatically by VM; represents a lookup context + * with the accessibility privileges of the caller. + * @param invokedType Stacked automatically by VM; the signature of the + * invoked method, which includes the expected static + * type of the returned lambda object, and the static + * types of the captured arguments for the lambda. In + * the event that the implementation method is an + * instance method, the first argument in the invocation + * signature will correspond to the receiver. + * @param samMethodName Name of the method in the functional interface to + * which the lambda or method reference is being + * converted, represented as a String. + * @param samMethodType Type of the method in the functional interface to + * which the lambda or method reference is being + * converted, represented as a MethodType. + * @param implMethod The implementation method which should be called (with + * suitable adaptation of argument types, return types, + * and adjustment for captured arguments) when methods of + * the resulting functional interface instance are invoked. + * @param instantiatedMethodType The signature of the primary functional + * interface method after type variables are + * substituted with their instantiation from + * the capture site + * @param isSerializable Should the lambda be made serializable? If set, + * either the target type or one of the additional SAM + * types must extend {@code Serializable}. + * @param markerInterfaces Additional interfaces which the lambda object + * should implement. + * @param additionalBridges Method types for additional signatures to be + * bridged to the implementation method + * @throws LambdaConversionException If any of the meta-factory protocol + * invariants are violated + */ + public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, + MethodType invokedType, + String samMethodName, + MethodType samMethodType, + MethodHandle implMethod, + MethodType instantiatedMethodType, + boolean isSerializable, + Class[] markerInterfaces, + MethodType[] additionalBridges) + throws LambdaConversionException { + super(caller, invokedType, samMethodName, samMethodType, + implMethod, instantiatedMethodType, + isSerializable, markerInterfaces, additionalBridges); + implMethodClassName = implDefiningClass.getName().replace('.', '/'); + implMethodName = implInfo.getName(); + implMethodDesc = implMethodType.toMethodDescriptorString(); + implMethodReturnClass = (implKind == MethodHandleInfo.REF_newInvokeSpecial) + ? implDefiningClass + : implMethodType.returnType(); + constructorType = invokedType.changeReturnType(Void.TYPE); + lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet(); + cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + int parameterCount = invokedType.parameterCount(); + if (parameterCount > 0) { + argNames = new String[parameterCount]; + argDescs = new String[parameterCount]; + for (int i = 0; i < parameterCount; i++) { + argNames[i] = "arg$" + (i + 1); + argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i)); + } + } else { + argNames = argDescs = EMPTY_STRING_ARRAY; + } + } + + /** + * Build the CallSite. Generate a class file which implements the functional + * interface, define the class, if there are no parameters create an instance + * of the class which the CallSite will return, otherwise, generate handles + * which will call the class' constructor. + * + * @return a CallSite, which, when invoked, will return an instance of the + * functional interface + * @throws ReflectiveOperationException + * @throws LambdaConversionException If properly formed functional interface + * is not found + */ + @Override + CallSite buildCallSite() throws LambdaConversionException { + final Class innerClass = spinInnerClass(); + if (invokedType.parameterCount() == 0) { + final Constructor[] ctrs = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Constructor[] run() { + Constructor[] ctrs = innerClass.getDeclaredConstructors(); + if (ctrs.length == 1) { + // The lambda implementing inner class constructor is private, set + // it accessible (by us) before creating the constant sole instance + ctrs[0].setAccessible(true); + } + return ctrs; + } + }); + if (ctrs.length != 1) { + throw new LambdaConversionException("Expected one lambda constructor for " + + innerClass.getCanonicalName() + ", got " + ctrs.length); + } + + try { + Object inst = ctrs[0].newInstance(); + return new ConstantCallSite(MethodHandles.constant(samBase, inst)); + } + catch (ReflectiveOperationException e) { + throw new LambdaConversionException("Exception instantiating lambda object", e); + } + } else { + try { + UNSAFE.ensureClassInitialized(innerClass); + return new ConstantCallSite( + MethodHandles.Lookup.IMPL_LOOKUP + .findStatic(innerClass, NAME_FACTORY, invokedType)); + } + catch (ReflectiveOperationException e) { + throw new LambdaConversionException("Exception finding constructor", e); + } + } + } + + /** + * Generate a class file which implements the functional + * interface, define and return the class. + * + * @implNote The class that is generated does not include signature + * information for exceptions that may be present on the SAM method. + * This is to reduce classfile size, and is harmless as checked exceptions + * are erased anyway, no one will ever compile against this classfile, + * and we make no guarantees about the reflective properties of lambda + * objects. + * + * @return a Class which implements the functional interface + * @throws LambdaConversionException If properly formed functional interface + * is not found + */ + private Class spinInnerClass() throws LambdaConversionException { + String[] interfaces; + String samIntf = samBase.getName().replace('.', '/'); + boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase); + if (markerInterfaces.length == 0) { + interfaces = new String[]{samIntf}; + } else { + // Assure no duplicate interfaces (ClassFormatError) + Set itfs = new LinkedHashSet<>(markerInterfaces.length + 1); + itfs.add(samIntf); + for (Class markerInterface : markerInterfaces) { + itfs.add(markerInterface.getName().replace('.', '/')); + accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(markerInterface); + } + interfaces = itfs.toArray(new String[itfs.size()]); + } + + cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, + lambdaClassName, null, + JAVA_LANG_OBJECT, interfaces); + + // Generate final fields to be filled in by constructor + for (int i = 0; i < argDescs.length; i++) { + FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, + argNames[i], + argDescs[i], + null, null); + fv.visitEnd(); + } + + generateConstructor(); + + if (invokedType.parameterCount() != 0) { + generateFactory(); + } + + // Forward the SAM method + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, + samMethodType.toMethodDescriptorString(), null, null); + new ForwardingMethodGenerator(mv).generate(samMethodType); + + // Forward the bridges + if (additionalBridges != null) { + for (MethodType mt : additionalBridges) { + mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName, + mt.toMethodDescriptorString(), null, null); + new ForwardingMethodGenerator(mv).generate(mt); + } + } + + if (isSerializable) + generateSerializationFriendlyMethods(); + else if (accidentallySerializable) + generateSerializationHostileMethods(); + + cw.visitEnd(); + + // Define the generated class in this VM. + + final byte[] classBytes = cw.toByteArray(); + + // If requested, dump out to a file for debugging purposes + if (dumper != null) { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + dumper.dumpClass(lambdaClassName, classBytes); + return null; + } + }, null, + new FilePermission("<>", "read, write"), + // createDirectories may need it + new PropertyPermission("user.dir", "read")); + } + + return UNSAFE.defineAnonymousClass(targetClass, classBytes, null); + } + + /** + * Generate the factory method for the class + */ + private void generateFactory() { + MethodVisitor m = cw.visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_FACTORY, invokedType.toMethodDescriptorString(), null, null); + m.visitCode(); + m.visitTypeInsn(NEW, lambdaClassName); + m.visitInsn(Opcodes.DUP); + int parameterCount = invokedType.parameterCount(); + for (int typeIndex = 0, varIndex = 0; typeIndex < parameterCount; typeIndex++) { + Class argType = invokedType.parameterType(typeIndex); + m.visitVarInsn(getLoadOpcode(argType), varIndex); + varIndex += getParameterSize(argType); + } + m.visitMethodInsn(INVOKESPECIAL, lambdaClassName, NAME_CTOR, constructorType.toMethodDescriptorString()); + m.visitInsn(ARETURN); + m.visitMaxs(-1, -1); + m.visitEnd(); + } + + /** + * Generate the constructor for the class + */ + private void generateConstructor() { + // Generate constructor + MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR, + constructorType.toMethodDescriptorString(), null, null); + ctor.visitCode(); + ctor.visitVarInsn(ALOAD, 0); + ctor.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, NAME_CTOR, + METHOD_DESCRIPTOR_VOID); + int parameterCount = invokedType.parameterCount(); + for (int i = 0, lvIndex = 0; i < parameterCount; i++) { + ctor.visitVarInsn(ALOAD, 0); + Class argType = invokedType.parameterType(i); + ctor.visitVarInsn(getLoadOpcode(argType), lvIndex + 1); + lvIndex += getParameterSize(argType); + ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argDescs[i]); + } + ctor.visitInsn(RETURN); + // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored + ctor.visitMaxs(-1, -1); + ctor.visitEnd(); + } + + /** + * Generate a writeReplace method that supports serialization + */ + private void generateSerializationFriendlyMethods() { + TypeConvertingMethodAdapter mv + = new TypeConvertingMethodAdapter( + cw.visitMethod(ACC_PRIVATE + ACC_FINAL, + NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, + null, null)); + + mv.visitCode(); + mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA); + mv.visitInsn(DUP); + mv.visitLdcInsn(Type.getType(targetClass)); + mv.visitLdcInsn(invokedType.returnType().getName().replace('.', '/')); + mv.visitLdcInsn(samMethodName); + mv.visitLdcInsn(samMethodType.toMethodDescriptorString()); + mv.visitLdcInsn(implInfo.getReferenceKind()); + mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/')); + mv.visitLdcInsn(implInfo.getName()); + mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString()); + mv.visitLdcInsn(instantiatedMethodType.toMethodDescriptorString()); + mv.iconst(argDescs.length); + mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT); + for (int i = 0; i < argDescs.length; i++) { + mv.visitInsn(DUP); + mv.iconst(i); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); + mv.boxIfTypePrimitive(Type.getType(argDescs[i])); + mv.visitInsn(AASTORE); + } + mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR, + DESCR_CTOR_SERIALIZED_LAMBDA); + mv.visitInsn(ARETURN); + // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + /** + * Generate a readObject/writeObject method that is hostile to serialization + */ + private void generateSerializationHostileMethods() { + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, + NAME_METHOD_WRITE_OBJECT, DESCR_METHOD_WRITE_OBJECT, + null, SER_HOSTILE_EXCEPTIONS); + mv.visitCode(); + mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(DUP); + mv.visitLdcInsn("Non-serializable lambda"); + mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, + DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(ATHROW); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + + mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, + NAME_METHOD_READ_OBJECT, DESCR_METHOD_READ_OBJECT, + null, SER_HOSTILE_EXCEPTIONS); + mv.visitCode(); + mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(DUP); + mv.visitLdcInsn("Non-serializable lambda"); + mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, + DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION); + mv.visitInsn(ATHROW); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + /** + * This class generates a method body which calls the lambda implementation + * method, converting arguments, as needed. + */ + private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter { + + ForwardingMethodGenerator(MethodVisitor mv) { + super(mv); + } + + void generate(MethodType methodType) { + visitCode(); + + if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { + visitTypeInsn(NEW, implMethodClassName); + visitInsn(DUP); + } + for (int i = 0; i < argNames.length; i++) { + visitVarInsn(ALOAD, 0); + visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); + } + + convertArgumentTypes(methodType); + + // Invoke the method we want to forward to + visitMethodInsn(invocationOpcode(), implMethodClassName, + implMethodName, implMethodDesc, + implDefiningClass.isInterface()); + + // Convert the return value (if any) and return it + // Note: if adapting from non-void to void, the 'return' + // instruction will pop the unneeded result + Class samReturnClass = methodType.returnType(); + convertType(implMethodReturnClass, samReturnClass, samReturnClass); + visitInsn(getReturnOpcode(samReturnClass)); + // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored + visitMaxs(-1, -1); + visitEnd(); + } + + private void convertArgumentTypes(MethodType samType) { + int lvIndex = 0; + boolean samIncludesReceiver = implIsInstanceMethod && + invokedType.parameterCount() == 0; + int samReceiverLength = samIncludesReceiver ? 1 : 0; + if (samIncludesReceiver) { + // push receiver + Class rcvrType = samType.parameterType(0); + visitVarInsn(getLoadOpcode(rcvrType), lvIndex + 1); + lvIndex += getParameterSize(rcvrType); + convertType(rcvrType, implDefiningClass, instantiatedMethodType.parameterType(0)); + } + int samParametersLength = samType.parameterCount(); + int argOffset = implMethodType.parameterCount() - samParametersLength; + for (int i = samReceiverLength; i < samParametersLength; i++) { + Class argType = samType.parameterType(i); + visitVarInsn(getLoadOpcode(argType), lvIndex + 1); + lvIndex += getParameterSize(argType); + convertType(argType, implMethodType.parameterType(argOffset + i), instantiatedMethodType.parameterType(i)); + } + } + + private int invocationOpcode() throws InternalError { + switch (implKind) { + case MethodHandleInfo.REF_invokeStatic: + return INVOKESTATIC; + case MethodHandleInfo.REF_newInvokeSpecial: + return INVOKESPECIAL; + case MethodHandleInfo.REF_invokeVirtual: + return INVOKEVIRTUAL; + case MethodHandleInfo.REF_invokeInterface: + return INVOKEINTERFACE; + case MethodHandleInfo.REF_invokeSpecial: + return INVOKESPECIAL; + default: + throw new InternalError("Unexpected invocation kind: " + implKind); + } + } + } + + static int getParameterSize(Class c) { + if (c == Void.TYPE) { + return 0; + } else if (c == Long.TYPE || c == Double.TYPE) { + return 2; + } + return 1; + } + + static int getLoadOpcode(Class c) { + if(c == Void.TYPE) { + throw new InternalError("Unexpected void type of load opcode"); + } + return ILOAD + getOpcodeOffset(c); + } + + static int getReturnOpcode(Class c) { + if(c == Void.TYPE) { + return RETURN; + } + return IRETURN + getOpcodeOffset(c); + } + + private static int getOpcodeOffset(Class c) { + if (c.isPrimitive()) { + if (c == Long.TYPE) { + return 1; + } else if (c == Float.TYPE) { + return 2; + } else if (c == Double.TYPE) { + return 3; + } + return 0; + } else { + return 4; + } + } + +}