8304846: Provide a shared utility to dump generated classes defined via Lookup API
Reviewed-by: jvernee
This commit is contained in:
parent
2ee4245105
commit
dd59471798
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2023, 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
|
||||
@ -25,7 +25,6 @@
|
||||
|
||||
package java.lang.invoke;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.loader.BootLoader;
|
||||
import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.FieldVisitor;
|
||||
@ -36,9 +35,6 @@ import sun.invoke.util.BytecodeName;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -570,22 +566,9 @@ abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesDat
|
||||
@SuppressWarnings("removal")
|
||||
Class<? extends T> generateConcreteSpeciesCode(String className, ClassSpecializer<T,K,S>.SpeciesData speciesData) {
|
||||
byte[] classFile = generateConcreteSpeciesCodeFile(className, speciesData);
|
||||
|
||||
// load class
|
||||
InvokerBytecodeGenerator.maybeDump(classBCName(className), classFile);
|
||||
ClassLoader cl = topClass.getClassLoader();
|
||||
ProtectionDomain pd = null;
|
||||
if (cl != null) {
|
||||
pd = AccessController.doPrivileged(
|
||||
new PrivilegedAction<>() {
|
||||
@Override
|
||||
public ProtectionDomain run() {
|
||||
return topClass().getProtectionDomain();
|
||||
}
|
||||
});
|
||||
}
|
||||
Class<?> speciesCode = SharedSecrets.getJavaLangAccess()
|
||||
.defineClass(cl, className, classFile, pd, "_ClassSpecializer_generateConcreteSpeciesCode");
|
||||
var lookup = new MethodHandles.Lookup(topClass);
|
||||
Class<?> speciesCode = lookup.makeClassDefiner(classBCName(className), classFile, dumper())
|
||||
.defineClass(false);
|
||||
return speciesCode.asSubclass(topClass());
|
||||
}
|
||||
|
||||
|
@ -27,21 +27,16 @@ package java.lang.invoke;
|
||||
|
||||
import jdk.internal.misc.CDS;
|
||||
import jdk.internal.org.objectweb.asm.*;
|
||||
import jdk.internal.util.ClassFileDumper;
|
||||
import sun.invoke.util.BytecodeDescriptor;
|
||||
import sun.invoke.util.VerifyAccess;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.io.Serializable;
|
||||
import java.lang.constant.ConstantDescs;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.PropertyPermission;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION;
|
||||
@ -85,11 +80,8 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
// Used to ensure that dumped class files for failed definitions have a unique class name
|
||||
private static final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
// For dumping generated classes to disk, for debugging purposes
|
||||
private static final ProxyClassesDumper dumper;
|
||||
private static final ClassFileDumper lambdaProxyClassFileDumper;
|
||||
|
||||
private static final boolean disableEagerInitialization;
|
||||
|
||||
@ -97,9 +89,11 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
private static final ConstantDynamic implMethodCondy;
|
||||
|
||||
static {
|
||||
final String dumpProxyClassesKey = "jdk.internal.lambda.dumpProxyClasses";
|
||||
String dumpPath = GetPropertyAction.privilegedGetProperty(dumpProxyClassesKey);
|
||||
dumper = (null == dumpPath) ? null : ProxyClassesDumper.getInstance(dumpPath);
|
||||
// To dump the lambda proxy classes, set this system property:
|
||||
// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
|
||||
// or -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true
|
||||
final String dumpProxyClassesKey = "jdk.invoke.LambdaMetafactory.dumpProxyClassFiles";
|
||||
lambdaProxyClassFileDumper = ClassFileDumper.getInstance(dumpProxyClassesKey, Path.of("DUMP_LAMBDA_PROXY_CLASS_FILES"));
|
||||
|
||||
final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization";
|
||||
disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey);
|
||||
@ -363,51 +357,15 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
final byte[] classBytes = cw.toByteArray();
|
||||
try {
|
||||
// this class is linked at the indy callsite; so define a hidden nestmate
|
||||
Lookup lookup = null;
|
||||
try {
|
||||
if (useImplMethodHandle) {
|
||||
lookup = caller.defineHiddenClassWithClassData(classBytes, implementation, !disableEagerInitialization,
|
||||
NESTMATE, STRONG);
|
||||
} else {
|
||||
lookup = caller.defineHiddenClass(classBytes, !disableEagerInitialization, NESTMATE, STRONG);
|
||||
}
|
||||
return lookup.lookupClass();
|
||||
} finally {
|
||||
// If requested, dump out to a file for debugging purposes
|
||||
if (dumper != null) {
|
||||
String name;
|
||||
if (lookup != null) {
|
||||
String definedName = lookup.lookupClass().getName();
|
||||
int suffixIdx = definedName.lastIndexOf('/');
|
||||
assert suffixIdx != -1;
|
||||
name = lambdaClassName + '.' + definedName.substring(suffixIdx + 1);
|
||||
} else {
|
||||
name = lambdaClassName + ".failed-" + counter.incrementAndGet();
|
||||
}
|
||||
doDump(name, classBytes);
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new LambdaConversionException("Exception defining lambda proxy class", e);
|
||||
var classdata = useImplMethodHandle? implementation : null;
|
||||
return caller.makeHiddenClassDefiner(lambdaClassName, classBytes, Set.of(NESTMATE, STRONG), lambdaProxyClassFileDumper)
|
||||
.defineClass(!disableEagerInitialization, classdata);
|
||||
|
||||
} catch (Throwable t) {
|
||||
throw new InternalError(t);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private void doDump(final String className, final byte[] classBytes) {
|
||||
AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
@Override
|
||||
public Void run() {
|
||||
dumper.dumpClass(className, classBytes);
|
||||
return null;
|
||||
}
|
||||
}, null,
|
||||
new FilePermission("<<ALL FILES>>", "read, write"),
|
||||
// createDirectories may need it
|
||||
new PropertyPermission("user.dir", "read"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a static field and a static initializer that sets this field to an instance of the lambda
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2023, 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
|
||||
@ -34,11 +34,7 @@ import jdk.internal.org.objectweb.asm.Type;
|
||||
import sun.invoke.util.VerifyAccess;
|
||||
import sun.invoke.util.VerifyType;
|
||||
import sun.invoke.util.Wrapper;
|
||||
import sun.reflect.misc.ReflectUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -125,7 +121,7 @@ class InvokerBytecodeGenerator {
|
||||
name = invokerName.substring(0, p);
|
||||
invokerName = invokerName.substring(p + 1);
|
||||
}
|
||||
if (DUMP_CLASS_FILES) {
|
||||
if (dumper().isEnabled()) {
|
||||
name = makeDumpableClassName(name);
|
||||
}
|
||||
this.name = name;
|
||||
@ -173,58 +169,8 @@ class InvokerBytecodeGenerator {
|
||||
}
|
||||
|
||||
/** instance counters for dumped classes */
|
||||
private static final HashMap<String,Integer> DUMP_CLASS_FILES_COUNTERS;
|
||||
/** debugging flag for saving generated class files */
|
||||
private static final File DUMP_CLASS_FILES_DIR;
|
||||
|
||||
static {
|
||||
if (DUMP_CLASS_FILES) {
|
||||
DUMP_CLASS_FILES_COUNTERS = new HashMap<>();
|
||||
try {
|
||||
File dumpDir = new File("DUMP_CLASS_FILES");
|
||||
if (!dumpDir.exists()) {
|
||||
dumpDir.mkdirs();
|
||||
}
|
||||
DUMP_CLASS_FILES_DIR = dumpDir;
|
||||
System.out.println("Dumping class files to "+DUMP_CLASS_FILES_DIR+"/...");
|
||||
} catch (Exception e) {
|
||||
throw newInternalError(e);
|
||||
}
|
||||
} else {
|
||||
DUMP_CLASS_FILES_COUNTERS = null;
|
||||
DUMP_CLASS_FILES_DIR = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeDump(final byte[] classFile) {
|
||||
if (DUMP_CLASS_FILES) {
|
||||
maybeDump(className, classFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Also used from BoundMethodHandle
|
||||
@SuppressWarnings("removal")
|
||||
static void maybeDump(final String className, final byte[] classFile) {
|
||||
if (DUMP_CLASS_FILES) {
|
||||
java.security.AccessController.doPrivileged(
|
||||
new java.security.PrivilegedAction<>() {
|
||||
public Void run() {
|
||||
try {
|
||||
String dumpName = className.replace('.','/');
|
||||
File dumpFile = new File(DUMP_CLASS_FILES_DIR, dumpName+".class");
|
||||
System.out.println("dump: " + dumpFile);
|
||||
dumpFile.getParentFile().mkdirs();
|
||||
FileOutputStream file = new FileOutputStream(dumpFile);
|
||||
file.write(classFile);
|
||||
file.close();
|
||||
return null;
|
||||
} catch (IOException ex) {
|
||||
throw newInternalError(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
private static final HashMap<String,Integer> DUMP_CLASS_FILES_COUNTERS =
|
||||
dumper().isEnabled() ? new HashMap<>(): null;
|
||||
|
||||
private static String makeDumpableClassName(String className) {
|
||||
Integer ctr;
|
||||
@ -271,7 +217,7 @@ class InvokerBytecodeGenerator {
|
||||
|
||||
// unique static variable name
|
||||
String name;
|
||||
if (DUMP_CLASS_FILES) {
|
||||
if (dumper().isEnabled()) {
|
||||
Class<?> c = arg.getClass();
|
||||
while (c.isArray()) {
|
||||
c = c.getComponentType();
|
||||
@ -299,7 +245,7 @@ class InvokerBytecodeGenerator {
|
||||
* Extract the MemberName of a newly-defined method.
|
||||
*/
|
||||
private MemberName loadMethod(byte[] classFile) {
|
||||
Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(className, classFile, Set.of())
|
||||
Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(className, classFile, Set.of(), dumper())
|
||||
.defineClass(true, classDataValues());
|
||||
return resolveInvokerMember(invokerClass, invokerName, invokerType);
|
||||
}
|
||||
@ -809,9 +755,7 @@ class InvokerBytecodeGenerator {
|
||||
clinit(cw, className, classData);
|
||||
bogusMethod(lambdaForm);
|
||||
|
||||
final byte[] classFile = toByteArray();
|
||||
maybeDump(classFile);
|
||||
return classFile;
|
||||
return toByteArray();
|
||||
}
|
||||
|
||||
void setClassWriter(ClassWriter cw) {
|
||||
@ -1898,9 +1842,7 @@ class InvokerBytecodeGenerator {
|
||||
clinit(cw, className, classData);
|
||||
bogusMethod(invokerType);
|
||||
|
||||
final byte[] classFile = cw.toByteArray();
|
||||
maybeDump(classFile);
|
||||
return classFile;
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1967,9 +1909,7 @@ class InvokerBytecodeGenerator {
|
||||
clinit(cw, className, classData);
|
||||
bogusMethod(dstType);
|
||||
|
||||
final byte[] classFile = cw.toByteArray();
|
||||
maybeDump(classFile);
|
||||
return classFile;
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1977,7 +1917,7 @@ class InvokerBytecodeGenerator {
|
||||
* for debugging purposes.
|
||||
*/
|
||||
private void bogusMethod(Object os) {
|
||||
if (DUMP_CLASS_FILES) {
|
||||
if (dumper().isEnabled()) {
|
||||
mv = cw.visitMethod(Opcodes.ACC_STATIC, "dummy", "()V", null, null);
|
||||
mv.visitLdcInsn(os.toString());
|
||||
mv.visitInsn(Opcodes.POP);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2008, 2023, 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
|
||||
@ -1101,8 +1101,9 @@ abstract class MethodHandleImpl {
|
||||
// use the original class name
|
||||
name = name.replace('/', '_');
|
||||
}
|
||||
name = name.replace('.', '/');
|
||||
Class<?> invokerClass = new Lookup(targetClass)
|
||||
.makeHiddenClassDefiner(name, INJECTED_INVOKER_TEMPLATE, Set.of(NESTMATE))
|
||||
.makeHiddenClassDefiner(name, INJECTED_INVOKER_TEMPLATE, Set.of(NESTMATE), dumper())
|
||||
.defineClass(true, targetClass);
|
||||
assert checkInjectedInvoker(targetClass, invokerClass);
|
||||
return invokerClass;
|
||||
@ -1655,12 +1656,6 @@ abstract class MethodHandleImpl {
|
||||
return BindCaller.reflectiveInvoker(caller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lookup defineHiddenClassWithClassData(Lookup caller, String name, byte[] bytes, Object classData, boolean initialize) {
|
||||
// skip name and access flags validation
|
||||
return caller.makeHiddenClassDefiner(name, bytes, Set.of()).defineClassAsLookup(initialize, classData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?>[] exceptionTypes(MethodHandle handle) {
|
||||
return VarHandles.exceptionTypes(handle);
|
||||
|
@ -27,9 +27,11 @@ package java.lang.invoke;
|
||||
|
||||
import jdk.internal.misc.CDS;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.util.ClassFileDumper;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
|
||||
import java.lang.reflect.ClassFileFormatVersion;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
import static java.lang.invoke.LambdaForm.basicTypeSignature;
|
||||
@ -49,7 +51,6 @@ class MethodHandleStatics {
|
||||
static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
static final int CLASSFILE_VERSION = ClassFileFormatVersion.latest().major();
|
||||
static final boolean DEBUG_METHOD_HANDLE_NAMES;
|
||||
static final boolean DUMP_CLASS_FILES;
|
||||
static final boolean TRACE_INTERPRETER;
|
||||
static final boolean TRACE_METHOD_LINKAGE;
|
||||
static final boolean TRACE_RESOLVE;
|
||||
@ -62,13 +63,13 @@ class MethodHandleStatics {
|
||||
static final boolean VAR_HANDLE_GUARDS;
|
||||
static final int MAX_ARITY;
|
||||
static final boolean VAR_HANDLE_IDENTITY_ADAPT;
|
||||
static final ClassFileDumper DUMP_CLASS_FILES;
|
||||
|
||||
static {
|
||||
Properties props = GetPropertyAction.privilegedGetProperties();
|
||||
DEBUG_METHOD_HANDLE_NAMES = Boolean.parseBoolean(
|
||||
props.getProperty("java.lang.invoke.MethodHandle.DEBUG_NAMES"));
|
||||
DUMP_CLASS_FILES = Boolean.parseBoolean(
|
||||
props.getProperty("java.lang.invoke.MethodHandle.DUMP_CLASS_FILES"));
|
||||
|
||||
TRACE_INTERPRETER = Boolean.parseBoolean(
|
||||
props.getProperty("java.lang.invoke.MethodHandle.TRACE_INTERPRETER"));
|
||||
TRACE_METHOD_LINKAGE = Boolean.parseBoolean(
|
||||
@ -96,6 +97,9 @@ class MethodHandleStatics {
|
||||
MAX_ARITY = Integer.parseInt(
|
||||
props.getProperty("java.lang.invoke.MethodHandleImpl.MAX_ARITY", "255"));
|
||||
|
||||
DUMP_CLASS_FILES = ClassFileDumper.getInstance("jdk.invoke.MethodHandle.dumpMethodHandleInternals",
|
||||
Path.of("DUMP_METHOD_HANDLE_INTERNALS"));
|
||||
|
||||
if (CUSTOMIZE_THRESHOLD < -1 || CUSTOMIZE_THRESHOLD > 127) {
|
||||
throw newInternalError("CUSTOMIZE_THRESHOLD should be in [-1...127] range");
|
||||
}
|
||||
@ -107,12 +111,16 @@ class MethodHandleStatics {
|
||||
/*non-public*/
|
||||
static boolean debugEnabled() {
|
||||
return (DEBUG_METHOD_HANDLE_NAMES |
|
||||
DUMP_CLASS_FILES |
|
||||
DUMP_CLASS_FILES.isEnabled() |
|
||||
TRACE_INTERPRETER |
|
||||
TRACE_METHOD_LINKAGE |
|
||||
LOG_LF_COMPILATION_FAILURE);
|
||||
}
|
||||
|
||||
static ClassFileDumper dumper() {
|
||||
return DUMP_CLASS_FILES;
|
||||
}
|
||||
|
||||
/**
|
||||
* If requested, logs the result of resolving the LambdaForm to stdout
|
||||
* and informs the CDS subsystem about it.
|
||||
|
@ -36,6 +36,7 @@ import jdk.internal.org.objectweb.asm.Type;
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.CallerSensitiveAdapter;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.util.ClassFileDumper;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import sun.invoke.util.ValueConversions;
|
||||
import sun.invoke.util.VerifyAccess;
|
||||
@ -55,6 +56,7 @@ import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.Path;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -2237,8 +2239,23 @@ public class MethodHandles {
|
||||
.defineClassAsLookup(initialize, classData);
|
||||
}
|
||||
|
||||
// A default dumper for writing class files passed to Lookup::defineClass
|
||||
// and Lookup::defineHiddenClass to disk for debugging purposes. To enable,
|
||||
// set -Djdk.invoke.MethodHandle.dumpHiddenClassFiles or
|
||||
// -Djdk.invoke.MethodHandle.dumpHiddenClassFiles=true
|
||||
//
|
||||
// This default dumper does not dump hidden classes defined by LambdaMetafactory
|
||||
// and LambdaForms and method handle internals. They are dumped via
|
||||
// different ClassFileDumpers.
|
||||
private static ClassFileDumper defaultDumper() {
|
||||
return DEFAULT_DUMPER;
|
||||
}
|
||||
|
||||
private static final ClassFileDumper DEFAULT_DUMPER = ClassFileDumper.getInstance(
|
||||
"jdk.invoke.MethodHandle.dumpClassFiles", Path.of("DUMP_CLASS_FILES"));
|
||||
|
||||
static class ClassFile {
|
||||
final String name;
|
||||
final String name; // internal name
|
||||
final int accessFlags;
|
||||
final byte[] bytes;
|
||||
ClassFile(String name, int accessFlags, byte[] bytes) {
|
||||
@ -2260,6 +2277,18 @@ public class MethodHandles {
|
||||
* or the class is not in the given package name.
|
||||
*/
|
||||
static ClassFile newInstance(byte[] bytes, String pkgName) {
|
||||
var cf = readClassFile(bytes);
|
||||
|
||||
// check if it's in the named package
|
||||
int index = cf.name.lastIndexOf('/');
|
||||
String pn = (index == -1) ? "" : cf.name.substring(0, index).replace('/', '.');
|
||||
if (!pn.equals(pkgName)) {
|
||||
throw newIllegalArgumentException(cf.name + " not in same package as lookup class");
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
|
||||
private static ClassFile readClassFile(byte[] bytes) {
|
||||
int magic = readInt(bytes, 0);
|
||||
if (magic != 0xCAFEBABE) {
|
||||
throw new ClassFormatError("Incompatible magic value: " + magic);
|
||||
@ -2274,7 +2303,7 @@ public class MethodHandles {
|
||||
int accessFlags;
|
||||
try {
|
||||
ClassReader reader = new ClassReader(bytes);
|
||||
// ClassReader::getClassName does not check if `this_class` is CONSTANT_Class_info
|
||||
// ClassReader does not check if `this_class` is CONSTANT_Class_info
|
||||
// workaround to read `this_class` using readConst and validate the value
|
||||
int thisClass = reader.readUnsignedShort(reader.header + 2);
|
||||
Object constant = reader.readConst(thisClass, new char[reader.getMaxStringLength()]);
|
||||
@ -2284,7 +2313,7 @@ public class MethodHandles {
|
||||
if (!type.getDescriptor().startsWith("L")) {
|
||||
throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info");
|
||||
}
|
||||
name = type.getClassName();
|
||||
name = type.getInternalName();
|
||||
accessFlags = reader.readUnsignedShort(reader.header);
|
||||
} catch (RuntimeException e) {
|
||||
// ASM exceptions are poorly specified
|
||||
@ -2292,19 +2321,10 @@ public class MethodHandles {
|
||||
cfe.initCause(e);
|
||||
throw cfe;
|
||||
}
|
||||
|
||||
// must be a class or interface
|
||||
if ((accessFlags & Opcodes.ACC_MODULE) != 0) {
|
||||
throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set");
|
||||
}
|
||||
|
||||
// check if it's in the named package
|
||||
int index = name.lastIndexOf('.');
|
||||
String pn = (index == -1) ? "" : name.substring(0, index);
|
||||
if (!pn.equals(pkgName)) {
|
||||
throw newIllegalArgumentException(name + " not in same package as lookup class");
|
||||
}
|
||||
|
||||
return new ClassFile(name, accessFlags, bytes);
|
||||
}
|
||||
|
||||
@ -2338,7 +2358,22 @@ public class MethodHandles {
|
||||
*/
|
||||
private ClassDefiner makeClassDefiner(byte[] bytes) {
|
||||
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
|
||||
return new ClassDefiner(this, cf, STRONG_LOADER_LINK);
|
||||
return new ClassDefiner(this, cf, STRONG_LOADER_LINK, defaultDumper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ClassDefiner that creates a {@code Class} object of a normal class
|
||||
* from the given bytes. No package name check on the given bytes.
|
||||
*
|
||||
* @param name internal name
|
||||
* @param bytes class bytes
|
||||
* @param dumper dumper to write the given bytes to the dumper's output directory
|
||||
* @return ClassDefiner that defines a normal class of the given bytes.
|
||||
*/
|
||||
ClassDefiner makeClassDefiner(String name, byte[] bytes, ClassFileDumper dumper) {
|
||||
// skip package name validation
|
||||
ClassFile cf = ClassFile.newInstanceNoCheck(name, bytes);
|
||||
return new ClassDefiner(this, cf, STRONG_LOADER_LINK, dumper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2349,14 +2384,15 @@ public class MethodHandles {
|
||||
* before calling this factory method.
|
||||
*
|
||||
* @param bytes class bytes
|
||||
* @param dumper dumper to write the given bytes to the dumper's output directory
|
||||
* @return ClassDefiner that defines a hidden class of the given bytes.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code bytes} is not a class or interface or
|
||||
* {@code bytes} denotes a class in a different package than the lookup class
|
||||
*/
|
||||
ClassDefiner makeHiddenClassDefiner(byte[] bytes) {
|
||||
ClassDefiner makeHiddenClassDefiner(byte[] bytes, ClassFileDumper dumper) {
|
||||
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
|
||||
return makeHiddenClassDefiner(cf, Set.of(), false);
|
||||
return makeHiddenClassDefiner(cf, Set.of(), false, dumper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2375,25 +2411,27 @@ public class MethodHandles {
|
||||
* @throws IllegalArgumentException if {@code bytes} is not a class or interface or
|
||||
* {@code bytes} denotes a class in a different package than the lookup class
|
||||
*/
|
||||
ClassDefiner makeHiddenClassDefiner(byte[] bytes,
|
||||
Set<ClassOption> options,
|
||||
boolean accessVmAnnotations) {
|
||||
private ClassDefiner makeHiddenClassDefiner(byte[] bytes,
|
||||
Set<ClassOption> options,
|
||||
boolean accessVmAnnotations) {
|
||||
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
|
||||
return makeHiddenClassDefiner(cf, options, accessVmAnnotations);
|
||||
return makeHiddenClassDefiner(cf, options, accessVmAnnotations, defaultDumper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
|
||||
* from the given bytes and the given options. No package name check on the given name.
|
||||
* from the given bytes and the given options. No package name check on the given bytes.
|
||||
*
|
||||
* @param name fully-qualified name that specifies the prefix of the hidden class
|
||||
* @param name internal name that specifies the prefix of the hidden class
|
||||
* @param bytes class bytes
|
||||
* @param options class options
|
||||
* @param dumper dumper to write the given bytes to the dumper's output directory
|
||||
* @return ClassDefiner that defines a hidden class of the given bytes and options.
|
||||
*/
|
||||
ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes, Set<ClassOption> options) {
|
||||
ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes, Set<ClassOption> options, ClassFileDumper dumper) {
|
||||
Objects.requireNonNull(dumper);
|
||||
// skip name and access flags validation
|
||||
return makeHiddenClassDefiner(ClassFile.newInstanceNoCheck(name, bytes), options, false);
|
||||
return makeHiddenClassDefiner(ClassFile.newInstanceNoCheck(name, bytes), options, false, dumper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2403,10 +2441,12 @@ public class MethodHandles {
|
||||
* @param cf ClassFile
|
||||
* @param options class options
|
||||
* @param accessVmAnnotations true to give the hidden class access to VM annotations
|
||||
* @param dumper dumper to write the given bytes to the dumper's output directory
|
||||
*/
|
||||
private ClassDefiner makeHiddenClassDefiner(ClassFile cf,
|
||||
Set<ClassOption> options,
|
||||
boolean accessVmAnnotations) {
|
||||
boolean accessVmAnnotations,
|
||||
ClassFileDumper dumper) {
|
||||
int flags = HIDDEN_CLASS | ClassOption.optionsToFlag(options);
|
||||
if (accessVmAnnotations | VM.isSystemDomainLoader(lookupClass.getClassLoader())) {
|
||||
// jdk.internal.vm.annotations are permitted for classes
|
||||
@ -2414,24 +2454,26 @@ public class MethodHandles {
|
||||
flags |= ACCESS_VM_ANNOTATIONS;
|
||||
}
|
||||
|
||||
return new ClassDefiner(this, cf, flags);
|
||||
return new ClassDefiner(this, cf, flags, dumper);
|
||||
}
|
||||
|
||||
static class ClassDefiner {
|
||||
private final Lookup lookup;
|
||||
private final String name;
|
||||
private final String name; // internal name
|
||||
private final byte[] bytes;
|
||||
private final int classFlags;
|
||||
private final ClassFileDumper dumper;
|
||||
|
||||
private ClassDefiner(Lookup lookup, ClassFile cf, int flags) {
|
||||
private ClassDefiner(Lookup lookup, ClassFile cf, int flags, ClassFileDumper dumper) {
|
||||
assert ((flags & HIDDEN_CLASS) != 0 || (flags & STRONG_LOADER_LINK) == STRONG_LOADER_LINK);
|
||||
this.lookup = lookup;
|
||||
this.bytes = cf.bytes;
|
||||
this.name = cf.name;
|
||||
this.classFlags = flags;
|
||||
this.dumper = dumper;
|
||||
}
|
||||
|
||||
String className() {
|
||||
String internalName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -2458,12 +2500,35 @@ public class MethodHandles {
|
||||
Class<?> lookupClass = lookup.lookupClass();
|
||||
ClassLoader loader = lookupClass.getClassLoader();
|
||||
ProtectionDomain pd = (loader != null) ? lookup.lookupClassProtectionDomain() : null;
|
||||
Class<?> c = SharedSecrets.getJavaLangAccess()
|
||||
.defineClass(loader, lookupClass, name, bytes, pd, initialize, classFlags, classData);
|
||||
assert !isNestmate() || c.getNestHost() == lookupClass.getNestHost();
|
||||
return c;
|
||||
Class<?> c = null;
|
||||
try {
|
||||
c = SharedSecrets.getJavaLangAccess()
|
||||
.defineClass(loader, lookupClass, name, bytes, pd, initialize, classFlags, classData);
|
||||
assert !isNestmate() || c.getNestHost() == lookupClass.getNestHost();
|
||||
return c;
|
||||
} finally {
|
||||
// dump the classfile for debugging
|
||||
if (dumper.isEnabled()) {
|
||||
String name = internalName();
|
||||
if (c != null) {
|
||||
dumper.dumpClass(name, c, bytes);
|
||||
} else {
|
||||
dumper.dumpFailedClass(name, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the class of the given bytes and the given classData.
|
||||
* If {@code initialize} parameter is true, then the class will be initialized.
|
||||
*
|
||||
* @param initialize true if the class to be initialized
|
||||
* @param classData classData or null
|
||||
* @return a Lookup for the defined class
|
||||
*
|
||||
* @throws LinkageError linkage error
|
||||
*/
|
||||
Lookup defineClassAsLookup(boolean initialize, Object classData) {
|
||||
Class<?> c = defineClass(initialize, classData);
|
||||
return new Lookup(c, null, FULL_POWER_MODES);
|
||||
|
@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2021, 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 sun.util.logging.PlatformLogger;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Helper class used by InnerClassLambdaMetafactory to log generated classes
|
||||
*
|
||||
* @implNote
|
||||
* <p> Because this class is called by LambdaMetafactory, make use
|
||||
* of lambda lead to recursive calls cause stack overflow.
|
||||
*/
|
||||
final class ProxyClassesDumper {
|
||||
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 final Path dumpDir;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public static ProxyClassesDumper getInstance(String path) {
|
||||
if (null == path) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
path = path.trim();
|
||||
final Path dir = Path.of(path.isEmpty() ? "." : path);
|
||||
AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
@Override
|
||||
public Void run() {
|
||||
validateDumpDir(dir);
|
||||
return null;
|
||||
}
|
||||
}, null, new FilePermission("<<ALL FILES>>", "read, write"));
|
||||
return new ProxyClassesDumper(dir);
|
||||
} catch (InvalidPathException ex) {
|
||||
PlatformLogger.getLogger(ProxyClassesDumper.class.getName())
|
||||
.warning("Path " + path + " is not valid - dumping disabled", ex);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
PlatformLogger.getLogger(ProxyClassesDumper.class.getName())
|
||||
.warning(iae.getMessage() + " - dumping disabled");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ProxyClassesDumper(Path path) {
|
||||
dumpDir = Objects.requireNonNull(path);
|
||||
}
|
||||
|
||||
private static void validateDumpDir(Path path) {
|
||||
if (!Files.exists(path)) {
|
||||
throw new IllegalArgumentException("Directory " + path + " does not exist");
|
||||
} else if (!Files.isDirectory(path)) {
|
||||
throw new IllegalArgumentException("Path " + path + " is not a directory");
|
||||
} else if (!Files.isWritable(path)) {
|
||||
throw new IllegalArgumentException("Directory " + path + " is not writable");
|
||||
}
|
||||
}
|
||||
|
||||
public 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();
|
||||
}
|
||||
|
||||
public void dumpClass(String className, final byte[] classBytes) {
|
||||
Path file;
|
||||
try {
|
||||
file = dumpDir.resolve(encodeForFilename(className) + ".class");
|
||||
} catch (InvalidPathException ex) {
|
||||
PlatformLogger.getLogger(ProxyClassesDumper.class.getName())
|
||||
.warning("Invalid path for class " + className);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Path dir = file.getParent();
|
||||
Files.createDirectories(dir);
|
||||
Files.write(file, classBytes);
|
||||
} catch (Exception ignore) {
|
||||
PlatformLogger.getLogger(ProxyClassesDumper.class.getName())
|
||||
.warning("Exception writing to path at " + file.toString());
|
||||
// simply don't care if this operation failed
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2023, 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
|
||||
@ -167,12 +167,6 @@ public interface JavaLangInvokeAccess {
|
||||
*/
|
||||
MethodHandle reflectiveInvoker(Class<?> caller);
|
||||
|
||||
/**
|
||||
* Defines a hidden class of the given name and bytes with class data.
|
||||
* The given bytes is trusted.
|
||||
*/
|
||||
Lookup defineHiddenClassWithClassData(Lookup caller, String name, byte[] bytes, Object classData, boolean initialize);
|
||||
|
||||
/**
|
||||
* A best-effort method that tries to find any exceptions thrown by the given method handle.
|
||||
* @param handle the handle to check
|
||||
|
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2023, 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 jdk.internal.util;
|
||||
|
||||
import jdk.internal.misc.VM;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.PropertyPermission;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* ClassFile dumper utility class to log normal and hidden classes.
|
||||
*
|
||||
* @implNote
|
||||
* Because this class is called by MethodHandleStatics, LambdaForms generation
|
||||
* and LambdaMetafactory, make use of lambda lead to recursive calls cause stack overflow.
|
||||
*/
|
||||
public final class ClassFileDumper {
|
||||
private static final ConcurrentHashMap<String, ClassFileDumper> DUMPER_MAP
|
||||
= new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Returns a ClassFileDumper instance for the given key. To enable
|
||||
* dumping of the generated classes, set the system property via
|
||||
* -D<key>=<path>.
|
||||
*
|
||||
* The system property is read only once when it is the first time
|
||||
* the dumper instance for the given key is created.
|
||||
*
|
||||
* If not enabled, this method returns ClassFileDumper with null
|
||||
* dump path.
|
||||
*/
|
||||
public static ClassFileDumper getInstance(String key) {
|
||||
Objects.requireNonNull(key);
|
||||
|
||||
var dumper = DUMPER_MAP.get(key);
|
||||
if (dumper == null) {
|
||||
String path = GetPropertyAction.privilegedGetProperty(key);
|
||||
Path dir;
|
||||
if (path == null || path.trim().isEmpty()) {
|
||||
dir = null;
|
||||
} else {
|
||||
dir = validateDumpDir(Path.of(path.trim()));
|
||||
}
|
||||
var newDumper = new ClassFileDumper(key, dir);
|
||||
var v = DUMPER_MAP.putIfAbsent(key, newDumper);
|
||||
dumper = v != null ? v : newDumper;
|
||||
}
|
||||
return dumper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ClassFileDumper instance for the given key with a given
|
||||
* dump path. To enable dumping of the generated classes
|
||||
* -D<key> or -D<key>=true
|
||||
*
|
||||
* The system property is read only once when it is the first time
|
||||
* the dumper instance for the given key is created.
|
||||
*
|
||||
* If not enabled, this method returns ClassFileDumper with null
|
||||
* dump path.
|
||||
*/
|
||||
public static ClassFileDumper getInstance(String key, Path path) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(path);
|
||||
|
||||
var dumper = DUMPER_MAP.get(key);
|
||||
if (dumper == null) {
|
||||
String value = GetPropertyAction.privilegedGetProperty(key);
|
||||
boolean enabled = value != null && value.isEmpty()
|
||||
? true : Boolean.parseBoolean(value);
|
||||
Path dir = enabled ? validateDumpDir(path) : null;
|
||||
var newDumper = new ClassFileDumper(key, dir);
|
||||
var v = DUMPER_MAP.putIfAbsent(key, newDumper);
|
||||
dumper = v != null ? v : newDumper;
|
||||
}
|
||||
|
||||
if (dumper.isEnabled() && !path.equals(dumper.dumpPath())) {
|
||||
throw new IllegalArgumentException("mismatched dump path for " + key);
|
||||
}
|
||||
return dumper;
|
||||
}
|
||||
|
||||
private final String key;
|
||||
private final Path dumpDir;
|
||||
private final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
private ClassFileDumper(String key, Path path) {
|
||||
this.key = key;
|
||||
this.dumpDir = path;
|
||||
}
|
||||
|
||||
public String key() {
|
||||
return key;
|
||||
}
|
||||
public boolean isEnabled() {
|
||||
return dumpDir != null;
|
||||
}
|
||||
|
||||
public Path dumpPath() {
|
||||
return dumpDir;
|
||||
}
|
||||
|
||||
public Path pathname(String internalName) {
|
||||
return dumpDir.resolve(encodeForFilename(internalName) + ".class");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines the path name from the given name and {@code Class}
|
||||
* object. If it is a hidden class, it will dump the given bytes at
|
||||
* a path of the given name with a suffix "." concatenated
|
||||
* with the suffix of the hidden class name.
|
||||
*/
|
||||
public void dumpClass(String name, Class<?> c, byte[] bytes) {
|
||||
if (!isEnabled()) return;
|
||||
|
||||
String cn = c.getName();
|
||||
int suffixIdx = cn.lastIndexOf('/');
|
||||
if (suffixIdx > 0) {
|
||||
name += '.' + cn.substring(suffixIdx + 1);
|
||||
}
|
||||
write(pathname(name), bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method dumps the given bytes at a path of the given name with
|
||||
* a suffix ".failed-$COUNTER" where $COUNTER will be incremented
|
||||
* for each time this method is called.
|
||||
*/
|
||||
public void dumpFailedClass(String name, byte[] bytes) {
|
||||
if (!isEnabled()) return;
|
||||
|
||||
write(pathname(name + ".failed-" + counter.incrementAndGet()), bytes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private void write(Path path, byte[] bytes) {
|
||||
AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
@Override public Void run() {
|
||||
try {
|
||||
Path dir = path.getParent();
|
||||
Files.createDirectories(dir);
|
||||
Files.write(path, bytes);
|
||||
} catch (Exception ex) {
|
||||
if (VM.isModuleSystemInited()) {
|
||||
// log only when lambda is ready to use
|
||||
System.getLogger(ClassFileDumper.class.getName())
|
||||
.log(System.Logger.Level.WARNING, "Exception writing to " +
|
||||
path.toString() + " " + ex.getMessage());
|
||||
}
|
||||
// simply don't care if this operation failed
|
||||
}
|
||||
return null;
|
||||
}},
|
||||
null,
|
||||
new FilePermission("<<ALL FILES>>", "read, write"),
|
||||
// createDirectories may need it
|
||||
new PropertyPermission("user.dir", "read"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static Path validateDumpDir(Path path) {
|
||||
return AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
@Override
|
||||
public Path run() {
|
||||
try {
|
||||
Files.createDirectories(path);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException("Fail to create " + path, ex);
|
||||
}
|
||||
if (!Files.isDirectory(path)) {
|
||||
throw new IllegalArgumentException("Path " + path + " is not a directory");
|
||||
} else if (!Files.isWritable(path)) {
|
||||
throw new IllegalArgumentException("Directory " + path + " is not writable");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final HexFormat HEX = HexFormat.of().withUpperCase();
|
||||
private static final Set<Character> BAD_CHARS = Set.of('\\', ':', '*', '?', '"', '<', '>', '|');
|
||||
|
||||
private static String encodeForFilename(String className) {
|
||||
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 || BAD_CHARS.contains(c)) {
|
||||
sb.append('%');
|
||||
HEX.toHexDigits(sb, (byte)c);
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -51,6 +51,7 @@ import static java.nio.file.Files.*;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public class LambdaAsm {
|
||||
static final Path DUMP_LAMBDA_PROXY_CLASS_FILES = Path.of("DUMP_LAMBDA_PROXY_CLASS_FILES");
|
||||
|
||||
static final File TestFile = new File("A.java");
|
||||
|
||||
@ -58,7 +59,7 @@ public class LambdaAsm {
|
||||
emitCode();
|
||||
LUtils.compile(TestFile.getName());
|
||||
LUtils.TestResult tr = LUtils.doExec(LUtils.JAVA_CMD.getAbsolutePath(),
|
||||
"-Djdk.internal.lambda.dumpProxyClasses=.",
|
||||
"-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true",
|
||||
"-cp", ".", "A");
|
||||
if (tr.exitValue != 0) {
|
||||
System.out.println("Error: " + tr.toString());
|
||||
@ -134,7 +135,7 @@ public class LambdaAsm {
|
||||
static void verifyInvokerBytecodeGenerator() throws Exception {
|
||||
int count = 0;
|
||||
int mcount = 0;
|
||||
try (DirectoryStream<Path> ds = newDirectoryStream(new File(".").toPath(),
|
||||
try (DirectoryStream<Path> ds = newDirectoryStream(DUMP_LAMBDA_PROXY_CLASS_FILES,
|
||||
// filter in lambda proxy classes
|
||||
"A$I$$Lambda.*.class")) {
|
||||
for (Path p : ds) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2023, 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
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8023524
|
||||
* @bug 8023524 8304846
|
||||
* @summary tests logging generated classes for lambda
|
||||
* @library /java/nio/file
|
||||
* @modules jdk.compiler
|
||||
@ -51,6 +51,7 @@ import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
public class LogGeneratedClassesTest extends LUtils {
|
||||
static final Path DUMP_LAMBDA_PROXY_CLASS_FILES = Path.of("DUMP_LAMBDA_PROXY_CLASS_FILES");
|
||||
String longFQCN;
|
||||
|
||||
@BeforeClass
|
||||
@ -93,20 +94,14 @@ public class LogGeneratedClassesTest extends LUtils {
|
||||
test = new File("LongPackageName.java");
|
||||
createFile(test, scratch);
|
||||
compile("-d", ".", test.getName());
|
||||
|
||||
// create target
|
||||
Files.createDirectory(Paths.get("dump"));
|
||||
Files.createDirectories(Paths.get("dumpLong/com/example/nonsense"));
|
||||
Files.createFile(Paths.get("dumpLong/com/example/nonsense/nonsense"));
|
||||
Files.createFile(Paths.get("file"));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void cleanup() throws IOException {
|
||||
Files.delete(Paths.get("TestLambda.java"));
|
||||
Files.delete(Paths.get("LongPackageName.java"));
|
||||
Files.delete(Paths.get("file"));
|
||||
TestUtil.removeAll(Paths.get("com"));
|
||||
TestUtil.removeAll(DUMP_LAMBDA_PROXY_CLASS_FILES);
|
||||
TestUtil.removeAll(Paths.get("notDir"));
|
||||
TestUtil.removeAll(Paths.get("dump"));
|
||||
TestUtil.removeAll(Paths.get("dumpLong"));
|
||||
}
|
||||
@ -122,52 +117,68 @@ public class LogGeneratedClassesTest extends LUtils {
|
||||
|
||||
@Test
|
||||
public void testLogging() throws IOException {
|
||||
assertTrue(Files.exists(Paths.get("dump")));
|
||||
Path testDir = Path.of("dump");
|
||||
Path dumpDir = testDir.resolve(DUMP_LAMBDA_PROXY_CLASS_FILES);
|
||||
Files.createDirectory(testDir);
|
||||
TestResult tr = doExec(JAVA_CMD.getAbsolutePath(),
|
||||
"-cp", ".",
|
||||
"-cp", "..",
|
||||
"-Duser.dir=" + testDir.toAbsolutePath(),
|
||||
"-Djava.security.manager=allow",
|
||||
"-Djdk.internal.lambda.dumpProxyClasses=dump",
|
||||
"-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles",
|
||||
"com.example.TestLambda");
|
||||
// 2 our own class files. We don't care about the others
|
||||
assertEquals(Files.find(
|
||||
Paths.get("dump"),
|
||||
dumpDir,
|
||||
99,
|
||||
(p, a) -> p.startsWith(Paths.get("dump/com/example"))
|
||||
(p, a) -> p.startsWith(dumpDir.resolve("com/example"))
|
||||
&& a.isRegularFile()).count(),
|
||||
2, "Two lambda captured");
|
||||
2, "Two lambda captured");
|
||||
tr.assertZero("Should still return 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDumpDirNotExist() throws IOException {
|
||||
assertFalse(Files.exists(Paths.get("notExist")));
|
||||
Path testDir = Path.of("NotExist");
|
||||
Path dumpDir = testDir.resolve(DUMP_LAMBDA_PROXY_CLASS_FILES);
|
||||
Files.createDirectory(testDir);
|
||||
TestUtil.removeAll(dumpDir);
|
||||
|
||||
assertFalse(Files.exists(dumpDir));
|
||||
TestResult tr = doExec(JAVA_CMD.getAbsolutePath(),
|
||||
"-cp", ".",
|
||||
"-cp", "..",
|
||||
"-Duser.dir=" + testDir.toAbsolutePath(),
|
||||
"-Djava.security.manager=allow",
|
||||
"-Djdk.internal.lambda.dumpProxyClasses=notExist",
|
||||
"-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles",
|
||||
"com.example.TestLambda");
|
||||
assertEquals(tr.testOutput.stream()
|
||||
.filter(s -> s.startsWith("WARNING"))
|
||||
.filter(s -> s.contains("does not exist"))
|
||||
.count(),
|
||||
1, "only show error once");
|
||||
|
||||
// The dump directory will be created if not exist
|
||||
assertEquals(Files.find(
|
||||
dumpDir,
|
||||
99,
|
||||
(p, a) -> p.startsWith(dumpDir.resolve("com/example"))
|
||||
&& a.isRegularFile()).count(),
|
||||
2, "Two lambda captured");
|
||||
tr.assertZero("Should still return 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDumpDirIsFile() throws IOException {
|
||||
assertTrue(Files.isRegularFile(Paths.get("file")));
|
||||
Path testDir = Path.of("notDir");
|
||||
Path dumpFile = testDir.resolve(DUMP_LAMBDA_PROXY_CLASS_FILES);
|
||||
Files.createDirectory(testDir);
|
||||
Files.createFile(dumpFile);
|
||||
assertTrue(Files.isRegularFile(dumpFile));
|
||||
TestResult tr = doExec(JAVA_CMD.getAbsolutePath(),
|
||||
"-cp", ".",
|
||||
"-cp", "..",
|
||||
"-Duser.dir=" + testDir.toAbsolutePath(),
|
||||
"-Djava.security.manager=allow",
|
||||
"-Djdk.internal.lambda.dumpProxyClasses=file",
|
||||
"-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles",
|
||||
"com.example.TestLambda");
|
||||
assertEquals(tr.testOutput.stream()
|
||||
.filter(s -> s.startsWith("WARNING"))
|
||||
.filter(s -> s.contains("not a directory"))
|
||||
.filter(s -> s.contains("java.nio.file.FileAlreadyExistsException"))
|
||||
.count(),
|
||||
1, "only show error once");
|
||||
tr.assertZero("Should still return 0");
|
||||
assertTrue(tr.exitValue !=0);
|
||||
}
|
||||
|
||||
private static boolean isWriteableDirectory(Path p) {
|
||||
@ -206,59 +217,66 @@ public class LogGeneratedClassesTest extends LUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
Files.createDirectory(Paths.get("readOnly"),
|
||||
Path testDir = Path.of("readOnly");
|
||||
Path dumpDir = testDir.resolve(DUMP_LAMBDA_PROXY_CLASS_FILES);
|
||||
Files.createDirectory(testDir);
|
||||
Files.createDirectory(dumpDir,
|
||||
asFileAttribute(fromString("r-xr-xr-x")));
|
||||
try {
|
||||
if (isWriteableDirectory(Paths.get("readOnly"))) {
|
||||
if (isWriteableDirectory(dumpDir)) {
|
||||
// Skipping the test: it's allowed to write into read-only directory
|
||||
// (e.g. current user is super user).
|
||||
System.out.println("WARNING: readOnly directory is writeable. Skipping testDumpDirNotWritable test.");
|
||||
System.out.println("WARNING: The dump directory is writeable. Skipping testDumpDirNotWritable test.");
|
||||
return;
|
||||
}
|
||||
|
||||
TestResult tr = doExec(JAVA_CMD.getAbsolutePath(),
|
||||
"-cp", ".",
|
||||
"-cp", "..",
|
||||
"-Duser.dir=" + testDir.toAbsolutePath(),
|
||||
"-Djava.security.manager=allow",
|
||||
"-Djdk.internal.lambda.dumpProxyClasses=readOnly",
|
||||
"-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles",
|
||||
"com.example.TestLambda");
|
||||
assertEquals(tr.testOutput.stream()
|
||||
.filter(s -> s.startsWith("WARNING"))
|
||||
.filter(s -> s.contains("not writable"))
|
||||
.filter(s -> s.contains("is not writable"))
|
||||
.count(),
|
||||
1, "only show error once");
|
||||
tr.assertZero("Should still return 0");
|
||||
assertTrue(tr.exitValue != 0);
|
||||
} finally {
|
||||
TestUtil.removeAll(Paths.get("readOnly"));
|
||||
TestUtil.removeAll(testDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoggingException() throws IOException {
|
||||
assertTrue(Files.exists(Paths.get("dumpLong")));
|
||||
Path testDir = Path.of("dumpLong");
|
||||
Path dumpDir = testDir.resolve(DUMP_LAMBDA_PROXY_CLASS_FILES);
|
||||
Files.createDirectories(dumpDir.resolve("com/example/nonsense"));
|
||||
Files.createFile(dumpDir.resolve("com/example/nonsense/nonsense"));
|
||||
TestResult tr = doExec(JAVA_CMD.getAbsolutePath(),
|
||||
"-cp", ".",
|
||||
"-Djava.security.manager=allow",
|
||||
"-Djdk.internal.lambda.dumpProxyClasses=dumpLong",
|
||||
"-cp", "..",
|
||||
"-Duser.dir=" + testDir.toAbsolutePath(),
|
||||
"-Djava.security.manager=allow",
|
||||
"-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles",
|
||||
longFQCN);
|
||||
assertEquals(tr.testOutput.stream()
|
||||
.filter(s -> s.startsWith("WARNING: Exception"))
|
||||
.count(),
|
||||
2, "show error each capture");
|
||||
// dumpLong/com/example/nonsense/nonsense
|
||||
Path dumpPath = Paths.get("dumpLong/com/example/nonsense");
|
||||
// dumpLong/DUMP_LAMBDA_PROXY_CLASS_FILES/com/example/nonsense/nonsense
|
||||
Path dumpPath = dumpDir.resolve("com/example/nonsense");
|
||||
Predicate<Path> filter = p -> p.getParent() == null || dumpPath.startsWith(p) || p.startsWith(dumpPath);
|
||||
boolean debug = true;
|
||||
if (debug) {
|
||||
Files.walk(Paths.get("dumpLong"))
|
||||
Files.walk(dumpDir)
|
||||
.forEachOrdered(p -> {
|
||||
if (filter.test(p)) {
|
||||
System.out.println("accepted: " + p.toString());
|
||||
} else {
|
||||
System.out.println("filetered out: " + p.toString());
|
||||
System.out.println("filtered out: " + p.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
assertEquals(Files.walk(Paths.get("dumpLong"))
|
||||
assertEquals(Files.walk(dumpDir)
|
||||
.filter(filter)
|
||||
.count(), 5, "Two lambda captured failed to log");
|
||||
tr.assertZero("Should still return 0");
|
||||
|
Loading…
Reference in New Issue
Block a user