Skip to content

Commit 7cc1ac1

Browse files
authored
ClassUtils now throws IllegalArgumentException when array is greater than JVM spec limit (255) (#1494)
* Add testLang1641() * Rename some test methods * ClassUtils now throws IllegalArgumentException if a class name represents an array with more dimensions than the JVM spec allow, 255 Affects ClassUtils methods: - getClass(ClassLoader, String, boolean), - ClassUtils.getClass(ClassLoader, String), - ClassUtils.getClass(String, boolean), - ClassUtils.getClass(String) * Add Javadoc for MAX_JVM_ARRAY_DIMENSION constant * Update Javadoc link format for JVM array dimension * Javadoc
1 parent 162575c commit 7cc1ac1

File tree

2 files changed

+108
-39
lines changed

2 files changed

+108
-39
lines changed

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

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@
4848
*/
4949
public class ClassUtils {
5050

51+
/**
52+
* The JVM {@code CONSTANT_Class_info} structure defines an array type descriptor is valid only if it represents 255 or fewer dimensions.
53+
*
54+
* @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>
55+
*/
56+
private static final int MAX_JVM_ARRAY_DIMENSION = 255;
57+
5158
/**
5259
* Inclusivity literals for {@link #hierarchy(Class, Interfaces)}.
5360
*
@@ -520,11 +527,16 @@ private static String getCanonicalName(final String name) {
520527
* supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
521528
* "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
522529
*
523-
* @param classLoader the class loader to use to load the class
524-
* @param className the class name
525-
* @return the class represented by {@code className} using the {@code classLoader}
526-
* @throws NullPointerException if the className is null
527-
* @throws ClassNotFoundException if the class is not found
530+
* @param classLoader the class loader to use to load the class.
531+
* @param className the class name.
532+
* @return the class represented by {@code className} using the {@code classLoader}.
533+
* @throws NullPointerException if the className is null.
534+
* @throws ClassNotFoundException if the class is not found.
535+
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
536+
* @see Class#forName(String, boolean, ClassLoader)
537+
* @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>
538+
* @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>
539+
* @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>
528540
*/
529541
public static Class<?> getClass(final ClassLoader classLoader, final String className) throws ClassNotFoundException {
530542
return getClass(classLoader, className, true);
@@ -535,12 +547,17 @@ public static Class<?> getClass(final ClassLoader classLoader, final String clas
535547
* syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", and
536548
* "{@code [Ljava.util.Map$Entry;}".
537549
*
538-
* @param classLoader the class loader to use to load the class
539-
* @param className the class name
540-
* @param initialize whether the class must be initialized
541-
* @return the class represented by {@code className} using the {@code classLoader}
542-
* @throws NullPointerException if the className is null
543-
* @throws ClassNotFoundException if the class is not found
550+
* @param classLoader the class loader to use to load the class.
551+
* @param className the class name.
552+
* @param initialize whether the class must be initialized.
553+
* @return the class represented by {@code className} using the {@code classLoader}.
554+
* @throws NullPointerException if the className is null.
555+
* @throws ClassNotFoundException if the class is not found.
556+
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
557+
* @see Class#forName(String, boolean, ClassLoader)
558+
* @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>
559+
* @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>
560+
* @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>
544561
*/
545562
public static Class<?> getClass(final ClassLoader classLoader, final String className, final boolean initialize) throws ClassNotFoundException {
546563
// This method was re-written to avoid recursion and stack overflows found by fuzz testing.
@@ -569,6 +586,11 @@ public static Class<?> getClass(final ClassLoader classLoader, final String clas
569586
* @return the class represented by {@code className} using the current thread's context class loader
570587
* @throws NullPointerException if the className is null
571588
* @throws ClassNotFoundException if the class is not found
589+
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
590+
* @see Class#forName(String, boolean, ClassLoader)
591+
* @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>
592+
* @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>
593+
* @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>
572594
*/
573595
public static Class<?> getClass(final String className) throws ClassNotFoundException {
574596
return getClass(className, true);
@@ -579,11 +601,16 @@ public static Class<?> getClass(final String className) throws ClassNotFoundExce
579601
* implementation supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code java.util.Map$Entry[]}",
580602
* "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
581603
*
582-
* @param className the class name
583-
* @param initialize whether the class must be initialized
584-
* @return the class represented by {@code className} using the current thread's context class loader
585-
* @throws NullPointerException if the className is null
586-
* @throws ClassNotFoundException if the class is not found
604+
* @param className the class name.
605+
* @param initialize whether the class must be initialized.
606+
* @return the class represented by {@code className} using the current thread's context class loader.
607+
* @throws NullPointerException if the className is null.
608+
* @throws ClassNotFoundException if the class is not found.
609+
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
610+
* @see Class#forName(String, boolean, ClassLoader)
611+
* @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>
612+
* @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>
613+
* @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>
587614
*/
588615
public static Class<?> getClass(final String className, final boolean initialize) throws ClassNotFoundException {
589616
final ClassLoader contextCL = Thread.currentThread().getContextClassLoader();
@@ -1494,17 +1521,22 @@ public static Class<?> primitiveToWrapper(final Class<?> cls) {
14941521
/**
14951522
* Converts a class name to a JLS style class name.
14961523
*
1497-
* @param className the class name
1498-
* @return the converted name
1499-
* @throws NullPointerException if the className is null
1524+
* @param className the class name.
1525+
* @return the converted name.
1526+
* @throws NullPointerException if the className is null.
1527+
* @throws IllegalArgumentException Thrown if the class name represents an array with more dimensions than the JVM supports, 255.
15001528
*/
15011529
private static String toCanonicalName(final String className) {
15021530
String canonicalName = StringUtils.deleteWhitespace(className);
15031531
Objects.requireNonNull(canonicalName, "className");
15041532
final String arrayMarker = "[]";
1533+
int arrayDim = 0;
15051534
if (canonicalName.endsWith(arrayMarker)) {
15061535
final StringBuilder classNameBuffer = new StringBuilder();
15071536
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+
}
15081540
canonicalName = canonicalName.substring(0, canonicalName.length() - 2);
15091541
classNameBuffer.append("[");
15101542
}

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

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static org.junit.jupiter.api.Assertions.assertSame;
2727
import static org.junit.jupiter.api.Assertions.assertThrows;
2828
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
import static org.junit.jupiter.api.Assertions.fail;
2930

3031
import java.io.Serializable;
3132
import java.lang.reflect.Constructor;
@@ -37,6 +38,7 @@
3738
import java.util.Iterator;
3839
import java.util.List;
3940
import java.util.Map;
41+
import java.util.Objects;
4042
import java.util.Set;
4143
import java.util.TreeMap;
4244
import java.util.function.Function;
@@ -48,6 +50,8 @@
4850
import org.junit.jupiter.api.Assertions;
4951
import org.junit.jupiter.api.DisplayName;
5052
import org.junit.jupiter.api.Test;
53+
import org.junit.jupiter.params.ParameterizedTest;
54+
import org.junitpioneer.jupiter.params.IntRangeSource;
5155

5256
/**
5357
* Tests {@link ClassUtils}.
@@ -113,6 +117,22 @@ private void assertGetClassThrowsNullPointerException(final String className) {
113117
assertGetClassThrowsException(className, NullPointerException.class);
114118
}
115119

120+
private int getDimension(final Class<?> clazz) {
121+
Objects.requireNonNull(clazz);
122+
if (!clazz.isArray()) {
123+
fail("Not an array: " + clazz);
124+
}
125+
final String className = clazz.getName();
126+
int dimension = 0;
127+
for (final char c : className.toCharArray()) {
128+
if (c != '[') {
129+
break;
130+
}
131+
dimension++;
132+
}
133+
return dimension;
134+
}
135+
116136
@Test
117137
void test_convertClassesToClassNames_List() {
118138
final List<Class<?>> list = new ArrayList<>();
@@ -1177,6 +1197,23 @@ void testConstructor() {
11771197
assertFalse(Modifier.isFinal(ClassUtils.class.getModifiers()));
11781198
}
11791199

1200+
@ParameterizedTest
1201+
@IntRangeSource(from = 1, to = 255)
1202+
void testGetClassArray(final int dimensions) throws ClassNotFoundException {
1203+
assertEquals(dimensions,
1204+
getDimension(ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested" + StringUtils.repeat("[]", dimensions))));
1205+
assertEquals(dimensions, getDimension(ClassUtils.getClass("java.lang.String" + StringUtils.repeat("[]", dimensions))));
1206+
}
1207+
1208+
@ParameterizedTest
1209+
@IntRangeSource(from = 256, to = 300)
1210+
void testGetClassArrayIllegal(final int dimensions) throws ClassNotFoundException {
1211+
assertThrows(IllegalArgumentException.class, () -> assertEquals(dimensions,
1212+
getDimension(ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested" + StringUtils.repeat("[]", dimensions)))));
1213+
assertThrows(IllegalArgumentException.class,
1214+
() -> assertEquals(dimensions, getDimension(ClassUtils.getClass("java.lang.String" + StringUtils.repeat("[]", dimensions)))));
1215+
}
1216+
11801217
@Test
11811218
void testGetClassByNormalNameArrays() throws ClassNotFoundException {
11821219
assertEquals(int[].class, ClassUtils.getClass("int[]"));
@@ -1214,6 +1251,26 @@ void testGetClassClassNotFound() throws Exception {
12141251
assertGetClassThrowsClassNotFound("integer[]");
12151252
}
12161253

1254+
@Test
1255+
void testGetClassInner() throws ClassNotFoundException {
1256+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
1257+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
1258+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
1259+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
1260+
assertEquals(Inner.DeeplyNested[].class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested[]"));
1261+
//
1262+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested", true));
1263+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested", true));
1264+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested", true));
1265+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested", true));
1266+
//
1267+
final ClassLoader classLoader = Inner.DeeplyNested.class.getClassLoader();
1268+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
1269+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
1270+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
1271+
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
1272+
}
1273+
12171274
@Test
12181275
void testGetClassInvalidArguments() throws Exception {
12191276
assertGetClassThrowsNullPointerException(null);
@@ -1275,26 +1332,6 @@ void testGetComponentType() {
12751332
assertNull(ClassUtils.getComponentType(null));
12761333
}
12771334

1278-
@Test
1279-
void testGetInnerClass() throws ClassNotFoundException {
1280-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
1281-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
1282-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
1283-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
1284-
//
1285-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested", true));
1286-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested", true));
1287-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested", true));
1288-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested", true));
1289-
//
1290-
final ClassLoader classLoader = Inner.DeeplyNested.class.getClassLoader();
1291-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
1292-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
1293-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
1294-
assertEquals(Inner.DeeplyNested.class, ClassUtils.getClass(classLoader, "org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
1295-
//
1296-
}
1297-
12981335
@Test
12991336
void testGetPublicMethod() throws Exception {
13001337
// Tests with Collections$UnmodifiableSet

0 commit comments

Comments
 (0)