mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +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
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue