1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 
  28 import jdk.internal.org.objectweb.asm.*;
  29 import sun.misc.Unsafe;
  30 import sun.util.logging.PlatformLogger;
  31 
  32 import java.io.File;
  33 import java.io.FileOutputStream;
  34 import java.io.IOException;
  35 import java.lang.reflect.Constructor;
  36 import java.security.AccessController;
  37 import java.security.PrivilegedAction;
  38 import java.security.ProtectionDomain;
  39 import java.util.concurrent.atomic.AtomicBoolean;
  40 import java.util.concurrent.atomic.AtomicInteger;
  41 
  42 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  43 
  44 /**
  45  * Lambda metafactory implementation which dynamically creates an
  46  * inner-class-like class per lambda callsite.
  47  *
  48  * @see LambdaMetafactory
  49  */
  50 /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory {
  51     private static final Unsafe UNSAFE = Unsafe.getUnsafe();
  52 
  53     private static final int CLASSFILE_VERSION = 51;
  54     private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE);
  55     private static final String NAME_MAGIC_ACCESSOR_IMPL = "java/lang/invoke/MagicLambdaImpl";
  56     private static final String NAME_CTOR = "<init>";
  57 
  58     //Serialization support
  59     private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
  60     private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;";
  61     private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace";
  62     private static final String NAME_OBJECT = "java/lang/Object";
  63     private static final String DESCR_CTOR_SERIALIZED_LAMBDA
  64             = MethodType.methodType(void.class,
  65                                     Class.class,
  66                                     String.class, String.class, String.class,
  67                                     int.class, String.class, String.class, String.class,
  68                                     String.class,
  69                                     Object[].class).toMethodDescriptorString();
  70 
  71     // Used to ensure that each spun class name is unique
  72     private static final AtomicInteger counter = new AtomicInteger(0);
  73 
  74     // For dumping generated classes to disk, for debugging purposes
  75     private static final String DUMP_PATH_PROPERTY = "jdk.internal.lambda.dumpProxyClasses";
  76     private static final String dumpDir;
  77     private static AtomicBoolean invalidDir = new AtomicBoolean(false);
  78     static {
  79         dumpDir = AccessController.doPrivileged(new PrivilegedAction<String>() {
  80             @Override
  81             public String run() {
  82                 String path = System.getProperty(DUMP_PATH_PROPERTY);
  83                 if (path != null) {
  84                     validateDumpDir(path);
  85                 }
  86                 return path;
  87             }
  88         });
  89     }
  90 
  91     // See context values in AbstractValidatingLambdaMetafactory
  92     private final String implMethodClassName;        // Name of type containing implementation "CC"
  93     private final String implMethodName;             // Name of implementation method "impl"
  94     private final String implMethodDesc;             // Type descriptor for implementation methods "(I)Ljava/lang/String;"
  95     private final Type[] implMethodArgumentTypes;    // ASM types for implementaion method parameters
  96     private final Type implMethodReturnType;         // ASM type for implementaion method return type "Ljava/lang/String;"
  97     private final MethodType constructorType;        // Generated class constructor type "(CC)void"
  98     private final String constructorDesc;            // Type descriptor for constructor "(LCC;)V"
  99     private final ClassWriter cw;                    // ASM class writer
 100     private final Type[] argTypes;                   // ASM types for the constructor arguments
 101     private final String[] argNames;                 // Generated names for the constructor arguments
 102     private final String lambdaClassName;            // Generated name for the generated class "X$$Lambda$1"
 103     private final Type[] instantiatedArgumentTypes;  // ASM types for the functional interface arguments
 104 
 105     /**
 106      * General meta-factory constructor, supporting both standard cases and
 107      * allowing for uncommon options such as serialization or bridging.
 108      *
 109      * @param caller Stacked automatically by VM; represents a lookup context
 110      *               with the accessibility privileges of the caller.
 111      * @param invokedType Stacked automatically by VM; the signature of the
 112      *                    invoked method, which includes the expected static
 113      *                    type of the returned lambda object, and the static
 114      *                    types of the captured arguments for the lambda.  In
 115      *                    the event that the implementation method is an
 116      *                    instance method, the first argument in the invocation
 117      *                    signature will correspond to the receiver.
 118      * @param samMethodName Name of the method in the functional interface to
 119      *                      which the lambda or method reference is being
 120      *                      converted, represented as a String.
 121      * @param samMethodType Type of the method in the functional interface to
 122      *                      which the lambda or method reference is being
 123      *                      converted, represented as a MethodType.
 124      * @param implMethod The implementation method which should be called (with
 125      *                   suitable adaptation of argument types, return types,
 126      *                   and adjustment for captured arguments) when methods of
 127      *                   the resulting functional interface instance are invoked.
 128      * @param instantiatedMethodType The signature of the primary functional
 129      *                               interface method after type variables are
 130      *                               substituted with their instantiation from
 131      *                               the capture site
 132      * @param isSerializable Should the lambda be made serializable?  If set,
 133      *                       either the target type or one of the additional SAM
 134      *                       types must extend {@code Serializable}.
 135      * @param markerInterfaces Additional interfaces which the lambda object
 136      *                       should implement.
 137      * @param additionalBridges Method types for additional signatures to be
 138      *                          bridged to the implementation method
 139      * @throws ReflectiveOperationException
 140      * @throws LambdaConversionException If any of the meta-factory protocol
 141      * invariants are violated
 142      */
 143     public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
 144                                        MethodType invokedType,
 145                                        String samMethodName,
 146                                        MethodType samMethodType,
 147                                        MethodHandle implMethod,
 148                                        MethodType instantiatedMethodType,
 149                                        boolean isSerializable,
 150                                        Class<?>[] markerInterfaces,
 151                                        MethodType[] additionalBridges)
 152             throws ReflectiveOperationException, LambdaConversionException {
 153         super(caller, invokedType, samMethodName, samMethodType,
 154               implMethod, instantiatedMethodType,
 155               isSerializable, markerInterfaces, additionalBridges);
 156         implMethodClassName = implDefiningClass.getName().replace('.', '/');
 157         implMethodName = implInfo.getName();
 158         implMethodDesc = implMethodType.toMethodDescriptorString();
 159         Type implMethodAsmType = Type.getMethodType(implMethodDesc);
 160         implMethodArgumentTypes = implMethodAsmType.getArgumentTypes();
 161         implMethodReturnType = (implKind == MethodHandleInfo.REF_newInvokeSpecial)
 162                 ? Type.getObjectType(implMethodClassName)
 163                 : implMethodAsmType.getReturnType();
 164         constructorType = invokedType.changeReturnType(Void.TYPE);
 165         constructorDesc = constructorType.toMethodDescriptorString();
 166         lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
 167         cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
 168         argTypes = Type.getArgumentTypes(constructorDesc);
 169         argNames = new String[argTypes.length];
 170         for (int i = 0; i < argTypes.length; i++) {
 171             argNames[i] = "arg$" + (i + 1);
 172         }
 173         instantiatedArgumentTypes = Type.getArgumentTypes(
 174                 instantiatedMethodType.toMethodDescriptorString());
 175     }
 176 
 177     /**
 178      * Build the CallSite. Generate a class file which implements the functional
 179      * interface, define the class, if there are no parameters create an instance
 180      * of the class which the CallSite will return, otherwise, generate handles
 181      * which will call the class' constructor.
 182      *
 183      * @return a CallSite, which, when invoked, will return an instance of the
 184      * functional interface
 185      * @throws ReflectiveOperationException
 186      * @throws LambdaConversionException If properly formed functional interface
 187      * is not found
 188      */
 189     @Override
 190     CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException {
 191         final Class<?> innerClass = spinInnerClass();
 192         if (invokedType.parameterCount() == 0) {
 193             final Constructor[] ctrs = AccessController.doPrivileged(
 194                     new PrivilegedAction<Constructor[]>() {
 195                 @Override
 196                 public Constructor[] run() {
 197                     return innerClass.getDeclaredConstructors();
 198                 }
 199             });
 200             if (ctrs.length != 1) {
 201                 throw new ReflectiveOperationException("Expected one lambda constructor for "
 202                         + innerClass.getCanonicalName() + ", got " + ctrs.length);
 203             }
 204             // The lambda implementing inner class constructor is private, set
 205             // it accessible (by us) before creating the constant sole instance
 206             AccessController.doPrivileged(new PrivilegedAction<Void>() {
 207                 @Override
 208                 public Void run() {
 209                     ctrs[0].setAccessible(true);
 210                     return null;
 211                 }
 212             });
 213             Object inst = ctrs[0].newInstance();
 214             return new ConstantCallSite(MethodHandles.constant(samBase, inst));
 215         } else {
 216             return new ConstantCallSite(
 217                     MethodHandles.Lookup.IMPL_LOOKUP
 218                          .findConstructor(innerClass, constructorType)
 219                          .asType(constructorType.changeReturnType(samBase)));
 220         }
 221     }
 222 
 223     private static File validateDumpDir(String path) {
 224         File dirPath = new File(path);
 225         String errMsg = null;
 226         if (!dirPath.exists()) {
 227             errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses does not exist";
 228         } else if (!dirPath.isDirectory()) {
 229             errMsg = "Path at jdk.internal.lambda.dumpProxyClasses is not a directory";
 230         } else if (!dirPath.canWrite()) {
 231             errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses is not writable";
 232         } else {
 233             // validate dump directory, ready to go
 234             return dirPath;
 235         }
 236 
 237         // show error message about invalid directory once
 238         if (! invalidDir.getAndSet(true)) {
 239             PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName())
 240                           .warning(errMsg);
 241         }
 242         return null;
 243     }
 244 
 245     /**
 246      * Generate a class file which implements the functional
 247      * interface, define and return the class.
 248      *
 249      * @implNote The class that is generated does not include signature
 250      * information for exceptions that may be present on the SAM method.
 251      * This is to reduce classfile size, and is harmless as checked exceptions
 252      * are erased anyway, no one will ever compile against this classfile,
 253      * and we make no guarantees about the reflective properties of lambda
 254      * objects.
 255      *
 256      * @return a Class which implements the functional interface
 257      * @throws LambdaConversionException If properly formed functional interface
 258      * is not found
 259      */
 260     private Class<?> spinInnerClass() throws LambdaConversionException {
 261         String[] interfaces = new String[markerInterfaces.length + 1];
 262         interfaces[0] = samBase.getName().replace('.', '/');
 263         for (int i=0; i<markerInterfaces.length; i++) {
 264             interfaces[i+1] = markerInterfaces[i].getName().replace('.', '/');
 265         }
 266         cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
 267                  lambdaClassName, null,
 268                  NAME_MAGIC_ACCESSOR_IMPL, interfaces);
 269 
 270         // Generate final fields to be filled in by constructor
 271         for (int i = 0; i < argTypes.length; i++) {
 272             FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL,
 273                                             argNames[i],
 274                                             argTypes[i].getDescriptor(),
 275                                             null, null);
 276             fv.visitEnd();
 277         }
 278 
 279         generateConstructor();
 280 
 281         // Forward the SAM method
 282         String methodDescriptor = samMethodType.toMethodDescriptorString();
 283         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
 284                                           methodDescriptor, null, null);
 285         new ForwardingMethodGenerator(mv).generate(methodDescriptor);
 286 
 287         // Forward the bridges
 288         if (additionalBridges != null) {
 289             for (MethodType mt : additionalBridges) {
 290                 methodDescriptor = mt.toMethodDescriptorString();
 291                 mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName,
 292                                     methodDescriptor, null, null);
 293                 new ForwardingMethodGenerator(mv).generate(methodDescriptor);
 294             }
 295         }
 296 
 297         if (isSerializable)
 298             generateWriteReplace();
 299 
 300         cw.visitEnd();
 301 
 302         // Define the generated class in this VM.
 303 
 304         final byte[] classBytes = cw.toByteArray();
 305 
 306         // If requested, dump out to a file for debugging purposes
 307         if (dumpDir != null) {
 308             AccessController.doPrivileged(new PrivilegedAction<Void>() {
 309                 @Override
 310                 public Void run() {
 311                     File dirPath = validateDumpDir(dumpDir);
 312                     if (dirPath != null) {
 313                         File out = new File(dirPath, lambdaClassName.replace('/', '.') + ".class");
 314                         try (FileOutputStream fos = new FileOutputStream(out)) {
 315                             fos.write(classBytes);
 316                         } catch (IOException ex) {
 317                             PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName())
 318                                           .warning("Exception writing to path at jdk.internal.lambda.dumpProxyClasses");
 319                         }
 320                     }
 321                     return null;
 322                 }
 323             });
 324         }
 325 
 326         ClassLoader loader = targetClass.getClassLoader();
 327         ProtectionDomain pd = (loader == null)
 328                               ? null
 329                               : AccessController.doPrivileged(
 330                                       new PrivilegedAction<ProtectionDomain>() {
 331                                           @Override
 332                                           public ProtectionDomain run() {
 333                                               return targetClass.getProtectionDomain();
 334                                           }
 335                                       }
 336                               );
 337 
 338         return UNSAFE.defineClass(lambdaClassName,
 339                                   classBytes, 0, classBytes.length,
 340                                   loader, pd);
 341     }
 342 
 343     /**
 344      * Generate the constructor for the class
 345      */
 346     private void generateConstructor() {
 347         // Generate constructor
 348         MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR,
 349                                             constructorDesc, null, null);
 350         ctor.visitCode();
 351         ctor.visitVarInsn(ALOAD, 0);
 352         ctor.visitMethodInsn(INVOKESPECIAL, NAME_MAGIC_ACCESSOR_IMPL, NAME_CTOR,
 353                              METHOD_DESCRIPTOR_VOID);
 354         int lvIndex = 0;
 355         for (int i = 0; i < argTypes.length; i++) {
 356             ctor.visitVarInsn(ALOAD, 0);
 357             ctor.visitVarInsn(argTypes[i].getOpcode(ILOAD), lvIndex + 1);
 358             lvIndex += argTypes[i].getSize();
 359             ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i],
 360                                 argTypes[i].getDescriptor());
 361         }
 362         ctor.visitInsn(RETURN);
 363         // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
 364         ctor.visitMaxs(-1, -1);
 365         ctor.visitEnd();
 366     }
 367 
 368     /**
 369      * Generate the writeReplace method (if needed for serialization)
 370      */
 371     private void generateWriteReplace() {
 372         TypeConvertingMethodAdapter mv
 373                 = new TypeConvertingMethodAdapter(
 374                     cw.visitMethod(ACC_PRIVATE + ACC_FINAL,
 375                     NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE,
 376                     null, null));
 377 
 378         mv.visitCode();
 379         mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA);
 380         mv.visitInsn(DUP);
 381         mv.visitLdcInsn(Type.getType(targetClass));
 382         mv.visitLdcInsn(invokedType.returnType().getName().replace('.', '/'));
 383         mv.visitLdcInsn(samMethodName);
 384         mv.visitLdcInsn(samMethodType.toMethodDescriptorString());
 385         mv.visitLdcInsn(implInfo.getReferenceKind());
 386         mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/'));
 387         mv.visitLdcInsn(implInfo.getName());
 388         mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString());
 389         mv.visitLdcInsn(instantiatedMethodType.toMethodDescriptorString());
 390 
 391         mv.iconst(argTypes.length);
 392         mv.visitTypeInsn(ANEWARRAY, NAME_OBJECT);
 393         for (int i = 0; i < argTypes.length; i++) {
 394             mv.visitInsn(DUP);
 395             mv.iconst(i);
 396             mv.visitVarInsn(ALOAD, 0);
 397             mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i],
 398                               argTypes[i].getDescriptor());
 399             mv.boxIfTypePrimitive(argTypes[i]);
 400             mv.visitInsn(AASTORE);
 401         }
 402         mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR,
 403                 DESCR_CTOR_SERIALIZED_LAMBDA);
 404         mv.visitInsn(ARETURN);
 405         // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
 406         mv.visitMaxs(-1, -1);
 407         mv.visitEnd();
 408     }
 409 
 410     /**
 411      * This class generates a method body which calls the lambda implementation
 412      * method, converting arguments, as needed.
 413      */
 414     private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter {
 415 
 416         ForwardingMethodGenerator(MethodVisitor mv) {
 417             super(mv);
 418         }
 419 
 420         void generate(String methodDescriptor) {
 421             visitCode();
 422 
 423             if (implKind == MethodHandleInfo.REF_newInvokeSpecial) {
 424                 visitTypeInsn(NEW, implMethodClassName);
 425                 visitInsn(DUP);
 426             }
 427             for (int i = 0; i < argTypes.length; i++) {
 428                 visitVarInsn(ALOAD, 0);
 429                 visitFieldInsn(GETFIELD, lambdaClassName, argNames[i],
 430                                argTypes[i].getDescriptor());
 431             }
 432 
 433             convertArgumentTypes(Type.getArgumentTypes(methodDescriptor));
 434 
 435             // Invoke the method we want to forward to
 436             visitMethodInsn(invocationOpcode(), implMethodClassName, implMethodName, implMethodDesc);
 437 
 438             // Convert the return value (if any) and return it
 439             // Note: if adapting from non-void to void, the 'return'
 440             // instruction will pop the unneeded result
 441             Type samReturnType = Type.getReturnType(methodDescriptor);
 442             convertType(implMethodReturnType, samReturnType, samReturnType);
 443             visitInsn(samReturnType.getOpcode(Opcodes.IRETURN));
 444             // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored
 445             visitMaxs(-1, -1);
 446             visitEnd();
 447         }
 448 
 449         private void convertArgumentTypes(Type[] samArgumentTypes) {
 450             int lvIndex = 0;
 451             boolean samIncludesReceiver = implIsInstanceMethod &&
 452                                                    argTypes.length == 0;
 453             int samReceiverLength = samIncludesReceiver ? 1 : 0;
 454             if (samIncludesReceiver) {
 455                 // push receiver
 456                 Type rcvrType = samArgumentTypes[0];
 457                 Type instantiatedRcvrType = instantiatedArgumentTypes[0];
 458 
 459                 visitVarInsn(rcvrType.getOpcode(ILOAD), lvIndex + 1);
 460                 lvIndex += rcvrType.getSize();
 461                 convertType(rcvrType, Type.getType(implDefiningClass), instantiatedRcvrType);
 462             }
 463             int argOffset = implMethodArgumentTypes.length - samArgumentTypes.length;
 464             for (int i = samReceiverLength; i < samArgumentTypes.length; i++) {
 465                 Type argType = samArgumentTypes[i];
 466                 Type targetType = implMethodArgumentTypes[argOffset + i];
 467                 Type instantiatedArgType = instantiatedArgumentTypes[i];
 468 
 469                 visitVarInsn(argType.getOpcode(ILOAD), lvIndex + 1);
 470                 lvIndex += argType.getSize();
 471                 convertType(argType, targetType, instantiatedArgType);
 472             }
 473         }
 474 
 475         private void convertType(Type argType, Type targetType, Type functionalType) {
 476             convertType(argType.getDescriptor(),
 477                         targetType.getDescriptor(),
 478                         functionalType.getDescriptor());
 479         }
 480 
 481         private int invocationOpcode() throws InternalError {
 482             switch (implKind) {
 483                 case MethodHandleInfo.REF_invokeStatic:
 484                     return INVOKESTATIC;
 485                 case MethodHandleInfo.REF_newInvokeSpecial:
 486                     return INVOKESPECIAL;
 487                  case MethodHandleInfo.REF_invokeVirtual:
 488                     return INVOKEVIRTUAL;
 489                 case MethodHandleInfo.REF_invokeInterface:
 490                     return INVOKEINTERFACE;
 491                 case MethodHandleInfo.REF_invokeSpecial:
 492                     return INVOKESPECIAL;
 493                 default:
 494                     throw new InternalError("Unexpected invocation kind: " + implKind);
 495             }
 496         }
 497     }
 498 }