io.github.dmlloyd.classfile.extras.constant.ConstantUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jdk-classfile-backport Show documentation
Show all versions of jdk-classfile-backport Show documentation
An unofficial backport of the JDK Classfile API to Java 17
/*
* 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 io.github.dmlloyd.classfile.extras.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.MethodTypeDesc;
import java.lang.invoke.MethodType;
import java.util.Set;
import static java.lang.constant.ConstantDescs.*;
/**
* Helper methods for the implementation of {@code java.lang.constant}.
*/
public final class ConstantUtils {
/** 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 ClassDesc CD_Object_array; // set from ConstantDescs, avoid circular initialization
private static final Set pointyNames = Set.of(ExtraConstantDescs.INIT_NAME, ExtraConstantDescs.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 ClassDesc.of(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 ClassDesc.ofDescriptor(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) {
return type.describeConstable().orElseThrow();
}
/**
* 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 type.describeConstable().orElseThrow();
}
/**
* 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) {
return ClassDesc.ofDescriptor(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) {
return type.describeConstable().orElseThrow();
}
/**
* 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) {
return MethodType.methodType(returnType, parameterTypes).describeConstable().orElseThrow();
}
/**
* 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 ClassDesc 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 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 prefix + value + suffix;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy