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