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

Reply via email to