This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new 7cc1ac1c3 ClassUtils now throws `IllegalArgumentException` when array
is greater than JVM spec limit (255) (#1494)
7cc1ac1c3 is described below
commit 7cc1ac1c30d9ad2b285d11ed26b121e1f794e57e
Author: Gary Gregory <[email protected]>
AuthorDate: Tue Nov 18 09:24:22 2025 -0500
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
---
.../java/org/apache/commons/lang3/ClassUtils.java | 70 ++++++++++++++------
.../org/apache/commons/lang3/ClassUtilsTest.java | 77 ++++++++++++++++------
2 files changed, 108 insertions(+), 39 deletions(-)
diff --git a/src/main/java/org/apache/commons/lang3/ClassUtils.java
b/src/main/java/org/apache/commons/lang3/ClassUtils.java
index 2e811b4f4..bc377bbec 100644
--- a/src/main/java/org/apache/commons/lang3/ClassUtils.java
+++ b/src/main/java/org/apache/commons/lang3/ClassUtils.java
@@ -48,6 +48,13 @@
*/
public class ClassUtils {
+ /**
+ * The JVM {@code CONSTANT_Class_info} structure defines an array type
descriptor is valid only if it represents 255 or fewer dimensions.
+ *
+ * @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>
+ */
+ private static final int MAX_JVM_ARRAY_DIMENSION = 255;
+
/**
* Inclusivity literals for {@link #hierarchy(Class, Interfaces)}.
*
@@ -520,11 +527,16 @@ private static String getCanonicalName(final String name)
{
* supports the syntaxes "{@code java.util.Map.Entry[]}", "{@code
java.util.Map$Entry[]}",
* "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
*
- * @param classLoader the class loader to use to load the class
- * @param className the class name
- * @return the class represented by {@code className} using the {@code
classLoader}
- * @throws NullPointerException if the className is null
- * @throws ClassNotFoundException if the class is not found
+ * @param classLoader the class loader to use to load the class.
+ * @param className the class name.
+ * @return the class represented by {@code className} using the {@code
classLoader}.
+ * @throws NullPointerException if the className is null.
+ * @throws ClassNotFoundException if the class is not found.
+ * @throws IllegalArgumentException Thrown if the class name represents an
array with more dimensions than the JVM supports, 255.
+ * @see Class#forName(String, boolean, ClassLoader)
+ * @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>
+ * @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>
+ * @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>
*/
public static Class<?> getClass(final ClassLoader classLoader, final
String className) throws ClassNotFoundException {
return getClass(classLoader, className, true);
@@ -535,12 +547,17 @@ public static Class<?> getClass(final ClassLoader
classLoader, final String clas
* syntaxes "{@code java.util.Map.Entry[]}", "{@code
java.util.Map$Entry[]}", "{@code [Ljava.util.Map.Entry;}", and
* "{@code [Ljava.util.Map$Entry;}".
*
- * @param classLoader the class loader to use to load the class
- * @param className the class name
- * @param initialize whether the class must be initialized
- * @return the class represented by {@code className} using the {@code
classLoader}
- * @throws NullPointerException if the className is null
- * @throws ClassNotFoundException if the class is not found
+ * @param classLoader the class loader to use to load the class.
+ * @param className the class name.
+ * @param initialize whether the class must be initialized.
+ * @return the class represented by {@code className} using the {@code
classLoader}.
+ * @throws NullPointerException if the className is null.
+ * @throws ClassNotFoundException if the class is not found.
+ * @throws IllegalArgumentException Thrown if the class name represents an
array with more dimensions than the JVM supports, 255.
+ * @see Class#forName(String, boolean, ClassLoader)
+ * @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>
+ * @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>
+ * @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>
*/
public static Class<?> getClass(final ClassLoader classLoader, final
String className, final boolean initialize) throws ClassNotFoundException {
// 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
* @return the class represented by {@code className} using the current
thread's context class loader
* @throws NullPointerException if the className is null
* @throws ClassNotFoundException if the class is not found
+ * @throws IllegalArgumentException Thrown if the class name represents an
array with more dimensions than the JVM supports, 255.
+ * @see Class#forName(String, boolean, ClassLoader)
+ * @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>
+ * @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>
+ * @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>
*/
public static Class<?> getClass(final String className) throws
ClassNotFoundException {
return getClass(className, true);
@@ -579,11 +601,16 @@ public static Class<?> getClass(final String className)
throws ClassNotFoundExce
* implementation supports the syntaxes "{@code java.util.Map.Entry[]}",
"{@code java.util.Map$Entry[]}",
* "{@code [Ljava.util.Map.Entry;}", and "{@code [Ljava.util.Map$Entry;}".
*
- * @param className the class name
- * @param initialize whether the class must be initialized
- * @return the class represented by {@code className} using the current
thread's context class loader
- * @throws NullPointerException if the className is null
- * @throws ClassNotFoundException if the class is not found
+ * @param className the class name.
+ * @param initialize whether the class must be initialized.
+ * @return the class represented by {@code className} using the current
thread's context class loader.
+ * @throws NullPointerException if the className is null.
+ * @throws ClassNotFoundException if the class is not found.
+ * @throws IllegalArgumentException Thrown if the class name represents an
array with more dimensions than the JVM supports, 255.
+ * @see Class#forName(String, boolean, ClassLoader)
+ * @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>
+ * @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>
+ * @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>
*/
public static Class<?> getClass(final String className, final boolean
initialize) throws ClassNotFoundException {
final ClassLoader contextCL =
Thread.currentThread().getContextClassLoader();
@@ -1494,17 +1521,22 @@ public static Class<?> primitiveToWrapper(final
Class<?> cls) {
/**
* Converts a class name to a JLS style class name.
*
- * @param className the class name
- * @return the converted name
- * @throws NullPointerException if the className is null
+ * @param className the class name.
+ * @return the converted name.
+ * @throws NullPointerException if the className is null.
+ * @throws IllegalArgumentException Thrown if the class name represents an
array with more dimensions than the JVM supports, 255.
*/
private static String toCanonicalName(final String className) {
String canonicalName = StringUtils.deleteWhitespace(className);
Objects.requireNonNull(canonicalName, "className");
final String arrayMarker = "[]";
+ int arrayDim = 0;
if (canonicalName.endsWith(arrayMarker)) {
final StringBuilder classNameBuffer = new StringBuilder();
while (canonicalName.endsWith(arrayMarker)) {
+ if (++arrayDim > MAX_JVM_ARRAY_DIMENSION) {
+ throw new IllegalArgumentException("Array dimension
greater than JVM specification maximum of 255.");
+ }
canonicalName = canonicalName.substring(0,
canonicalName.length() - 2);
classNameBuffer.append("[");
}
diff --git a/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java
b/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java
index 320391193..43957c97e 100644
--- a/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/ClassUtilsTest.java
@@ -26,6 +26,7 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import java.io.Serializable;
import java.lang.reflect.Constructor;
@@ -37,6 +38,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
@@ -48,6 +50,8 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junitpioneer.jupiter.params.IntRangeSource;
/**
* Tests {@link ClassUtils}.
@@ -113,6 +117,22 @@ private void
assertGetClassThrowsNullPointerException(final String className) {
assertGetClassThrowsException(className, NullPointerException.class);
}
+ private int getDimension(final Class<?> clazz) {
+ Objects.requireNonNull(clazz);
+ if (!clazz.isArray()) {
+ fail("Not an array: " + clazz);
+ }
+ final String className = clazz.getName();
+ int dimension = 0;
+ for (final char c : className.toCharArray()) {
+ if (c != '[') {
+ break;
+ }
+ dimension++;
+ }
+ return dimension;
+ }
+
@Test
void test_convertClassesToClassNames_List() {
final List<Class<?>> list = new ArrayList<>();
@@ -1177,6 +1197,23 @@ void testConstructor() {
assertFalse(Modifier.isFinal(ClassUtils.class.getModifiers()));
}
+ @ParameterizedTest
+ @IntRangeSource(from = 1, to = 255)
+ void testGetClassArray(final int dimensions) throws ClassNotFoundException
{
+ assertEquals(dimensions,
+
getDimension(ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"
+ StringUtils.repeat("[]", dimensions))));
+ assertEquals(dimensions,
getDimension(ClassUtils.getClass("java.lang.String" + StringUtils.repeat("[]",
dimensions))));
+ }
+
+ @ParameterizedTest
+ @IntRangeSource(from = 256, to = 300)
+ void testGetClassArrayIllegal(final int dimensions) throws
ClassNotFoundException {
+ assertThrows(IllegalArgumentException.class, () ->
assertEquals(dimensions,
+
getDimension(ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"
+ StringUtils.repeat("[]", dimensions)))));
+ assertThrows(IllegalArgumentException.class,
+ () -> assertEquals(dimensions,
getDimension(ClassUtils.getClass("java.lang.String" + StringUtils.repeat("[]",
dimensions)))));
+ }
+
@Test
void testGetClassByNormalNameArrays() throws ClassNotFoundException {
assertEquals(int[].class, ClassUtils.getClass("int[]"));
@@ -1214,6 +1251,26 @@ void testGetClassClassNotFound() throws Exception {
assertGetClassThrowsClassNotFound("integer[]");
}
+ @Test
+ void testGetClassInner() throws ClassNotFoundException {
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
+ assertEquals(Inner.DeeplyNested[].class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested[]"));
+ //
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested",
true));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested",
true));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested",
true));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested",
true));
+ //
+ final ClassLoader classLoader =
Inner.DeeplyNested.class.getClassLoader();
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
+ assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
+ }
+
@Test
void testGetClassInvalidArguments() throws Exception {
assertGetClassThrowsNullPointerException(null);
@@ -1275,26 +1332,6 @@ void testGetComponentType() {
assertNull(ClassUtils.getComponentType(null));
}
- @Test
- void testGetInnerClass() throws ClassNotFoundException {
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
- //
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested",
true));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested",
true));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested",
true));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass("org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested",
true));
- //
- final ClassLoader classLoader =
Inner.DeeplyNested.class.getClassLoader();
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest.Inner.DeeplyNested"));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest.Inner$DeeplyNested"));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest$Inner$DeeplyNested"));
- assertEquals(Inner.DeeplyNested.class,
ClassUtils.getClass(classLoader,
"org.apache.commons.lang3.ClassUtilsTest$Inner.DeeplyNested"));
- //
- }
-
@Test
void testGetPublicMethod() throws Exception {
// Tests with Collections$UnmodifiableSet