mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
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
11 changed files with 440 additions and 393 deletions
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue