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(