/
githubmirror
/
panama-vector
Обзор
Документация
Войти
/
githubmirror
/
panama-vector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java
518 строк
21 KB
Chen Liang
8388374: Class-File API does not enforce required validation for null matches and empty PackageEntry names
11 авг 2026, 16:25
11 авг 2026, 16:25
b1467bd
Код
Авторство
О чём код?
/* * Copyright (c) 2018, 2026, 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.AOTSafeClassInitializer; 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.constant.ModuleDesc; import java.lang.constant.PackageDesc; 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}. */ @AOTSafeClassInitializer // initialization dependency of PrimitiveClassDescImpl 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 an 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 an 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 package descriptor for a PackageEntry, * where an unnamed package is not allowed. * * @param desc the package descriptor * @throws IllegalArgumentException if the package descriptor is empty * @throws NullPointerException if the package descriptor is {@code null} */ public static PackageDesc validateNamedPackage(PackageDesc desc) { if (desc.internalName().isEmpty()) { throw new IllegalArgumentException("Unnamed package not allowed"); } return desc; } /** * Validates the correctness of a module descriptor for a ModuleEntry, * where an unnamed module is not allowed. * * @param desc the module descriptor * @throws IllegalArgumentException if the module descriptor is empty * @throws NullPointerException if the module descriptor is {@code null} */ public static ModuleDesc validateNamedModule(ModuleDesc desc) { if (desc.name().isEmpty()) { throw new IllegalArgumentException("Unnamed module not allowed"); } return desc; } /** * 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); } }