src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java

Print this page
rev 9851 : 8023524: Mechanism to dump generated lambda classes / log lambda code generation
Reviewed-by:
Contributed-by: brian.goetz@oracle.com, henry.jen@oracle.com

@@ -25,15 +25,20 @@
 
 package java.lang.invoke;
 
 import jdk.internal.org.objectweb.asm.*;
 import sun.misc.Unsafe;
+import sun.util.logging.PlatformLogger;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static jdk.internal.org.objectweb.asm.Opcodes.*;
 
 /**

@@ -49,10 +54,21 @@
     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_CTOR = "<init>";
 
     //Serialization support
+    private static final char[] HEX = {
+        '0', '1', '2', '3', '4', '5', '6', '7',
+        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+    private static final char[] BAD_CHARS = {
+        '\\', '/', ':', '*', '?', '"', '<', '>', '|'
+    };
+    private static final String[] REPLACEMENT = {
+        "%5C", ".", "%3A", "%2A", "%3F", "%22", "%3C", "%3E", "%7C"
+    };
+
     private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
     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";
     private static final String DESCR_CTOR_SERIALIZED_LAMBDA

@@ -64,10 +80,27 @@
                                     Object[].class).toMethodDescriptorString();
 
     // 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 String DUMP_PATH_PROPERTY = "jdk.internal.lambda.dumpProxyClasses";
+    private static final String dumpDir;
+    private static AtomicBoolean invalidDir = new AtomicBoolean(false);
+    static {
+        dumpDir = AccessController.doPrivileged(new PrivilegedAction<String>() {
+            @Override
+            public String run() {
+                String path = System.getProperty(DUMP_PATH_PROPERTY);
+                if (path != null) {
+                    validateDumpDir(path);
+                }
+                return 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 Type[] implMethodArgumentTypes;    // ASM types for implementaion method parameters

@@ -196,10 +229,60 @@
                          .findConstructor(innerClass, constructorType)
                          .asType(constructorType.changeReturnType(samBase)));
         }
     }
 
+    private static File validateDumpDir(String path) {
+        File dirPath = new File(path);
+        String errMsg = null;
+        if (!dirPath.exists()) {
+            errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses does not exist";
+        } else if (!dirPath.isDirectory()) {
+            errMsg = "Path at jdk.internal.lambda.dumpProxyClasses is not a directory";
+        } else if (!dirPath.canWrite()) {
+            errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses is not writable";
+        } else {
+            // validate dump directory, ready to go
+            return dirPath;
+        }
+
+        // show error message about invalid directory once
+        if (! invalidDir.getAndSet(true)) {
+            PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName())
+                          .warning(errMsg);
+        }
+        return null;
+    }
+
+    private static String encodeForFilename(String className) {
+        final int len = className.length();
+        StringBuilder sb = new StringBuilder(len);
+
+        for (int i = 0; i < len; i++) {
+            char c = className.charAt(i);
+            // control characters
+            if (c <= 31) {
+                sb.append('%');
+                sb.append(HEX[c >> 4 & 0x0F]);
+                sb.append(HEX[c & 0x0F]);
+            } else {
+                int j = 0;
+                for (; j < BAD_CHARS.length; j++) {
+                    if (c == BAD_CHARS[j]) {
+                        sb.append(REPLACEMENT[j]);
+                        break;
+                    }
+                }
+                if (j >= BAD_CHARS.length) {
+                    sb.append(c);
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+
     /**
      * Generate a class file which implements the functional
      * interface, define and return the class.
      *
      * @implNote The class that is generated does not include signature

@@ -257,21 +340,29 @@
 
         // Define the generated class in this VM.
 
         final byte[] classBytes = cw.toByteArray();
 
-        /*** Uncomment to dump the generated file
-            System.out.printf("Loaded: %s (%d bytes) %n", lambdaClassName,
-                              classBytes.length);
-            try (FileOutputStream fos = new FileOutputStream(lambdaClassName
-                                            .replace('/', '.') + ".class")) {
+        // If requested, dump out to a file for debugging purposes
+        if (dumpDir != null) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    File dirPath = validateDumpDir(dumpDir);
+                    if (dirPath != null) {
+                        File out = new File(dirPath, encodeForFilename(lambdaClassName) + ".class");
+                        try (FileOutputStream fos = new FileOutputStream(out)) {
                 fos.write(classBytes);
             } catch (IOException ex) {
-                PlatformLogger.getLogger(InnerClassLambdaMetafactory.class
-                                      .getName()).severe(ex.getMessage(), ex);
+                            PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName())
+                                          .warning("Exception writing to path at jdk.internal.lambda.dumpProxyClasses");
+                        }
+                    }
+                    return null;
+                }
+            });
             }
-        ***/
 
         ClassLoader loader = targetClass.getClassLoader();
         ProtectionDomain pd = (loader == null)
             ? null
             : AccessController.doPrivileged(