From e8b256a63bfbb17dd7da9f75bb18a82b81c7773b Mon Sep 17 00:00:00 2001 From: rfield Date: Thu, 25 Oct 2012 17:34:24 -0700 Subject: [PATCH] 8000806: Implement runtime lambda metafactory Summary: Implement lambda invokedynamic bootstrap by generating at runtime an inner class that implements the functional interface Reviewed-by: twisti --- .../AbstractValidatingLambdaMetafactory.java | 376 ++++++++++++++++ .../invoke/InnerClassLambdaMetafactory.java | 402 ++++++++++++++++++ .../invoke/LambdaConversionException.java | 50 +++ .../java/lang/invoke/LambdaMetafactory.java | 178 ++++++++ .../java/lang/invoke/MagicLambdaImpl.java | 39 ++ .../invoke/TypeConvertingMethodAdapter.java | 267 ++++++++++++ 6 files changed, 1312 insertions(+) create mode 100644 src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java create mode 100644 src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java create mode 100644 src/share/classes/java/lang/invoke/LambdaConversionException.java create mode 100644 src/share/classes/java/lang/invoke/LambdaMetafactory.java create mode 100644 src/share/classes/java/lang/invoke/MagicLambdaImpl.java create mode 100644 src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java diff --git a/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java b/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java new file mode 100644 index 000000000..516443dc1 --- /dev/null +++ b/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2012, 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 java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import sun.invoke.util.Wrapper; +import static sun.invoke.util.Wrapper.*; + +/** + * Abstract implementation of a meta-factory which provides parameter unrolling and input validation. + * + * @author Robert Field + */ +/*non-public*/ abstract class AbstractValidatingLambdaMetafactory { + + /* + * For context, the comments for the following fields are marked in quotes with their values, given this program: + * interface II { Object foo(T x); } + * interface JJ extends II { } + * class CC { String impl(int i) { return "impl:"+i; }} + * class X { + * public static void main(String[] args) { + * JJ iii = (new CC())::impl; + * System.out.printf(">>> %s\n", iii.foo(44)); + * }} + */ + final Class targetClass; // The class calling the meta-factory via invokedynamic "class X" + final MethodType invokedType; // The type of the invoked method "(CC)II" + final Class samBase; // The type of the returned instance "interface JJ" + final boolean isSerializable; // Should the returned instance be serializable + final MethodHandleInfo samInfo; // Info about the SAM method handle "MethodHandleInfo[9 II.foo(Object)Object]" + final Class samClass; // Interface containing the SAM method "interface II" + final MethodType samMethodType; // Type of the SAM method "(Object)Object" + final MethodHandleInfo implInfo; // Info about the implementation method handle "MethodHandleInfo[5 CC.impl(int)String]" + final int implKind; // Invocation kind for implementation "5"=invokevirtual + final boolean implIsInstanceMethod; // Is the implementation an instance method "true" + final Class implDefiningClass; // Type defining the implementation "class CC" + final MethodType implMethodType; // Type of the implementation method "(int)String" + final MethodType instantiatedMethodType; // Instantiated erased functional interface method type "(Integer)Object" + + + /** + * Meta-factory constructor. + * + * @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 samMethod The primary method in the functional interface to which the lambda or method reference is + * being converted, represented as a method handle. + * @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 SAM method from the functional interface's perspective + * @throws ReflectiveOperationException + */ + AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller, + MethodType invokedType, + MethodHandle samMethod, + MethodHandle implMethod, + MethodType instantiatedMethodType) + throws ReflectiveOperationException { + this.targetClass = caller.lookupClass(); + this.invokedType = invokedType; + + this.samBase = invokedType.returnType(); + this.isSerializable = Serializable.class.isAssignableFrom(samBase); + + this.samInfo = new MethodHandleInfo(samMethod); + this.samClass = samInfo.getDeclaringClass(); + this.samMethodType = samInfo.getMethodType(); + + this.implInfo = new MethodHandleInfo(implMethod); + this.implKind = implInfo.getReferenceKind() == MethodHandleInfo.REF_invokeSpecial? MethodHandleInfo.REF_invokeVirtual : implInfo.getReferenceKind(); // @@@ Temp work-around to hotspot incorrectly converting to invokespecial + this.implIsInstanceMethod = + implKind == MethodHandleInfo.REF_invokeVirtual || + implKind == MethodHandleInfo.REF_invokeSpecial || + implKind == MethodHandleInfo.REF_invokeInterface; + this.implDefiningClass = implInfo.getDeclaringClass(); + this.implMethodType = implInfo.getMethodType(); + + this.instantiatedMethodType = instantiatedMethodType; + } + + /** + * Build the CallSite. + * + * @return a CallSite, which, when invoked, will return an instance of the + * functional interface + * @throws ReflectiveOperationException + */ + abstract CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException; + + /** + * Check the meta-factory arguments for errors + * @throws LambdaConversionException if there are improper conversions + */ + void validateMetafactoryArgs() throws LambdaConversionException { + // Check target type is a subtype of class where SAM method is defined + if (!samClass.isAssignableFrom(samBase)) { + throw new LambdaConversionException(String.format("Invalid target type %s for lambda conversion; not a subtype of functional interface %s", + samBase.getName(), samClass.getName())); + } + + switch (implKind) { + case MethodHandleInfo.REF_invokeInterface: + case MethodHandleInfo.REF_invokeVirtual: + case MethodHandleInfo.REF_invokeStatic: + case MethodHandleInfo.REF_newInvokeSpecial: + case MethodHandleInfo.REF_invokeSpecial: + break; + default: + throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", implInfo)); + } + + // Check arity: optional-receiver + captured + SAM == impl + final int implArity = implMethodType.parameterCount(); + final int receiverArity = implIsInstanceMethod ? 1 : 0; + final int capturedArity = invokedType.parameterCount(); + final int samArity = samMethodType.parameterCount(); + final int instantiatedArity = instantiatedMethodType.parameterCount(); + if (implArity + receiverArity != capturedArity + samArity) { + throw new LambdaConversionException(String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface parameters, %d implementation parameters", + implIsInstanceMethod ? "instance" : "static", implInfo, + capturedArity, samArity, implArity)); + } + if (instantiatedArity != samArity) { + throw new LambdaConversionException(String.format("Incorrect number of parameters for %s method %s; %d functional interface parameters, %d SAM method parameters", + implIsInstanceMethod ? "instance" : "static", implInfo, + instantiatedArity, samArity)); + } + + // If instance: first captured arg (receiver) must be subtype of class where impl method is defined + final int capturedStart; + final int samStart; + if (implIsInstanceMethod) { + final Class receiverClass; + + // implementation is an instance method, adjust for receiver in captured variables / SAM arguments + if (capturedArity == 0) { + // receiver is function parameter + capturedStart = 0; + samStart = 1; + receiverClass = instantiatedMethodType.parameterType(0); + } else { + // receiver is a captured variable + capturedStart = 1; + samStart = 0; + receiverClass = invokedType.parameterType(0); + } + + // check receiver type + if (!implDefiningClass.isAssignableFrom(receiverClass)) { + throw new LambdaConversionException(String.format("Invalid receiver type %s; not a subtype of implementation type %s", + receiverClass, implDefiningClass)); + } + } else { + // no receiver + capturedStart = 0; + samStart = 0; + } + + // Check for exact match on non-receiver captured arguments + final int implFromCaptured = capturedArity - capturedStart; + for (int i=0; i implParamType = implMethodType.parameterType(i); + Class capturedParamType = invokedType.parameterType(i + capturedStart); + if (!capturedParamType.equals(implParamType)) { + throw new LambdaConversionException( + String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s", i, capturedParamType, implParamType)); + } + } + // Check for adaptation match on SAM arguments + final int samOffset = samStart - implFromCaptured; + for (int i=implFromCaptured; i implParamType = implMethodType.parameterType(i); + Class instantiatedParamType = instantiatedMethodType.parameterType(i + samOffset); + if (!isAdaptableTo(instantiatedParamType, implParamType, true)) { + throw new LambdaConversionException( + String.format("Type mismatch for lambda argument %d: %s is not convertible to %s", i, instantiatedParamType, implParamType)); + } + } + + // Adaptation match: return type + Class expectedType = instantiatedMethodType.returnType(); + Class actualReturnType = + (implKind == MethodHandleInfo.REF_newInvokeSpecial) + ? implDefiningClass + : implMethodType.returnType(); + if (!isAdaptableToAsReturn(actualReturnType, expectedType)) { + throw new LambdaConversionException( + String.format("Type mismatch for lambda return: %s is not convertible to %s", actualReturnType, expectedType)); + } + } + + /** + * Check type adaptability + * @param fromType + * @param toType + * @param strict If true, do strict checks, else allow that fromType may be parameterized + * @return True if 'fromType' can be passed to an argument of 'toType' + */ + private boolean isAdaptableTo(Class fromType, Class toType, boolean strict) { + if (fromType.equals(toType)) { + return true; + } + if (fromType.isPrimitive()) { + Wrapper wfrom = forPrimitiveType(fromType); + if (toType.isPrimitive()) { + // both are primitive: widening + Wrapper wto = forPrimitiveType(toType); + return wto.isConvertibleFrom(wfrom); + } else { + // from primitive to reference: boxing + return toType.isAssignableFrom(wfrom.wrapperType()); + } + } else { + if (toType.isPrimitive()) { + // from reference to primitive: unboxing + Wrapper wfrom; + if (isWrapperType(fromType) && (wfrom = forWrapperType(fromType)).primitiveType().isPrimitive()) { + // fromType is a primitive wrapper; unbox+widen + Wrapper wto = forPrimitiveType(toType); + return wto.isConvertibleFrom(wfrom); + } else { + // must be convertible to primitive + return !strict; + } + } else { + // both are reference types: fromType should be a superclass of toType. + return strict? toType.isAssignableFrom(fromType) : true; + } + } + } + + /** + * Check type adaptability for return types -- special handling of void type) and parameterized fromType + * @param fromType + * @param toType + * @return True if 'fromType' can be converted to 'toType' + */ + private boolean isAdaptableToAsReturn(Class fromType, Class toType) { + return toType.equals(void.class) + || !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false); + } + + + /*********** Logging support -- for debugging only + static final Executor logPool = Executors.newSingleThreadExecutor(); // @@@ For debugging only + protected static void log(final String s) { + MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { + @Override + public void run() { + System.out.println(s); + } + }); + } + + protected static void log(final String s, final Throwable e) { + MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { + @Override + public void run() { + System.out.println(s); + e.printStackTrace(System.out); + } + }); + } + ***********************/ + + /** + * Find the SAM method and corresponding methods which should be bridged. SAM method and those to be bridged + * will have the same name and number of parameters. Check for matching default methods (non-abstract), they + * should not be bridged-over and indicate a complex bridging situation. + */ + class MethodAnalyzer { + private final Method[] methods = samBase.getMethods(); + private final List methodsFound = new ArrayList<>(methods.length); + + private Method samMethod = null; + private final List methodsToBridge = new ArrayList<>(methods.length); + private boolean defaultMethodFound = false; + + MethodAnalyzer() { + String samMethodName = samInfo.getName(); + Class[] samParamTypes = samMethodType.parameterArray(); + int samParamLength = samParamTypes.length; + Class samReturnType = samMethodType.returnType(); + Class objectClass = Object.class; + + for (Method m : methods) { + if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) { + Class[] mParamTypes = m.getParameterTypes(); + if (mParamTypes.length == samParamLength) { + if (Modifier.isAbstract(m.getModifiers())) { + // Exclude methods with duplicate signatures + if (methodUnique(m)) { + if (m.getReturnType().equals(samReturnType) && Arrays.equals(mParamTypes, samParamTypes)) { + // Exact match, this is the SAM method signature + samMethod = m; + } else { + methodsToBridge.add(m); + } + } + } else { + // This is a default method, flag for special processing + defaultMethodFound = true; + // Ignore future matching abstracts. + // Note, due to reabstraction, this is really a punt, hence pass-off to VM + methodUnique(m); + } + } + } + } + } + + Method getSamMethod() { + return samMethod; + } + + List getMethodsToBridge() { + return methodsToBridge; + } + + boolean wasDefaultMethodFound() { + return defaultMethodFound; + } + + /** + * Search the list of previously found methods to determine if there is a method with the same signature + * (return and parameter types) as the specified method. If it wasn't found before, add to the found list. + * + * @param m The method to match + * @return False if the method was found, True otherwise + */ + private boolean methodUnique(Method m) { + Class[] ptypes = m.getParameterTypes(); + Class rtype = m.getReturnType(); + for (Method md : methodsFound) { + if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) { + return false; + } + } + methodsFound.add(m); + return true; + } + } +} diff --git a/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java new file mode 100644 index 000000000..d41a62d39 --- /dev/null +++ b/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2012, 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 java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import jdk.internal.org.objectweb.asm.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import sun.misc.Unsafe; + +/** + * InnerClassLambdaMetafactory + */ +/*non-public*/ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { + private static final int CLASSFILE_VERSION = 51; + private static final Type TYPE_VOID = Type.getType(void.class); + private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); + private static final String NAME_MAGIC_ACCESSOR_IMPL = "java/lang/invoke/MagicLambdaImpl"; + private static final String NAME_SERIALIZABLE = "java/io/Serializable"; + private static final String NAME_CTOR = ""; + + //Serialization support + private static final String NAME_SERIALIZED_LAMBDA = "com/oracle/java/lang/invoke/SerializedLambdaImpl"; + private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; + private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; + private static final String NAME_OBJECT = "java/lang/Object"; + + // Used to ensure that each spun class name is unique + private static final AtomicInteger counter = new AtomicInteger(0); + + // 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 Type[] implMethodArgumentTypes; // ASM types for implementaion method parameters + private final Type implMethodReturnType; // ASM type for implementaion method return type "Ljava/lang/String;" + private final MethodType constructorType; // Generated class constructor type "(CC)void" + private final String constructorDesc; // Type descriptor for constructor "(LCC;)V" + private final ClassWriter cw; // ASM class writer + private final Type[] argTypes; // ASM types for the constructor arguments + private final String[] argNames; // Generated names for the constructor arguments + private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" + private final Type[] instantiatedArgumentTypes; // ASM types for the functional interface arguments + + /** + * Meta-factory constructor. + * + * @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 samMethod The primary method in the functional interface to which the lambda or method reference is + * being converted, represented as a method handle. + * @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 SAM method from the functional interface's perspective + * @throws ReflectiveOperationException + */ + public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, + MethodType invokedType, + MethodHandle samMethod, + MethodHandle implMethod, + MethodType instantiatedMethodType) + throws ReflectiveOperationException { + super(caller, invokedType, samMethod, implMethod, instantiatedMethodType); + implMethodClassName = implDefiningClass.getName().replace('.', '/'); + implMethodName = implInfo.getName(); + implMethodDesc = implMethodType.toMethodDescriptorString(); + Type implMethodAsmType = Type.getMethodType(implMethodDesc); + implMethodArgumentTypes = implMethodAsmType.getArgumentTypes(); + implMethodReturnType = implMethodAsmType.getReturnType(); + constructorType = invokedType.changeReturnType(Void.TYPE); + constructorDesc = constructorType.toMethodDescriptorString(); + lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet(); + cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + argTypes = Type.getArgumentTypes(constructorDesc); + argNames = new String[argTypes.length]; + for (int i = 0; i < argTypes.length; i++) { + argNames[i] = "arg$" + (i + 1); + } + instantiatedArgumentTypes = Type.getArgumentTypes(instantiatedMethodType.toMethodDescriptorString()); + + } + + /** + * 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 + */ + @Override + CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException { + final Class innerClass = spinInnerClass(); + if (invokedType.parameterCount() == 0) { + return new ConstantCallSite(MethodHandles.constant(samBase, innerClass.newInstance())); + } else { + return new ConstantCallSite( + MethodHandles.Lookup.IMPL_LOOKUP + .findConstructor(innerClass, constructorType) + .asType(constructorType.changeReturnType(samBase))); + } + } + + /** + * Generate a class file which implements the functional + * interface, define and return the class. + * + * @return a Class which implements the functional interface + */ + private Class spinInnerClass() throws LambdaConversionException { + String samName = samBase.getName().replace('.', '/'); + + cw.visit(CLASSFILE_VERSION, ACC_PUBLIC + ACC_SUPER, lambdaClassName, null, NAME_MAGIC_ACCESSOR_IMPL, + isSerializable ? new String[]{samName, NAME_SERIALIZABLE} : new String[]{samName}); + + // Generate final fields to be filled in by constructor + for (int i = 0; i < argTypes.length; i++) { + FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argTypes[i].getDescriptor(), null, null); + fv.visitEnd(); + } + + generateConstructor(); + + MethodAnalyzer ma = new MethodAnalyzer(); + + // Forward the SAM method + if (ma.getSamMethod() == null) { + throw new LambdaConversionException(String.format("SAM method not found: %s", samMethodType)); + } else { + generateForwardingMethod(ma.getSamMethod(), false); + } + + // Forward the bridges + // @@@ Once the VM can do fail-over, uncomment the default method test + if (!ma.getMethodsToBridge().isEmpty() /* && !ma.wasDefaultMethodFound() */) { + for (Method m : ma.getMethodsToBridge()) { + generateForwardingMethod(m, true); + } + } + + /***** Serialization not yet supported + if (isSerializable) { + String samMethodName = samInfo.getName(); + Type samType = Type.getType(samBase); + generateSerializationMethod(samType, samMethodName); + } + ******/ + + cw.visitEnd(); + + // Define the generated class in this VM. + + final byte[] classBytes = cw.toByteArray(); + + if (System.getProperty("debug.dump.generated") != null) { + System.out.printf("Loaded: %s (%d bytes) %n", lambdaClassName, classBytes.length); + try (FileOutputStream fos = new FileOutputStream(lambdaClassName.replace('/', '.') + ".class")) { + fos.write(classBytes); + } catch (IOException ex) { + Logger.getLogger(InnerClassLambdaMetafactory.class.getName()).log(Level.SEVERE, null, ex); + } + } + + ClassLoader loader = targetClass.getClassLoader(); + ProtectionDomain pd = (loader == null) ? null : targetClass.getProtectionDomain(); + return (Class) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length, loader, pd); + } + + /** + * Generate the constructor for the class + */ + private void generateConstructor() { + // Generate constructor + MethodVisitor ctor = cw.visitMethod(ACC_PUBLIC, NAME_CTOR, constructorDesc, null, null); + ctor.visitCode(); + ctor.visitVarInsn(ALOAD, 0); + ctor.visitMethodInsn(INVOKESPECIAL, NAME_MAGIC_ACCESSOR_IMPL, NAME_CTOR, METHOD_DESCRIPTOR_VOID); + int lvIndex = 0; + for (int i = 0; i < argTypes.length; i++) { + ctor.visitVarInsn(ALOAD, 0); + ctor.visitVarInsn(argTypes[i].getOpcode(ILOAD), lvIndex + 1); + lvIndex += argTypes[i].getSize(); + ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argTypes[i].getDescriptor()); + } + ctor.visitInsn(RETURN); + ctor.visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored + ctor.visitEnd(); + } + + /** + * Generate the serialization method (if needed) + */ + /****** This code is out of date -- known to be wrong -- and not currently used ****** + private void generateSerializationMethod(Type samType, String samMethodName) { + String samMethodDesc = samMethodType.toMethodDescriptorString(); + 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.dup(); + mv.visitLdcInsn(samType); + mv.visitLdcInsn(samMethodName); + mv.visitLdcInsn(samMethodDesc); + mv.visitLdcInsn(Type.getType(implDefiningClass)); + mv.visitLdcInsn(implMethodName); + mv.visitLdcInsn(implMethodDesc); + + mv.iconst(argTypes.length); + mv.visitTypeInsn(ANEWARRAY, NAME_OBJECT); + for (int i = 0; i < argTypes.length; i++) { + mv.dup(); + mv.iconst(i); + mv.visitVarInsn(ALOAD, 0); + mv.getfield(lambdaClassName, argNames[i], argTypes[i].getDescriptor()); + mv.boxIfPrimitive(argTypes[i]); + mv.visitInsn(AASTORE); + } + mv.invokespecial(NAME_SERIALIZED_LAMBDA, NAME_CTOR, + "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"); + mv.visitInsn(ARETURN); + mv.visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored + mv.visitEnd(); + } + ********/ + + /** + * Generate a method which calls the lambda implementation method, + * converting arguments, as needed. + * @param m The method whose signature should be generated + * @param isBridge True if this methods should be flagged as a bridge + */ + private void generateForwardingMethod(Method m, boolean isBridge) { + Class[] exceptionTypes = m.getExceptionTypes(); + String[] exceptionNames = new String[exceptionTypes.length]; + for (int i = 0; i < exceptionTypes.length; i++) { + exceptionNames[i] = exceptionTypes[i].getName().replace('.', '/'); + } + String methodDescriptor = Type.getMethodDescriptor(m); + int access = isBridge? ACC_PUBLIC | ACC_BRIDGE : ACC_PUBLIC; + MethodVisitor mv = cw.visitMethod(access, m.getName(), methodDescriptor, null, exceptionNames); + new ForwardingMethodGenerator(mv).generate(m); + } + + /** + * 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(Method m) throws InternalError { + visitCode(); + + if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { + visitTypeInsn(NEW, implMethodClassName); + dup(); + } + for (int i = 0; i < argTypes.length; i++) { + visitVarInsn(ALOAD, 0); + getfield(lambdaClassName, argNames[i], argTypes[i].getDescriptor()); + } + + convertArgumentTypes(Type.getArgumentTypes(m)); + + // Invoke the method we want to forward to + visitMethodInsn(invocationOpcode(), implMethodClassName, implMethodName, implMethodDesc); + + // 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 + Type samReturnType = Type.getReturnType(m); + convertType(implMethodReturnType, samReturnType, samReturnType); + areturn(samReturnType); + + visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored + visitEnd(); + } + + private void convertArgumentTypes(Type[] samArgumentTypes) { + int lvIndex = 0; + boolean samIncludesReceiver = implIsInstanceMethod && argTypes.length == 0; + int samReceiverLength = samIncludesReceiver ? 1 : 0; + if (samIncludesReceiver) { + // push receiver + Type rcvrType = samArgumentTypes[0]; + Type instantiatedRcvrType = instantiatedArgumentTypes[0]; + + load(lvIndex + 1, rcvrType); + lvIndex += rcvrType.getSize(); + convertType(rcvrType, Type.getType(implDefiningClass), instantiatedRcvrType); + } + int argOffset = implMethodArgumentTypes.length - samArgumentTypes.length; + for (int i = samReceiverLength; i < samArgumentTypes.length; i++) { + Type argType = samArgumentTypes[i]; + Type targetType = implMethodArgumentTypes[argOffset + i]; + Type instantiatedArgType = instantiatedArgumentTypes[i]; + + load(lvIndex + 1, argType); + lvIndex += argType.getSize(); + convertType(argType, targetType, instantiatedArgType); + } + } + + private void convertType(Type argType, Type targetType, Type functionalType) { + convertType(argType.getDescriptor(), targetType.getDescriptor(), functionalType.getDescriptor()); + } + + 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); + } + } + + /** + * The following methods are copied from + * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very + * small and fast Java bytecode manipulation framework. Copyright (c) + * 2000-2005 INRIA, France Telecom All rights reserved. + * + * Subclass with that (removing these methods) if that package/class is + * ever added to the JDK. + */ + private void iconst(final int cst) { + if (cst >= -1 && cst <= 5) { + mv.visitInsn(Opcodes.ICONST_0 + cst); + } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { + mv.visitIntInsn(Opcodes.BIPUSH, cst); + } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { + mv.visitIntInsn(Opcodes.SIPUSH, cst); + } else { + mv.visitLdcInsn(cst); + } + } + + private void load(final int var, final Type type) { + mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), var); + } + + private void dup() { + mv.visitInsn(Opcodes.DUP); + } + + private void areturn(final Type t) { + mv.visitInsn(t.getOpcode(Opcodes.IRETURN)); + } + + private void getfield( + final String owner, + final String name, + final String desc) { + mv.visitFieldInsn(Opcodes.GETFIELD, owner, name, desc); + } + } +} diff --git a/src/share/classes/java/lang/invoke/LambdaConversionException.java b/src/share/classes/java/lang/invoke/LambdaConversionException.java new file mode 100644 index 000000000..11ffb580e --- /dev/null +++ b/src/share/classes/java/lang/invoke/LambdaConversionException.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012, 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; + +/** + * LambdaConversionException + */ +public class LambdaConversionException extends Exception { + public LambdaConversionException() { + } + + public LambdaConversionException(String message) { + super(message); + } + + public LambdaConversionException(String message, Throwable cause) { + super(message, cause); + } + + public LambdaConversionException(Throwable cause) { + super(cause); + } + + public LambdaConversionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/share/classes/java/lang/invoke/LambdaMetafactory.java b/src/share/classes/java/lang/invoke/LambdaMetafactory.java new file mode 100644 index 000000000..378494b48 --- /dev/null +++ b/src/share/classes/java/lang/invoke/LambdaMetafactory.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2012, 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; + +/** + *

Bootstrap methods for converting lambda expressions and method references to functional interface objects.

+ * + *

For every lambda expressions or method reference in the source code, there is a target type which is a + * functional interface. Evaluating a lambda expression produces an object of its target type. The mechanism for + * evaluating lambda expressions is to invoke an invokedynamic call site, which takes arguments describing the sole + * method of the functional interface and the implementation method, and returns an object (the lambda object) that + * implements the target type. Methods of the lambda object invoke the implementation method. For method + * references, the implementation method is simply the referenced method; for lambda expressions, the + * implementation method is produced by the compiler based on the body of the lambda expression. The methods in + * this file are the bootstrap methods for those invokedynamic call sites, called lambda factories, and the + * bootstrap methods responsible for linking the lambda factories are called lambda meta-factories. + * + *

The bootstrap methods in this class take the information about the functional interface, the implementation + * method, and the static types of the captured lambda arguments, and link a call site which, when invoked, + * produces the lambda object. + * + *

Two pieces of information are needed about the functional interface: the SAM method and the type of the SAM + * method in the functional interface. The type can be different when parameterized types are used. For example, + * consider + * interface I<T> { int m(T x); } if this SAM type is used in a lambda + * I<Byte> v = ..., we need both the actual SAM method which has the signature + * (Object)int and the functional interface type of the method, which has signature + * (Byte)int. The latter is the instantiated erased functional interface method type, or + * simply instantiated method type. + * + *

While functional interfaces only have a single abstract method from the language perspective (concrete + * methods in Object are and default methods may be present), at the bytecode level they may actually have multiple + * methods because of the need for bridge methods. Invoking any of these methods on the lambda object will result + * in invoking the implementation method. + * + *

The argument list of the implementation method and the argument list of the functional interface method(s) + * may differ in several ways. The implementation methods may have additional arguments to accommodate arguments + * captured by the lambda expression; there may also be differences resulting from permitted adaptations of + * arguments, such as casting, boxing, unboxing, and primitive widening. They may also differ because of var-args, + * but this is expected to be handled by the compiler. + * + *

Invokedynamic call sites have two argument lists: a static argument list and a dynamic argument list. The + * static argument list lives in the constant pool; the dynamic argument list lives on the operand stack at + * invocation time. The bootstrap method has access to the entire static argument list (which in this case, + * contains method handles describing the implementation method and the canonical functional interface method), + * as well as a method signature describing the number and static types (but not the values) of the dynamic + * arguments, and the static return type of the invokedynamic site. + * + *

The implementation method is described with a method handle. In theory, any method handle could be used. + * Currently supported are method handles representing invocation of virtual, interface, constructor and static + * methods. + * + *

Assume: + *

    + *
  • the functional interface method has N arguments, of types (U1, U2, ... Un) and return type Ru
  • + *
  • then the instantiated method type also has N arguments, of types (T1, T2, ... Tn) and return type Rt
  • + *
  • the implementation method has M arguments, of types (A1..Am) and return type Ra,
  • + *
  • the dynamic argument list has K arguments of types (D1..Dk), and the invokedynamic return site has + * type Rd
  • + *
  • the functional interface type is F
  • + *
+ * + *

The following signature invariants must hold: + *

    + *
  • Rd is a subtype of F
  • + *
  • For i=1..N, Ti is a subtype of Ui
  • + *
  • Either Rt and Ru are primitive and are the same type, or both are reference types and + * Rt is a subtype of Ru
  • + *
  • If the implementation method is a static method: + *
      + *
    • K + N = M
    • + *
    • For i=1..K, Di = Ai
    • + *
    • For i=1..N, Ti is adaptable to Aj, where j=i+k
    • + *
  • + *
  • If the implementation method is an instance method: + *
      + *
    • K + N = M + 1
    • + *
    • D1 must be a subtype of the enclosing class for the implementation method
    • + *
    • For i=2..K, Di = Aj, where j=i-1
    • + *
    • For i=1..N, Ti is adaptable to Aj, where j=i+k-1
    • + *
  • + *
  • The return type Rt is void, or the return type Ra is not void and is adaptable to Rt
  • + *
+ * + *

Note that the potentially parameterized implementation return type provides the value for the SAM. Whereas + * the completely known instantiated return type is adapted to the implementation arguments. Because the + * instantiated type of the implementation method is not available, the adaptability of return types cannot be + * checked as precisely at link-time as the arguments can be checked. Thus a loose version of link-time checking is + * done on return type, while a strict version is applied to arguments. + * + *

A type Q is considered adaptable to S as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
QSLink-time checksCapture-time checks
PrimitivePrimitiveQ can be converted to S via a primitive widening conversionNone
PrimitiveReferenceS is a supertype of the Wrapper(Q)Cast from Wrapper(Q) to S
ReferencePrimitivestrict: Q is a primitive wrapper and Primitive(Q) can be widened to S + *
loose: If Q is a primitive wrapper, check that Primitive(Q) can be widened to S
If Q is not a primitive wrapper, cast Q to the base Wrapper(S); for example Number for numeric types
ReferenceReferencestrict: S is a supertype of Q + *
loose: none
Cast from Q to S
+ * + * + */ +public class LambdaMetafactory { + + /** + * Standard meta-factory for conversion of lambda expressions or method references to functional interfaces. + * + * @param caller Stacked automatically by VM; represents a lookup context with the accessibility privileges + * of the caller. + * @param invokedName Stacked automatically by VM; the name of the invoked method as it appears at the call site. + * Currently unused. + * @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 samMethod The primary method in the functional interface to which the lambda or method reference is + * being converted, represented as a method handle. + * @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 SAM method from the functional interface's perspective + * @return a CallSite, which, when invoked, will return an instance of the functional interface + * @throws ReflectiveOperationException + * @throws LambdaConversionException If any of the meta-factory protocol invariants are violated + */ + public static CallSite metaFactory(MethodHandles.Lookup caller, + String invokedName, + MethodType invokedType, + MethodHandle samMethod, + MethodHandle implMethod, + MethodType instantiatedMethodType) + throws ReflectiveOperationException, LambdaConversionException { + AbstractValidatingLambdaMetafactory mf; + mf = new InnerClassLambdaMetafactory(caller, invokedType, samMethod, implMethod, instantiatedMethodType); + mf.validateMetafactoryArgs(); + return mf.buildCallSite(); + } +} diff --git a/src/share/classes/java/lang/invoke/MagicLambdaImpl.java b/src/share/classes/java/lang/invoke/MagicLambdaImpl.java new file mode 100644 index 000000000..b78588bf8 --- /dev/null +++ b/src/share/classes/java/lang/invoke/MagicLambdaImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012, 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; + +/**

MagicLambdaImpl (named for similarity to MagicAccessorImpl and + others, not because it actually implements an interface) is a + marker class in the hierarchy. All subclasses of this class are + "magically" granted access by the VM to otherwise inaccessible + fields and methods of other classes. It is distinct from MagicAccessorImpl + because, while we want to bypass accessibility checks, we do not want to + bypass verification.

+ +

Do not change the name of this class without also changing the + VM's code.

*/ + +class MagicLambdaImpl { +} diff --git a/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java b/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java new file mode 100644 index 000000000..7384bee46 --- /dev/null +++ b/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2012, 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.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import sun.invoke.util.Wrapper; +import static sun.invoke.util.Wrapper.*; + +class TypeConvertingMethodAdapter extends MethodVisitor { + + TypeConvertingMethodAdapter(MethodVisitor mv) { + super(Opcodes.ASM4, mv); + } + + private static final int NUM_WRAPPERS = Wrapper.values().length; + + private static final String NAME_OBJECT = "java/lang/Object"; + private static final String WRAPPER_PREFIX = "Ljava/lang/"; + + // Same for all primitives; name of the boxing method + private static final String NAME_BOX_METHOD = "valueOf"; + + // Table of opcodes for widening primitive conversions; NOP = no conversion + private static final int[][] wideningOpcodes = new int[NUM_WRAPPERS][NUM_WRAPPERS]; + + private static final Wrapper[] FROM_WRAPPER_NAME = new Wrapper[16]; + + static { + for (Wrapper w : Wrapper.values()) { + if (w.basicTypeChar() != 'L') { + int wi = hashWrapperName(w.wrapperSimpleName()); + assert (FROM_WRAPPER_NAME[wi] == null); + FROM_WRAPPER_NAME[wi] = w; + } + } + + for (int i = 0; i < NUM_WRAPPERS; i++) { + for (int j = 0; j < NUM_WRAPPERS; j++) { + wideningOpcodes[i][j] = Opcodes.NOP; + } + } + + initWidening(LONG, Opcodes.I2L, BYTE, SHORT, INT, CHAR); + initWidening(LONG, Opcodes.F2L, FLOAT); + initWidening(FLOAT, Opcodes.I2F, BYTE, SHORT, INT, CHAR); + initWidening(FLOAT, Opcodes.L2F, LONG); + initWidening(DOUBLE, Opcodes.I2D, BYTE, SHORT, INT, CHAR); + initWidening(DOUBLE, Opcodes.F2D, FLOAT); + initWidening(DOUBLE, Opcodes.L2D, LONG); + } + + private static void initWidening(Wrapper to, int opcode, Wrapper... from) { + for (Wrapper f : from) { + wideningOpcodes[f.ordinal()][to.ordinal()] = opcode; + } + } + + /** + * Class name to Wrapper hash, derived from Wrapper.hashWrap() + * @param xn + * @return The hash code 0-15 + */ + private static int hashWrapperName(String xn) { + if (xn.length() < 3) { + return 0; + } + return (3 * xn.charAt(1) + xn.charAt(2)) % 16; + } + + private Wrapper wrapperOrNullFromDescriptor(String desc) { + if (!desc.startsWith(WRAPPER_PREFIX)) { + // Not a class type (array or method), so not a boxed type + // or not in the right package + return null; + } + // Pare it down to the simple class name + String cname = desc.substring(WRAPPER_PREFIX.length(), desc.length() - 1); + // Hash to a Wrapper + Wrapper w = FROM_WRAPPER_NAME[hashWrapperName(cname)]; + if (w == null || w.wrapperSimpleName().equals(cname)) { + return w; + } else { + return null; + } + } + + private static String wrapperName(Wrapper w) { + return "java/lang/" + w.wrapperSimpleName(); + } + + private static String unboxMethod(Wrapper w) { + return w.primitiveSimpleName() + "Value"; + } + + private static String boxingDescriptor(Wrapper w) { + return String.format("(%s)L%s;", w.basicTypeChar(), wrapperName(w)); + } + + private static String unboxingDescriptor(Wrapper w) { + return "()" + w.basicTypeChar(); + } + + void boxIfPrimitive(Wrapper w) { + if (w.zero() != null) { + box(w); + } + } + + void widen(Wrapper ws, Wrapper wt) { + if (ws != wt) { + int opcode = wideningOpcodes[ws.ordinal()][wt.ordinal()]; + if (opcode != Opcodes.NOP) { + visitInsn(opcode); + } + } + } + + void box(Wrapper w) { + visitMethodInsn(Opcodes.INVOKESTATIC, + wrapperName(w), + NAME_BOX_METHOD, + boxingDescriptor(w)); + } + + /** + * Convert types by unboxing. The source type is known to be a primitive wrapper. + * @param ws A primitive wrapper corresponding to wrapped reference source type + * @param wt A primitive wrapper being converted to + */ + void unbox(String sname, Wrapper wt) { + visitMethodInsn(Opcodes.INVOKEVIRTUAL, + sname, + unboxMethod(wt), + unboxingDescriptor(wt)); + } + + private String descriptorToName(String desc) { + int last = desc.length() - 1; + if (desc.charAt(0) == 'L' && desc.charAt(last) == ';') { + // In descriptor form + return desc.substring(1, last); + } else { + // Already in internal name form + return desc; + } + } + + void cast(String ds, String dt) { + String ns = descriptorToName(ds); + String nt = descriptorToName(dt); + if (!nt.equals(ns) && !nt.equals(NAME_OBJECT)) { + visitTypeInsn(Opcodes.CHECKCAST, nt); + } + } + + private boolean isPrimitive(Wrapper w) { + return w != OBJECT; + } + + private Wrapper toWrapper(String desc) { + char first = desc.charAt(0); + if (first == '[' || first == '(') { + first = 'L'; + } + return Wrapper.forBasicType(first); + } + + /** + * Convert an argument of type 'argType' to be passed to 'targetType' assuring that it is 'functionalType'. + * Insert the needed conversion instructions in the method code. + * @param argType + * @param targetType + * @param functionalType + */ + void convertType(String dArg, String dTarget, String dFunctional) { + if (dArg.equals(dTarget)) { + return; + } + Wrapper wArg = toWrapper(dArg); + Wrapper wTarget = toWrapper(dTarget); + if (wArg == VOID || wTarget == VOID) { + return; + } + if (isPrimitive(wArg)) { + if (isPrimitive(wTarget)) { + // Both primitives: widening + widen(wArg, wTarget); + } else { + // Primitive argument to reference target + Wrapper wPrimTarget = wrapperOrNullFromDescriptor(dTarget); + if (wPrimTarget != null) { + // The target is a boxed primitive type, widen to get there before boxing + widen(wArg, wPrimTarget); + box(wPrimTarget); + } else { + // Otherwise, box and cast + box(wArg); + cast(wrapperName(wArg), dTarget); + } + } + } else { + String dSrc; + Wrapper wFunctional = toWrapper(dFunctional); + if (isPrimitive(wFunctional)) { + dSrc = dArg; + } else { + // Cast to convert to possibly more specific type, and generate CCE for invalid arg + dSrc = dFunctional; + cast(dArg, dFunctional); + } + if (isPrimitive(wTarget)) { + // Reference argument to primitive target + Wrapper wps = wrapperOrNullFromDescriptor(dSrc); + if (wps != null) { + if (wps.isSigned() || wps.isFloating()) { + // Boxed number to primitive + unbox(wrapperName(wps), wTarget); + } else { + // Character or Boolean + unbox(wrapperName(wps), wps); + widen(wps, wTarget); + } + } else { + // Source type is reference type, but not boxed type, + // assume it is super type of target type + String intermediate; + if (wTarget.isSigned() || wTarget.isFloating()) { + // Boxed number to primitive + intermediate = "java/lang/Number"; + } else { + // Character or Boolean + intermediate = wrapperName(wTarget); + } + cast(dSrc, intermediate); + unbox(intermediate, wTarget); + } + } else { + // Both reference types: just case to target type + cast(dSrc, dTarget); + } + } + } +} -- GitLab