This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git


The following commit(s) were added to refs/heads/master by this push:
     new c160e588 NUMBERS-212: Detect modulo 2^32 overflow during construction
c160e588 is described below

commit c160e588836faac81c96de202d1ba7b50fbaed3d
Author: Alex Herbert <[email protected]>
AuthorDate: Fri Aug 21 08:56:33 2026 +0100

    NUMBERS-212: Detect modulo 2^32 overflow during construction
---
 .../numbers/arrays/MultidimensionalCounter.java    | 39 ++++++++++++++++------
 .../arrays/MultidimensionalCounterTest.java        | 24 +++++++++++++
 src/changes/changes.xml                            |  4 +++
 3 files changed, 56 insertions(+), 11 deletions(-)

diff --git 
a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/MultidimensionalCounter.java
 
b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/MultidimensionalCounter.java
index 449e4baa..4c30a0e0 100644
--- 
a/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/MultidimensionalCounter.java
+++ 
b/commons-numbers-arrays/src/main/java/org/apache/commons/numbers/arrays/MultidimensionalCounter.java
@@ -65,7 +65,7 @@ public final class MultidimensionalCounter {
      *
      * @param size Counter sizes (number of slots in each dimension).
      * @throws IllegalArgumentException if one of the sizes is negative
-     * or zero.
+     * or zero, or if the total size exceeds {@link Integer#MAX_VALUE}.
      */
     private MultidimensionalCounter(int... size) {
         dimension = size.length;
@@ -76,17 +76,23 @@ public final class MultidimensionalCounter {
         last = dimension - 1;
         uniCounterOffset[last] = 1;
 
-        int tS = 1;
+        // Compute the running products in long arithmetic.
+        // Each dimension is checked to be a positive int before
+        // multiplying by a representable int factor, so the long
+        // product cannot overflow.
+        long tS = 1;
         for (int i = last - 1; i >= 0; i--) {
             final int index = i + 1;
-            checkStrictlyPositive("index size", size[index]);
+            checkSizeStrictlyPositive(size[index]);
             tS *= size[index];
-            checkStrictlyPositive("cumulative size", tS);
-            uniCounterOffset[i] = tS;
+            checkSizeRepresentable(tS);
+            uniCounterOffset[i] = (int) tS;
         }
 
-        totalSize = tS * size[0];
-        checkStrictlyPositive("total size", totalSize);
+        checkSizeStrictlyPositive(size[0]);
+        final long total = tS * size[0];
+        checkSizeRepresentable(total);
+        totalSize = (int) total;
     }
 
     /**
@@ -95,7 +101,7 @@ public final class MultidimensionalCounter {
      * @param size Counter sizes (number of slots in each dimension).
      * @return a new instance.
      * @throws IllegalArgumentException if one of the sizes is negative
-     * or zero.
+     * or zero, or if the total size exceeds {@link Integer#MAX_VALUE}.
      */
     public static MultidimensionalCounter of(int... size) {
         return new MultidimensionalCounter(size);
@@ -193,12 +199,23 @@ public final class MultidimensionalCounter {
     /**
      * Check the size is strictly positive: {@code size > 0}.
      *
-     * @param name the name of the size
      * @param size the size
      */
-    private static void checkStrictlyPositive(String name, int size) {
+    private static void checkSizeStrictlyPositive(int size) {
         if (size <= 0) {
-            throw new IllegalArgumentException("Not positive " + name + ": " + 
size);
+            throw new IllegalArgumentException("Dimension size not strictly 
positive: " + size);
+        }
+    }
+
+    /**
+     * Check the size is representable as an {@code int}: {@code <= 
Integer.MAX_VALUE}.
+     * It is assumed the size is positive.
+     *
+     * @param size the size
+     */
+    private static void checkSizeRepresentable(long size) {
+        if (size > Integer.MAX_VALUE) {
+            throw new IllegalArgumentException("Cumulative size too large: " + 
size);
         }
     }
 
diff --git 
a/commons-numbers-arrays/src/test/java/org/apache/commons/numbers/arrays/MultidimensionalCounterTest.java
 
b/commons-numbers-arrays/src/test/java/org/apache/commons/numbers/arrays/MultidimensionalCounterTest.java
index 50a93814..b43ffaee 100644
--- 
a/commons-numbers-arrays/src/test/java/org/apache/commons/numbers/arrays/MultidimensionalCounterTest.java
+++ 
b/commons-numbers-arrays/src/test/java/org/apache/commons/numbers/arrays/MultidimensionalCounterTest.java
@@ -42,6 +42,30 @@ class MultidimensionalCounterTest {
         Assertions.assertThrows(IndexOutOfBoundsException.class, () -> 
c.toMulti(6));
     }
 
+    @Test
+    void testSizeProductOverflow() {
+        // NUMBERS-212
+        // Size products that wrap modulo 2^32 back into positive range
+        // previously passed the constructor checks, creating an invalid 
counter.
+        // Total size wraps to +65536.
+        Assertions.assertThrows(IllegalArgumentException.class,
+            () -> MultidimensionalCounter.of(65537, 65536));
+        // Total size 4294967298 wraps to +2.
+        Assertions.assertThrows(IllegalArgumentException.class,
+            () -> MultidimensionalCounter.of(3, 1431655766));
+        // Cumulative (prefix) product wraps to +4, bypassing the per-step 
check.
+        Assertions.assertThrows(IllegalArgumentException.class,
+            () -> MultidimensionalCounter.of(2, 1073741825, 4));
+        // Cumulative product 641 * 6700417 = 2^32 + 1 wraps to +1.
+        Assertions.assertThrows(IllegalArgumentException.class,
+            () -> MultidimensionalCounter.of(1, 641, 6700417));
+        // The maximum representable total size is still accepted.
+        Assertions.assertEquals(Integer.MAX_VALUE,
+            MultidimensionalCounter.of(1, Integer.MAX_VALUE).getSize());
+        Assertions.assertEquals(Integer.MAX_VALUE,
+            MultidimensionalCounter.of(Integer.MAX_VALUE, 1).getSize());
+    }
+
     @Test
     void testMulti2UniConversion() {
         final MultidimensionalCounter c = MultidimensionalCounter.of(2, 4, 5);
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 2b9c2d5f..2cf87881 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,10 @@ If the output is not quite correct, check for invisible 
trailing spaces!
     <release version="1.4" date="TBD" description="
 New features, updates and bug fixes.
 ">
+      <action dev="aherbert" type="fix" due-to="Security scan, Alex Herbert" 
issue="NUMBERS-212">
+        "MultidimensionalCounter": Any product of strictly positive dimension 
sizes
+        equal or above 2^31 is rejected.
+      </action>
       <action dev="aherbert" type="fix" due-to="Security scan, Alex Herbert" 
issue="NUMBERS-211">
         "BrentSolver": Modifies the convergence tolerance to at least 1 unit 
in the last
         place (ULP) of the current best solution. Avoids an infinite loop when 
the

Reply via email to