8340831: Simplify simple validation for class definition in MethodHandles.Lookup
Reviewed-by: redestad
This commit is contained in:
parent
df1959fd7a
commit
84751cbfdd
@ -39,7 +39,9 @@ import sun.invoke.util.Wrapper;
|
|||||||
import sun.reflect.misc.ReflectUtil;
|
import sun.reflect.misc.ReflectUtil;
|
||||||
import sun.security.util.SecurityConstants;
|
import sun.security.util.SecurityConstants;
|
||||||
|
|
||||||
|
import java.lang.classfile.ClassFile;
|
||||||
import java.lang.classfile.ClassModel;
|
import java.lang.classfile.ClassModel;
|
||||||
|
import java.lang.constant.ClassDesc;
|
||||||
import java.lang.constant.ConstantDescs;
|
import java.lang.constant.ConstantDescs;
|
||||||
import java.lang.invoke.LambdaForm.BasicType;
|
import java.lang.invoke.LambdaForm.BasicType;
|
||||||
import java.lang.invoke.MethodHandleImpl.Intrinsic;
|
import java.lang.invoke.MethodHandleImpl.Intrinsic;
|
||||||
@ -2242,85 +2244,70 @@ public class MethodHandles {
|
|||||||
private static final ClassFileDumper DEFAULT_DUMPER = ClassFileDumper.getInstance(
|
private static final ClassFileDumper DEFAULT_DUMPER = ClassFileDumper.getInstance(
|
||||||
"jdk.invoke.MethodHandle.dumpClassFiles", "DUMP_CLASS_FILES");
|
"jdk.invoke.MethodHandle.dumpClassFiles", "DUMP_CLASS_FILES");
|
||||||
|
|
||||||
static class ClassFile {
|
/**
|
||||||
final String name; // internal name
|
* This method checks the class file version and the structure of `this_class`.
|
||||||
final int accessFlags;
|
* and checks if the bytes is a class or interface (ACC_MODULE flag not set)
|
||||||
final byte[] bytes;
|
* that is in the named package.
|
||||||
ClassFile(String name, int accessFlags, byte[] bytes) {
|
*
|
||||||
this.name = name;
|
* @throws IllegalArgumentException if ACC_MODULE flag is set in access flags
|
||||||
this.accessFlags = accessFlags;
|
* or the class is not in the given package name.
|
||||||
this.bytes = bytes;
|
*/
|
||||||
|
static String validateAndFindInternalName(byte[] bytes, String pkgName) {
|
||||||
|
int magic = readInt(bytes, 0);
|
||||||
|
if (magic != ClassFile.MAGIC_NUMBER) {
|
||||||
|
throw new ClassFormatError("Incompatible magic value: " + magic);
|
||||||
|
}
|
||||||
|
// We have to read major and minor this way as ClassFile API throws IAE
|
||||||
|
// yet we want distinct ClassFormatError and UnsupportedClassVersionError
|
||||||
|
int minor = readUnsignedShort(bytes, 4);
|
||||||
|
int major = readUnsignedShort(bytes, 6);
|
||||||
|
|
||||||
|
if (!VM.isSupportedClassFileVersion(major, minor)) {
|
||||||
|
throw new UnsupportedClassVersionError("Unsupported class file version " + major + "." + minor);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ClassFile newInstanceNoCheck(String name, byte[] bytes) {
|
String name;
|
||||||
return new ClassFile(name, 0, bytes);
|
ClassDesc sym;
|
||||||
|
int accessFlags;
|
||||||
|
try {
|
||||||
|
ClassModel cm = ClassFile.of().parse(bytes);
|
||||||
|
var thisClass = cm.thisClass();
|
||||||
|
name = thisClass.asInternalName();
|
||||||
|
sym = thisClass.asSymbol();
|
||||||
|
accessFlags = cm.flags().flagsMask();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
ClassFormatError cfe = new ClassFormatError();
|
||||||
|
cfe.initCause(e);
|
||||||
|
throw cfe;
|
||||||
|
}
|
||||||
|
// must be a class or interface
|
||||||
|
if ((accessFlags & ACC_MODULE) != 0) {
|
||||||
|
throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
String pn = sym.packageName();
|
||||||
* This method checks the class file version and the structure of `this_class`.
|
if (!pn.equals(pkgName)) {
|
||||||
* and checks if the bytes is a class or interface (ACC_MODULE flag not set)
|
throw newIllegalArgumentException(name + " not in same package as lookup class");
|
||||||
* that is in the named package.
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if ACC_MODULE flag is set in access flags
|
|
||||||
* 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) {
|
return name;
|
||||||
int magic = readInt(bytes, 0);
|
}
|
||||||
if (magic != 0xCAFEBABE) {
|
|
||||||
throw new ClassFormatError("Incompatible magic value: " + magic);
|
|
||||||
}
|
|
||||||
int minor = readUnsignedShort(bytes, 4);
|
|
||||||
int major = readUnsignedShort(bytes, 6);
|
|
||||||
if (!VM.isSupportedClassFileVersion(major, minor)) {
|
|
||||||
throw new UnsupportedClassVersionError("Unsupported class file version " + major + "." + minor);
|
|
||||||
}
|
|
||||||
|
|
||||||
String name;
|
private static int readInt(byte[] bytes, int offset) {
|
||||||
int accessFlags;
|
if ((offset + 4) > bytes.length) {
|
||||||
try {
|
throw new ClassFormatError("Invalid ClassFile structure");
|
||||||
ClassModel cm = java.lang.classfile.ClassFile.of().parse(bytes);
|
|
||||||
name = cm.thisClass().asInternalName();
|
|
||||||
accessFlags = cm.flags().flagsMask();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
ClassFormatError cfe = new ClassFormatError();
|
|
||||||
cfe.initCause(e);
|
|
||||||
throw cfe;
|
|
||||||
}
|
|
||||||
// must be a class or interface
|
|
||||||
if ((accessFlags & ACC_MODULE) != 0) {
|
|
||||||
throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set");
|
|
||||||
}
|
|
||||||
return new ClassFile(name, accessFlags, bytes);
|
|
||||||
}
|
}
|
||||||
|
return ((bytes[offset] & 0xFF) << 24)
|
||||||
|
| ((bytes[offset + 1] & 0xFF) << 16)
|
||||||
|
| ((bytes[offset + 2] & 0xFF) << 8)
|
||||||
|
| (bytes[offset + 3] & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
private static int readInt(byte[] bytes, int offset) {
|
private static int readUnsignedShort(byte[] bytes, int offset) {
|
||||||
if ((offset+4) > bytes.length) {
|
if ((offset+2) > bytes.length) {
|
||||||
throw new ClassFormatError("Invalid ClassFile structure");
|
throw new ClassFormatError("Invalid ClassFile structure");
|
||||||
}
|
|
||||||
return ((bytes[offset] & 0xFF) << 24)
|
|
||||||
| ((bytes[offset + 1] & 0xFF) << 16)
|
|
||||||
| ((bytes[offset + 2] & 0xFF) << 8)
|
|
||||||
| (bytes[offset + 3] & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int readUnsignedShort(byte[] bytes, int offset) {
|
|
||||||
if ((offset+2) > bytes.length) {
|
|
||||||
throw new ClassFormatError("Invalid ClassFile structure");
|
|
||||||
}
|
|
||||||
return ((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF);
|
|
||||||
}
|
}
|
||||||
|
return ((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2334,23 +2321,22 @@ public class MethodHandles {
|
|||||||
* {@code bytes} denotes a class in a different package than the lookup class
|
* {@code bytes} denotes a class in a different package than the lookup class
|
||||||
*/
|
*/
|
||||||
private ClassDefiner makeClassDefiner(byte[] bytes) {
|
private ClassDefiner makeClassDefiner(byte[] bytes) {
|
||||||
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
|
var internalName = validateAndFindInternalName(bytes, lookupClass().getPackageName());
|
||||||
return new ClassDefiner(this, cf, STRONG_LOADER_LINK, defaultDumper());
|
return new ClassDefiner(this, internalName, bytes, STRONG_LOADER_LINK, defaultDumper());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a ClassDefiner that creates a {@code Class} object of a normal class
|
* 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.
|
* from the given bytes. No package name check on the given bytes.
|
||||||
*
|
*
|
||||||
* @param name internal name
|
* @param internalName internal name
|
||||||
* @param bytes class bytes
|
* @param bytes class bytes
|
||||||
* @param dumper dumper to write the given bytes to the dumper's output directory
|
* @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.
|
* @return ClassDefiner that defines a normal class of the given bytes.
|
||||||
*/
|
*/
|
||||||
ClassDefiner makeClassDefiner(String name, byte[] bytes, ClassFileDumper dumper) {
|
ClassDefiner makeClassDefiner(String internalName, byte[] bytes, ClassFileDumper dumper) {
|
||||||
// skip package name validation
|
// skip package name validation
|
||||||
ClassFile cf = ClassFile.newInstanceNoCheck(name, bytes);
|
return new ClassDefiner(this, internalName, bytes, STRONG_LOADER_LINK, dumper);
|
||||||
return new ClassDefiner(this, cf, STRONG_LOADER_LINK, dumper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2368,8 +2354,8 @@ public class MethodHandles {
|
|||||||
* {@code bytes} denotes a class in a different package than the lookup class
|
* {@code bytes} denotes a class in a different package than the lookup class
|
||||||
*/
|
*/
|
||||||
ClassDefiner makeHiddenClassDefiner(byte[] bytes, ClassFileDumper dumper) {
|
ClassDefiner makeHiddenClassDefiner(byte[] bytes, ClassFileDumper dumper) {
|
||||||
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
|
var internalName = validateAndFindInternalName(bytes, lookupClass().getPackageName());
|
||||||
return makeHiddenClassDefiner(cf, false, dumper, 0);
|
return makeHiddenClassDefiner(internalName, bytes, false, dumper, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2391,51 +2377,53 @@ public class MethodHandles {
|
|||||||
private ClassDefiner makeHiddenClassDefiner(byte[] bytes,
|
private ClassDefiner makeHiddenClassDefiner(byte[] bytes,
|
||||||
boolean accessVmAnnotations,
|
boolean accessVmAnnotations,
|
||||||
int flags) {
|
int flags) {
|
||||||
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
|
var internalName = validateAndFindInternalName(bytes, lookupClass().getPackageName());
|
||||||
return makeHiddenClassDefiner(cf, accessVmAnnotations, defaultDumper(), flags);
|
return makeHiddenClassDefiner(internalName, bytes, accessVmAnnotations, defaultDumper(), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
|
* 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 bytes.
|
* from the given bytes and the given options. No package name check on the given bytes.
|
||||||
*
|
*
|
||||||
* @param name internal name that specifies the prefix of the hidden class
|
* @param internalName internal name that specifies the prefix of the hidden class
|
||||||
* @param bytes class bytes
|
* @param bytes class bytes
|
||||||
* @param dumper dumper to write the given bytes to the dumper's output directory
|
* @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.
|
* @return ClassDefiner that defines a hidden class of the given bytes and options.
|
||||||
*/
|
*/
|
||||||
ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes, ClassFileDumper dumper) {
|
ClassDefiner makeHiddenClassDefiner(String internalName, byte[] bytes, ClassFileDumper dumper) {
|
||||||
Objects.requireNonNull(dumper);
|
Objects.requireNonNull(dumper);
|
||||||
// skip name and access flags validation
|
// skip name and access flags validation
|
||||||
return makeHiddenClassDefiner(ClassFile.newInstanceNoCheck(name, bytes), false, dumper, 0);
|
return makeHiddenClassDefiner(internalName, bytes, false, dumper, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
|
* 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 bytes.
|
* from the given bytes and the given options. No package name check on the given bytes.
|
||||||
*
|
*
|
||||||
* @param name internal name that specifies the prefix of the hidden class
|
* @param internalName internal name that specifies the prefix of the hidden class
|
||||||
* @param bytes class bytes
|
* @param bytes class bytes
|
||||||
* @param flags class options flag mask
|
* @param flags class options flag mask
|
||||||
* @param dumper dumper to write the given bytes to the dumper's output directory
|
* @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.
|
* @return ClassDefiner that defines a hidden class of the given bytes and options.
|
||||||
*/
|
*/
|
||||||
ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes, ClassFileDumper dumper, int flags) {
|
ClassDefiner makeHiddenClassDefiner(String internalName, byte[] bytes, ClassFileDumper dumper, int flags) {
|
||||||
Objects.requireNonNull(dumper);
|
Objects.requireNonNull(dumper);
|
||||||
// skip name and access flags validation
|
// skip name and access flags validation
|
||||||
return makeHiddenClassDefiner(ClassFile.newInstanceNoCheck(name, bytes), false, dumper, flags);
|
return makeHiddenClassDefiner(internalName, bytes, false, dumper, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
|
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
|
||||||
* from the given class file and options.
|
* from the given class file and options.
|
||||||
*
|
*
|
||||||
* @param cf ClassFile
|
* @param internalName internal name
|
||||||
|
* @param bytes Class byte array
|
||||||
* @param flags class option flag mask
|
* @param flags class option flag mask
|
||||||
* @param accessVmAnnotations true to give the hidden class access to VM annotations
|
* @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
|
* @param dumper dumper to write the given bytes to the dumper's output directory
|
||||||
*/
|
*/
|
||||||
private ClassDefiner makeHiddenClassDefiner(ClassFile cf,
|
private ClassDefiner makeHiddenClassDefiner(String internalName,
|
||||||
|
byte[] bytes,
|
||||||
boolean accessVmAnnotations,
|
boolean accessVmAnnotations,
|
||||||
ClassFileDumper dumper,
|
ClassFileDumper dumper,
|
||||||
int flags) {
|
int flags) {
|
||||||
@ -2446,27 +2434,12 @@ public class MethodHandles {
|
|||||||
flags |= ACCESS_VM_ANNOTATIONS;
|
flags |= ACCESS_VM_ANNOTATIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ClassDefiner(this, cf, flags, dumper);
|
return new ClassDefiner(this, internalName, bytes, flags, dumper);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ClassDefiner {
|
record ClassDefiner(Lookup lookup, String internalName, byte[] bytes, int classFlags, ClassFileDumper dumper) {
|
||||||
private final Lookup lookup;
|
ClassDefiner {
|
||||||
private final String name; // internal name
|
assert ((classFlags & HIDDEN_CLASS) != 0 || (classFlags & STRONG_LOADER_LINK) == STRONG_LOADER_LINK);
|
||||||
private final byte[] bytes;
|
|
||||||
private final int classFlags;
|
|
||||||
private final ClassFileDumper dumper;
|
|
||||||
|
|
||||||
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 internalName() {
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> defineClass(boolean initialize) {
|
Class<?> defineClass(boolean initialize) {
|
||||||
@ -2495,7 +2468,7 @@ public class MethodHandles {
|
|||||||
Class<?> c = null;
|
Class<?> c = null;
|
||||||
try {
|
try {
|
||||||
c = SharedSecrets.getJavaLangAccess()
|
c = SharedSecrets.getJavaLangAccess()
|
||||||
.defineClass(loader, lookupClass, name, bytes, pd, initialize, classFlags, classData);
|
.defineClass(loader, lookupClass, internalName, bytes, pd, initialize, classFlags, classData);
|
||||||
assert !isNestmate() || c.getNestHost() == lookupClass.getNestHost();
|
assert !isNestmate() || c.getNestHost() == lookupClass.getNestHost();
|
||||||
return c;
|
return c;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -28,6 +28,7 @@ package jdk.internal.misc;
|
|||||||
import static java.lang.Thread.State.*;
|
import static java.lang.Thread.State.*;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
import java.lang.classfile.ClassFile;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -158,10 +159,6 @@ public class VM {
|
|||||||
return pageAlignDirectMemory;
|
return pageAlignDirectMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int classFileMajorVersion;
|
|
||||||
private static int classFileMinorVersion;
|
|
||||||
private static final int PREVIEW_MINOR_VERSION = 65535;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests if the given version is a supported {@code class}
|
* Tests if the given version is a supported {@code class}
|
||||||
* file version.
|
* file version.
|
||||||
@ -175,11 +172,11 @@ public class VM {
|
|||||||
* @jvms 4.1 Table 4.1-A. class file format major versions
|
* @jvms 4.1 Table 4.1-A. class file format major versions
|
||||||
*/
|
*/
|
||||||
public static boolean isSupportedClassFileVersion(int major, int minor) {
|
public static boolean isSupportedClassFileVersion(int major, int minor) {
|
||||||
if (major < 45 || major > classFileMajorVersion) return false;
|
if (major < ClassFile.JAVA_1_VERSION || major > ClassFile.latestMajorVersion()) return false;
|
||||||
// for major version is between 45 and 55 inclusive, the minor version may be any value
|
// for major version is between 45 and 55 inclusive, the minor version may be any value
|
||||||
if (major < 56) return true;
|
if (major < ClassFile.JAVA_12_VERSION) return true;
|
||||||
// otherwise, the minor version must be 0 or 65535
|
// otherwise, the minor version must be 0 or 65535
|
||||||
return minor == 0 || minor == PREVIEW_MINOR_VERSION;
|
return minor == 0 || (minor == ClassFile.PREVIEW_MINOR_VERSION && major == ClassFile.latestMajorVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -189,12 +186,8 @@ public class VM {
|
|||||||
* major.minor version >= 53.0
|
* major.minor version >= 53.0
|
||||||
*/
|
*/
|
||||||
public static boolean isSupportedModuleDescriptorVersion(int major, int minor) {
|
public static boolean isSupportedModuleDescriptorVersion(int major, int minor) {
|
||||||
if (major < 53 || major > classFileMajorVersion) return false;
|
if (major < ClassFile.JAVA_9_VERSION) return false;
|
||||||
// for major version is between 45 and 55 inclusive, the minor version may be any value
|
return isSupportedClassFileVersion(major, minor);
|
||||||
if (major < 56) return true;
|
|
||||||
// otherwise, the minor version must be 0 or 65535
|
|
||||||
// preview features do not apply to module-info.class but JVMS allows it
|
|
||||||
return minor == 0 || minor == PREVIEW_MINOR_VERSION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -271,15 +264,6 @@ public class VM {
|
|||||||
s = props.get("sun.nio.PageAlignDirectMemory");
|
s = props.get("sun.nio.PageAlignDirectMemory");
|
||||||
if ("true".equals(s))
|
if ("true".equals(s))
|
||||||
pageAlignDirectMemory = true;
|
pageAlignDirectMemory = true;
|
||||||
|
|
||||||
s = props.get("java.class.version");
|
|
||||||
int index = s.indexOf('.');
|
|
||||||
try {
|
|
||||||
classFileMajorVersion = Integer.parseInt(s.substring(0, index));
|
|
||||||
classFileMinorVersion = Integer.parseInt(s.substring(index + 1));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new InternalError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize any miscellaneous operating system settings that need to be
|
// Initialize any miscellaneous operating system settings that need to be
|
||||||
|
Loading…
Reference in New Issue
Block a user