Skip to content

Commit 5713c6c

Browse files
authored
Bugfix/class utils to canonical name length check (#1495)
* Add testLang1641() * Rename some test methods * ClassUtils now throws IllegalArgumentException if a class name is > 65535 This affects: - getClass(ClassLoader, String, boolean), - ClassUtils.getClass(ClassLoader, String), - ClassUtils.getClass(String, boolean), - ClassUtils.getClass(String) * ClassUtils now throws IllegalArgumentException if a class name is > 65535 This affects: - getClass(ClassLoader, String, boolean), - ClassUtils.getClass(ClassLoader, String), - ClassUtils.getClass(String, boolean), - ClassUtils.getClass(String) * ClassUtils now throws IllegalArgumentException if a class name is > 65535 This affects: - getClass(ClassLoader, String, boolean), - ClassUtils.getClass(ClassLoader, String), - ClassUtils.getClass(String, boolean), - ClassUtils.getClass(String)
1 parent f8a6bf9 commit 5713c6c

File tree

2 files changed

+82
-14
lines changed

2 files changed

+82
-14
lines changed

src/main/java/org/apache/commons/lang3/ClassUtils.java

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,22 @@
4949
public class ClassUtils {
5050

5151
/**
52-
* The JVM {@code CONSTANT_Class_info} structure defines an array type descriptor is valid only if it represents 255 or fewer dimensions.
52+
* The JLS-specified maximum class name length {@value}.
5353
*
54+
* @see Class#forName(String, boolean, ClassLoader)
5455
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
56+
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
57+
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
58+
*/
59+
private static final int MAX_CLASS_NAME_LENGTH = 65535;
60+
61+
/**
62+
* The JVM-specified {@code CONSTANT_Class_info} structure defines an array type descriptor is valid only if it represents {@value} or fewer dimensions.
63+
*
64+
* @see Class#forName(String, boolean, ClassLoader)
65+
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
66+
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
67+
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
5568
*/
5669
private static final int MAX_JVM_ARRAY_DIMENSION = 255;
5770

@@ -533,6 +546,7 @@ private static String getCanonicalName(final String name) {
533546
* @throws NullPointerException if the className is null.
534547
* @throws ClassNotFoundException if the class is not found.
535548
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
549+
* @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
536550
* @see Class#forName(String, boolean, ClassLoader)
537551
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
538552
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
@@ -554,6 +568,7 @@ public static Class<?> getClass(final ClassLoader classLoader, final String clas
554568
* @throws NullPointerException if the className is null.
555569
* @throws ClassNotFoundException if the class is not found.
556570
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
571+
* @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
557572
* @see Class#forName(String, boolean, ClassLoader)
558573
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
559574
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
@@ -566,7 +581,7 @@ public static Class<?> getClass(final ClassLoader classLoader, final String clas
566581
do {
567582
try {
568583
final Class<?> clazz = getPrimitiveClass(next);
569-
return clazz != null ? clazz : Class.forName(toCanonicalName(next), initialize, classLoader);
584+
return clazz != null ? clazz : Class.forName(toCleanName(next), initialize, classLoader);
570585
} catch (final ClassNotFoundException ex) {
571586
lastDotIndex = next.lastIndexOf(PACKAGE_SEPARATOR_CHAR);
572587
if (lastDotIndex != -1) {
@@ -587,6 +602,7 @@ public static Class<?> getClass(final ClassLoader classLoader, final String clas
587602
* @throws NullPointerException if the className is null
588603
* @throws ClassNotFoundException if the class is not found
589604
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
605+
* @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
590606
* @see Class#forName(String, boolean, ClassLoader)
591607
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
592608
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
@@ -607,6 +623,7 @@ public static Class<?> getClass(final String className) throws ClassNotFoundExce
607623
* @throws NullPointerException if the className is null.
608624
* @throws ClassNotFoundException if the class is not found.
609625
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
626+
* @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
610627
* @see Class#forName(String, boolean, ClassLoader)
611628
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification CONSTANT_Class_info</a>
612629
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
@@ -1519,32 +1536,60 @@ public static Class<?> primitiveToWrapper(final Class<?> cls) {
15191536
}
15201537

15211538
/**
1522-
* Converts a class name to a JLS style class name.
1539+
* Converts and cleans up a class name to a JLS style class name.
15231540
*
15241541
* @param className the class name.
15251542
* @return the converted name.
1526-
* @throws NullPointerException if the className is null.
1543+
* @throws NullPointerException if the className is null.
15271544
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
1545+
* @throws IllegalArgumentException Thrown if the class name length is greater than 65,535.
1546+
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.4.1">JVM: Array dimension limits in JVM Specification
1547+
* CONSTANT_Class_info</a>
1548+
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html#jls-6.7">JLS: Fully Qualified Names and Canonical Names</a>
1549+
* @see <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-13.html#jls-13.1">JLS: The Form of a Binary</a>
15281550
*/
1529-
private static String toCanonicalName(final String className) {
1551+
private static String toCleanName(final String className) {
15301552
String canonicalName = StringUtils.deleteWhitespace(className);
15311553
Objects.requireNonNull(canonicalName, "className");
1554+
if (canonicalName.isEmpty()) {
1555+
throw new IllegalArgumentException("Class name is empty");
1556+
}
1557+
final String encodedArrayOpen = "[";
1558+
final String encodedClassNameStart = "L";
1559+
final String encodedClassNameEnd = ";";
1560+
final boolean encodedName = canonicalName.startsWith(encodedArrayOpen) && canonicalName.endsWith(encodedClassNameEnd);
1561+
if (encodedName) {
1562+
final int arrIdx = canonicalName.indexOf(encodedClassNameStart);
1563+
if (arrIdx > MAX_JVM_ARRAY_DIMENSION) {
1564+
throw new IllegalArgumentException("Array dimension greater than JVM specification maximum of 255.");
1565+
}
1566+
if (arrIdx < 0) {
1567+
throw new IllegalArgumentException("Expected 'L' after '[' for an array style string.");
1568+
}
1569+
final int cnLen = canonicalName.length() - (arrIdx + 2); // account for the ending ';'
1570+
if (cnLen > MAX_CLASS_NAME_LENGTH) {
1571+
throw new IllegalArgumentException(String.format("Class name greater than maxium length %,d", MAX_CLASS_NAME_LENGTH));
1572+
}
1573+
}
15321574
final String arrayMarker = "[]";
1533-
int arrayDim = 0;
1575+
final int arrIdx = canonicalName.indexOf(arrayMarker);
1576+
// The class name length without array markers.
1577+
final int cnLen = arrIdx > 0 ? arrIdx : canonicalName.length();
1578+
if (cnLen > MAX_CLASS_NAME_LENGTH && !encodedName) {
1579+
throw new IllegalArgumentException(String.format("Class name greater than maxium length %,d", MAX_CLASS_NAME_LENGTH));
1580+
}
15341581
if (canonicalName.endsWith(arrayMarker)) {
1535-
final StringBuilder classNameBuffer = new StringBuilder();
1536-
while (canonicalName.endsWith(arrayMarker)) {
1537-
if (++arrayDim > MAX_JVM_ARRAY_DIMENSION) {
1538-
throw new IllegalArgumentException("Array dimension greater than JVM specification maximum of 255.");
1539-
}
1540-
canonicalName = canonicalName.substring(0, canonicalName.length() - 2);
1541-
classNameBuffer.append("[");
1582+
final int dims = (canonicalName.length() - arrIdx) / 2;
1583+
if (dims > MAX_JVM_ARRAY_DIMENSION) {
1584+
throw new IllegalArgumentException("Array dimension greater than JVM specification maximum of 255.");
15421585
}
1586+
final StringBuilder classNameBuffer = new StringBuilder(StringUtils.repeat(encodedArrayOpen, dims));
1587+
canonicalName = canonicalName.substring(0, arrIdx);
15431588
final String abbreviation = ABBREVIATION_MAP.get(canonicalName);
15441589
if (abbreviation != null) {
15451590
classNameBuffer.append(abbreviation);
15461591
} else {
1547-
classNameBuffer.append("L").append(canonicalName).append(";");
1592+
classNameBuffer.append(encodedClassNameStart).append(canonicalName).append(encodedClassNameEnd);
15481593
}
15491594
canonicalName = classNameBuffer.toString();
15501595
}

src/test/java/org/apache/commons/lang3/ClassUtilsTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
@SuppressWarnings("boxing") // JUnit4 does not support primitive equality testing apart from long
6060
class ClassUtilsTest extends AbstractLangTest {
6161

62+
private static final int MAX_ARRAY_DIMENSIONS = 255;
63+
6264
private static class CX implements IB, IA, IE {
6365
// empty
6466
}
@@ -1229,6 +1231,7 @@ void testGetClassByNormalNameArrays() throws ClassNotFoundException {
12291231
assertEquals(java.util.Map.Entry[].class, ClassUtils.getClass("java.util.Map$Entry[]"));
12301232
assertEquals(java.util.Map.Entry[].class, ClassUtils.getClass("[Ljava.util.Map.Entry;"));
12311233
assertEquals(java.util.Map.Entry[].class, ClassUtils.getClass("[Ljava.util.Map$Entry;"));
1234+
assertEquals(java.util.Map.Entry[][].class, ClassUtils.getClass("[[Ljava.util.Map$Entry;"));
12321235
}
12331236

12341237
@Test
@@ -1282,6 +1285,26 @@ void testGetClassInvalidArguments() throws Exception {
12821285
assertGetClassThrowsClassNotFound("hello..world");
12831286
}
12841287

1288+
@ParameterizedTest
1289+
@IntRangeSource(from = 65536, to = 65555)
1290+
void testGetClassLengthIllegal(final int classNameLength) throws ClassNotFoundException {
1291+
assertThrows(IllegalArgumentException.class, () -> ClassUtils.getClass(StringUtils.repeat("a", classNameLength)));
1292+
assertThrows(IllegalArgumentException.class, () -> assertEquals(classNameLength, ClassUtils.getClass(StringUtils.repeat("a.", classNameLength / 2))));
1293+
}
1294+
1295+
@Test
1296+
void testGetClassLongestCheck() throws ClassNotFoundException {
1297+
final String maxClassName = StringUtils.repeat("a", 65535);
1298+
final String maxDimensions = StringUtils.repeat("[]", MAX_ARRAY_DIMENSIONS);
1299+
final String maxOpens = StringUtils.repeat("[", MAX_ARRAY_DIMENSIONS);
1300+
assertThrows(ClassNotFoundException.class, () -> ClassUtils.getClass(maxClassName));
1301+
assertNotNull(ClassUtils.getClass("java.lang.String" + maxDimensions));
1302+
assertThrows(ClassNotFoundException.class, () -> ClassUtils.getClass(maxClassName + maxDimensions));
1303+
assertThrows(ClassNotFoundException.class, () -> ClassUtils.getClass(maxOpens + "L" + maxClassName + ";"));
1304+
// maxOpens + 1
1305+
assertThrows(IllegalArgumentException.class, () -> ClassUtils.getClass(maxOpens + "[L" + maxClassName + ";"));
1306+
}
1307+
12851308
@Test
12861309
void testGetClassRawPrimitives() throws ClassNotFoundException {
12871310
assertEquals(int.class, ClassUtils.getClass("int"));

0 commit comments

Comments
 (0)