mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
484 lines
20 KiB
Java
484 lines
20 KiB
Java
/*
|
|
* Copyright (c) 2018, 2024, 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.constant;
|
|
|
|
import jdk.internal.vm.annotation.Stable;
|
|
import sun.invoke.util.Wrapper;
|
|
|
|
import java.lang.constant.ClassDesc;
|
|
import java.lang.constant.ConstantDesc;
|
|
import java.lang.constant.ConstantDescs;
|
|
import java.lang.constant.MethodTypeDesc;
|
|
import java.lang.invoke.MethodType;
|
|
import java.util.Set;
|
|
|
|
import jdk.internal.access.JavaLangAccess;
|
|
import jdk.internal.access.SharedSecrets;
|
|
import static jdk.internal.constant.PrimitiveClassDescImpl.*;
|
|
|
|
/**
|
|
* Helper methods for the implementation of {@code java.lang.constant}.
|
|
*/
|
|
public final class ConstantUtils {
|
|
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
|
|
|
/** an empty constant descriptor */
|
|
public static final ConstantDesc[] EMPTY_CONSTANTDESC = new ConstantDesc[0];
|
|
public static final ClassDesc[] EMPTY_CLASSDESC = new ClassDesc[0];
|
|
public static final int MAX_ARRAY_TYPE_DESC_DIMENSIONS = 255;
|
|
public static final ClassDesc CD_module_info = binaryNameToDesc("module-info");
|
|
public static @Stable ClassDesc CD_Object_array; // set from ConstantDescs, avoid circular initialization
|
|
|
|
private static final Set<String> pointyNames = Set.of(ConstantDescs.INIT_NAME, ConstantDescs.CLASS_INIT_NAME);
|
|
|
|
/** No instantiation */
|
|
private ConstantUtils() {}
|
|
|
|
// Note:
|
|
// Non-JDK users should create their own utilities that wrap
|
|
// {@code .describeConstable().orElseThrow()} calls;
|
|
// these xxDesc methods has undefined and unsafe exceptional
|
|
// behavior, so they are not suitable as public APIs.
|
|
|
|
/**
|
|
* Creates a {@linkplain ClassDesc} from a pre-validated binary name
|
|
* for a class or interface type. Validated version of {@link
|
|
* ClassDesc#of(String)}.
|
|
*
|
|
* @param binaryName a binary name
|
|
*/
|
|
public static ClassDesc binaryNameToDesc(String binaryName) {
|
|
return internalNameToDesc(binaryToInternal(binaryName));
|
|
}
|
|
|
|
/**
|
|
* Creates a {@linkplain ClassDesc} from a pre-validated internal name
|
|
* for a class or interface type. Validated version of {@link
|
|
* ClassDesc#ofInternalName(String)}.
|
|
*
|
|
* @param internalName a binary name
|
|
*/
|
|
public static ClassDesc internalNameToDesc(String internalName) {
|
|
return ClassOrInterfaceDescImpl.ofValidated(concat("L", internalName, ";"));
|
|
}
|
|
|
|
/**
|
|
* Creates a ClassDesc from a Class object, requires that this class
|
|
* can always be described nominally, i.e. this class is not a
|
|
* hidden class or interface or an array with a hidden component
|
|
* type.
|
|
*/
|
|
public static ClassDesc classDesc(Class<?> type) {
|
|
if (type.isPrimitive()) {
|
|
return Wrapper.forPrimitiveType(type).basicClassDescriptor();
|
|
}
|
|
return referenceClassDesc(type);
|
|
}
|
|
|
|
/**
|
|
* Creates a ClassDesc from a Class object representing a non-hidden
|
|
* class or interface or an array type with a non-hidden component type.
|
|
*/
|
|
public static ClassDesc referenceClassDesc(Class<?> type) {
|
|
return referenceClassDesc(type.descriptorString());
|
|
}
|
|
|
|
/**
|
|
* Creates a {@linkplain ClassDesc} from a pre-validated descriptor string
|
|
* for a class or interface type or an array type.
|
|
*
|
|
* @param descriptor a field descriptor string for a class or interface type
|
|
* @jvms 4.3.2 Field Descriptors
|
|
*/
|
|
public static ClassDesc referenceClassDesc(String descriptor) {
|
|
if (descriptor.charAt(0) == '[') {
|
|
return ArrayClassDescImpl.ofValidatedDescriptor(descriptor);
|
|
}
|
|
return ClassOrInterfaceDescImpl.ofValidated(descriptor);
|
|
}
|
|
|
|
/**
|
|
* Creates a MethodTypeDesc from a MethodType object, requires that
|
|
* the type can be described nominally, i.e. all of its return
|
|
* type and parameter types can be described nominally.
|
|
*/
|
|
public static MethodTypeDesc methodTypeDesc(MethodType type) {
|
|
var returnDesc = classDesc(type.returnType());
|
|
if (type.parameterCount() == 0) {
|
|
return MethodTypeDescImpl.ofValidated(returnDesc, EMPTY_CLASSDESC);
|
|
}
|
|
var paramDescs = new ClassDesc[type.parameterCount()];
|
|
for (int i = 0; i < type.parameterCount(); i++) {
|
|
paramDescs[i] = classDesc(type.parameterType(i));
|
|
}
|
|
return MethodTypeDescImpl.ofValidated(returnDesc, paramDescs);
|
|
}
|
|
|
|
/**
|
|
* Creates a MethodTypeDesc from return class and parameter
|
|
* class objects, requires that all of them can be described nominally.
|
|
* This version is mainly useful for working with Method objects.
|
|
*/
|
|
public static MethodTypeDesc methodTypeDesc(Class<?> returnType, Class<?>[] parameterTypes) {
|
|
var returnDesc = classDesc(returnType);
|
|
if (parameterTypes.length == 0) {
|
|
return MethodTypeDescImpl.ofValidated(returnDesc, EMPTY_CLASSDESC);
|
|
}
|
|
var paramDescs = new ClassDesc[parameterTypes.length];
|
|
for (int i = 0; i < parameterTypes.length; i++) {
|
|
paramDescs[i] = classDesc(parameterTypes[i]);
|
|
}
|
|
return MethodTypeDescImpl.ofValidated(returnDesc, paramDescs);
|
|
}
|
|
|
|
/**
|
|
* Creates a {@linkplain ClassDesc} from a descriptor string for a class or
|
|
* interface type or an array type.
|
|
*
|
|
* @param descriptor a field descriptor string for a class or interface type
|
|
* @throws IllegalArgumentException if the descriptor string is not a valid
|
|
* field descriptor string, or does not describe a class or interface type
|
|
* @jvms 4.3.2 Field Descriptors
|
|
*/
|
|
public static ClassDesc parseReferenceTypeDesc(String descriptor) {
|
|
int dLen = descriptor.length();
|
|
int len = ConstantUtils.skipOverFieldSignature(descriptor, 0, dLen);
|
|
if (len <= 1 || len != dLen)
|
|
throw new IllegalArgumentException(String.format("not a valid reference type descriptor: %s", descriptor));
|
|
if (descriptor.charAt(0) == '[') {
|
|
return ArrayClassDescImpl.ofValidatedDescriptor(descriptor);
|
|
}
|
|
return ClassOrInterfaceDescImpl.ofValidated(descriptor);
|
|
}
|
|
|
|
/**
|
|
* Validates the correctness of a class or interface name or a package name.
|
|
* In particular checks for the presence of invalid characters,
|
|
* consecutive, leading, or trailing separator char, for both non-internal
|
|
* and internal forms, and the empty string for class or interface names.
|
|
*
|
|
* @param name the name
|
|
* @param slashSeparator {@code true} means {@code /} is the separator char
|
|
* (internal form); otherwise {@code .} is the separator char
|
|
* @param allowEmpty {@code true} means the empty string is a valid name
|
|
* @return the name passed if valid
|
|
* @throws IllegalArgumentException if the name is invalid
|
|
* @throws NullPointerException if name is {@code null}
|
|
*/
|
|
private static String validateClassOrPackageName(String name, boolean slashSeparator, boolean allowEmpty) {
|
|
int len = name.length(); // implicit null check
|
|
// empty name special rule
|
|
if (allowEmpty && len == 0)
|
|
return name;
|
|
// state variable for detection of illegal states of
|
|
// empty name, consecutive, leading, or trailing separators
|
|
int afterSeparator = 0;
|
|
for (int i = 0; i < len; i++) {
|
|
char ch = name.charAt(i);
|
|
// reject ';' or '['
|
|
if (ch == ';' || ch == '[')
|
|
throw invalidClassName(name);
|
|
// encounter a separator
|
|
boolean foundSlash = ch == '/';
|
|
if (foundSlash || ch == '.') {
|
|
// reject the other separator char
|
|
// reject consecutive or leading separators
|
|
if (foundSlash != slashSeparator || i == afterSeparator)
|
|
throw invalidClassName(name);
|
|
afterSeparator = i + 1;
|
|
}
|
|
}
|
|
// reject empty name or trailing separators
|
|
if (len == afterSeparator)
|
|
throw invalidClassName(name);
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Validates the correctness of a binary class name.
|
|
* In particular checks for the presence of invalid characters, empty
|
|
* name, consecutive, leading, or trailing {@code .}.
|
|
*
|
|
* @param name the class name
|
|
* @return the class name passed if valid
|
|
* @throws IllegalArgumentException if the class name is invalid
|
|
* @throws NullPointerException if class name is {@code null}
|
|
*/
|
|
public static String validateBinaryClassName(String name) {
|
|
return validateClassOrPackageName(name, false, false);
|
|
}
|
|
|
|
/**
|
|
* Validates the correctness of an internal class name.
|
|
* In particular checks for the presence of invalid characters, empty
|
|
* name, consecutive, leading, or trailing {@code /}.
|
|
*
|
|
* @param name the class name
|
|
* @return the class name passed if valid
|
|
* @throws IllegalArgumentException if the class name is invalid
|
|
* @throws NullPointerException if class name is {@code null}
|
|
*/
|
|
public static String validateInternalClassName(String name) {
|
|
return validateClassOrPackageName(name, true, false);
|
|
}
|
|
|
|
/**
|
|
* Validates the correctness of a binary package name.
|
|
* In particular checks for the presence of invalid characters, consecutive,
|
|
* leading, or trailing {@code .}. Allows empty strings for the unnamed package.
|
|
*
|
|
* @param name the package name
|
|
* @return the package name passed if valid
|
|
* @throws IllegalArgumentException if the package name is invalid
|
|
* @throws NullPointerException if the package name is {@code null}
|
|
*/
|
|
public static String validateBinaryPackageName(String name) {
|
|
return validateClassOrPackageName(name, false, true);
|
|
}
|
|
|
|
/**
|
|
* Validates the correctness of an internal package name.
|
|
* In particular checks for the presence of invalid characters, consecutive,
|
|
* leading, or trailing {@code /}. Allows empty strings for the unnamed package.
|
|
*
|
|
* @param name the package name
|
|
* @return the package name passed if valid
|
|
* @throws IllegalArgumentException if the package name is invalid
|
|
* @throws NullPointerException if the package name is {@code null}
|
|
*/
|
|
public static String validateInternalPackageName(String name) {
|
|
return validateClassOrPackageName(name, true, true);
|
|
}
|
|
|
|
/**
|
|
* Validates the correctness of a module name.
|
|
* In particular checks for the presence of invalid characters in the name.
|
|
* Empty module name is allowed.
|
|
*
|
|
* {@jvms 4.2.3} Module and Package Names
|
|
*
|
|
* @param name the module name
|
|
* @return the module name passed if valid
|
|
* @throws IllegalArgumentException if the module name is invalid
|
|
* @throws NullPointerException if the module name is {@code null}
|
|
*/
|
|
public static String validateModuleName(String name) {
|
|
for (int i = name.length() - 1; i >= 0; i--) {
|
|
char ch = name.charAt(i);
|
|
if ((ch >= '\u0000' && ch <= '\u001F')
|
|
|| ((ch == '\\' || ch == ':' || ch =='@') && (i == 0 || name.charAt(--i) != '\\')))
|
|
throw new IllegalArgumentException("Invalid module name: " + name);
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Validates a member name
|
|
*
|
|
* @param name the name of the member
|
|
* @return the name passed if valid
|
|
* @throws IllegalArgumentException if the member name is invalid
|
|
* @throws NullPointerException if the member name is {@code null}
|
|
*/
|
|
public static String validateMemberName(String name, boolean method) {
|
|
int len = name.length();
|
|
if (len == 0)
|
|
throw new IllegalArgumentException("zero-length member name");
|
|
for (int i = 0; i < len; i++) {
|
|
char ch = name.charAt(i);
|
|
// common case fast-path
|
|
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
|
|
continue;
|
|
if (ch == '.' || ch == ';' || ch == '[' || ch == '/')
|
|
throw new IllegalArgumentException("Invalid member name: " + name);
|
|
if (method && (ch == '<' || ch == '>')) {
|
|
if (!pointyNames.contains(name))
|
|
throw new IllegalArgumentException("Invalid member name: " + name);
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
public static void validateClassOrInterface(ClassDesc classDesc) {
|
|
if (!classDesc.isClassOrInterface())
|
|
throw new IllegalArgumentException("not a class or interface type: " + classDesc);
|
|
}
|
|
|
|
public static void validateArrayRank(int rank) {
|
|
// array rank must be representable with u1 and nonzero
|
|
if (rank == 0 || (rank & ~0xFF) != 0) {
|
|
throw new IllegalArgumentException(invalidArrayRankMessage(rank));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the array depth on a trusted descriptor.
|
|
* Uses a simple loop with the assumption that most descriptors have
|
|
* 0 or very low array depths.
|
|
*/
|
|
public static int arrayDepth(String descriptorString, int off) {
|
|
int depth = 0;
|
|
while (descriptorString.charAt(off) == '[') {
|
|
depth++;
|
|
off++;
|
|
}
|
|
return depth;
|
|
}
|
|
|
|
public static String binaryToInternal(String name) {
|
|
return name.replace('.', '/');
|
|
}
|
|
|
|
public static String internalToBinary(String name) {
|
|
return name.replace('/', '.');
|
|
}
|
|
|
|
public static String dropFirstAndLastChar(String s) {
|
|
return s.substring(1, s.length() - 1);
|
|
}
|
|
|
|
public static PrimitiveClassDescImpl forPrimitiveType(String descriptor, int offset) {
|
|
return switch (descriptor.charAt(offset)) {
|
|
case JVM_SIGNATURE_BYTE -> CD_byte;
|
|
case JVM_SIGNATURE_CHAR -> CD_char;
|
|
case JVM_SIGNATURE_FLOAT -> CD_float;
|
|
case JVM_SIGNATURE_DOUBLE -> CD_double;
|
|
case JVM_SIGNATURE_INT -> CD_int;
|
|
case JVM_SIGNATURE_LONG -> CD_long;
|
|
case JVM_SIGNATURE_SHORT -> CD_short;
|
|
case JVM_SIGNATURE_VOID -> CD_void;
|
|
case JVM_SIGNATURE_BOOLEAN -> CD_boolean;
|
|
default -> throw badMethodDescriptor(descriptor);
|
|
};
|
|
}
|
|
|
|
static ClassDesc resolveClassDesc(String descriptor, int start, int len) {
|
|
if (len == 1) {
|
|
return forPrimitiveType(descriptor, start);
|
|
}
|
|
|
|
// Pre-verified in MethodTypeDescImpl#ofDescriptor; avoid redundant verification
|
|
int arrayDepth = arrayDepth(descriptor, start);
|
|
if (arrayDepth == 0) {
|
|
return ClassOrInterfaceDescImpl.ofValidated(descriptor.substring(start, start + len));
|
|
} else if (arrayDepth + 1 == len) {
|
|
return ArrayClassDescImpl.ofValidated(forPrimitiveType(descriptor, start + arrayDepth), arrayDepth);
|
|
} else {
|
|
return ArrayClassDescImpl.ofValidated(ClassOrInterfaceDescImpl.ofValidated(descriptor.substring(start + arrayDepth, start + len)), arrayDepth);
|
|
}
|
|
}
|
|
|
|
static String invalidArrayRankMessage(int rank) {
|
|
return "Array rank must be within [1, 255]: " + rank;
|
|
}
|
|
|
|
static IllegalArgumentException invalidClassName(String className) {
|
|
return new IllegalArgumentException("Invalid class name: ".concat(className));
|
|
}
|
|
|
|
static IllegalArgumentException badMethodDescriptor(String descriptor) {
|
|
return new IllegalArgumentException("Bad method descriptor: " + descriptor);
|
|
}
|
|
|
|
private static final char JVM_SIGNATURE_ARRAY = '[';
|
|
private static final char JVM_SIGNATURE_BYTE = 'B';
|
|
private static final char JVM_SIGNATURE_CHAR = 'C';
|
|
private static final char JVM_SIGNATURE_CLASS = 'L';
|
|
private static final char JVM_SIGNATURE_FLOAT = 'F';
|
|
private static final char JVM_SIGNATURE_DOUBLE = 'D';
|
|
private static final char JVM_SIGNATURE_INT = 'I';
|
|
private static final char JVM_SIGNATURE_LONG = 'J';
|
|
private static final char JVM_SIGNATURE_SHORT = 'S';
|
|
private static final char JVM_SIGNATURE_VOID = 'V';
|
|
private static final char JVM_SIGNATURE_BOOLEAN = 'Z';
|
|
|
|
/**
|
|
* Validates that the characters at [start, end) within the provided string
|
|
* describe a valid field type descriptor.
|
|
* @param descriptor the descriptor string
|
|
* @param start the starting index into the string
|
|
* @param end the ending index within the string
|
|
* @return the length of the descriptor, or 0 if it is not a descriptor
|
|
* @throws IllegalArgumentException if the descriptor string is not valid
|
|
*/
|
|
static int skipOverFieldSignature(String descriptor, int start, int end) {
|
|
int arrayDim = 0;
|
|
int index = start;
|
|
if (index < end) {
|
|
char ch;
|
|
while ((ch = descriptor.charAt(index++)) == JVM_SIGNATURE_ARRAY) {
|
|
arrayDim++;
|
|
}
|
|
if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
|
|
throw maxArrayTypeDescDimensions();
|
|
}
|
|
|
|
switch (ch) {
|
|
case JVM_SIGNATURE_BOOLEAN:
|
|
case JVM_SIGNATURE_BYTE:
|
|
case JVM_SIGNATURE_CHAR:
|
|
case JVM_SIGNATURE_SHORT:
|
|
case JVM_SIGNATURE_INT:
|
|
case JVM_SIGNATURE_FLOAT:
|
|
case JVM_SIGNATURE_LONG:
|
|
case JVM_SIGNATURE_DOUBLE:
|
|
return index - start;
|
|
case JVM_SIGNATURE_CLASS:
|
|
// state variable for detection of illegal states of
|
|
// empty name, '//', leading '/', or trailing '/'
|
|
int afterSeparator = index + 1; // start of internal name
|
|
while (index < end) {
|
|
ch = descriptor.charAt(index++);
|
|
if (ch == ';')
|
|
// reject empty name or trailing '/'
|
|
return index == afterSeparator ? 0 : index - start;
|
|
// reject '.' or '['
|
|
if (ch == '.' || ch == '[')
|
|
return 0;
|
|
if (ch == '/') {
|
|
// reject '//' or leading '/'
|
|
if (index == afterSeparator)
|
|
return 0;
|
|
afterSeparator = index + 1;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static IllegalArgumentException maxArrayTypeDescDimensions() {
|
|
return new IllegalArgumentException(String.format(
|
|
"Cannot create an array type descriptor with more than %d dimensions",
|
|
ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS));
|
|
}
|
|
|
|
public static String concat(String prefix, Object value, String suffix) {
|
|
return JLA.concat(prefix, value, suffix);
|
|
}
|
|
}
|