This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git
The following commit(s) were added to refs/heads/master by this push:
new 3dbd6861b clamp size() int overflow in composite and multi-valued
classes (#710)
3dbd6861b is described below
commit 3dbd6861bb32d2a41520514f5c2ded7b1a830245
Author: Naveed Khan <[email protected]>
AuthorDate: Fri Jul 10 14:33:26 2026 +0000
clamp size() int overflow in composite and multi-valued classes (#710)
CompositeCollection, CompositeSet and CompositeMap sum the sizes of
their composited containers into an int, and AbstractMultiValuedMap and
AbstractMultiSet sum value collection sizes and entry counts the same
way, so a total past Integer.MAX_VALUE wraps negative and size()
breaks the Collection contract. Sum into a long and clamp the return
to Integer.MAX_VALUE. Follow-up to #705 covering the remaining
int/long size mismatches.
---
src/changes/changes.xml | 1 +
.../collection/CompositeCollection.java | 7 +++--
.../commons/collections4/map/CompositeMap.java | 4 +--
.../multimap/AbstractMultiValuedMap.java | 4 +--
.../collections4/multiset/AbstractMultiSet.java | 7 +++--
.../commons/collections4/set/CompositeSet.java | 7 +++--
.../collection/CompositeCollectionTest.java | 11 ++++++++
.../commons/collections4/map/CompositeMapTest.java | 25 ++++++++++++++++
.../multimap/AbstractMultiValuedMapTest.java | 17 +++++++++++
.../multiset/AbstractMultiSetTest.java | 33 ++++++++++++++++++++++
.../commons/collections4/set/CompositeSetTest.java | 26 +++++++++++++++++
11 files changed, 129 insertions(+), 13 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4efffe41a..b4b85fa88 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -25,6 +25,7 @@
<release version="4.6.0" date="YYYY-MM-DD" description="This is a feature
and maintenance release. Java 8 or later is required.">
<!-- FIX -->
<action type="fix" dev="ggregory" due-to="Naveed Khan, Gary
Gregory">AbstractMapBag and AbstractMapMultiSet.add(Object, int) overflow the
size and element count past Integer.MAX_VALUE.</action>
+ <action type="fix" dev="ggregory" due-to="Naveed
Khan">CompositeCollection, CompositeSet, CompositeMap, AbstractMultiValuedMap
and AbstractMultiSet size() overflow int when the total exceeds
Integer.MAX_VALUE.</action>
<action type="fix" dev="ggregory" due-to="Gary Gregory"
issue="COLLECTIONS-776">Wrapping PassiveExpiringMap in
Collections.synchronizedMap breaks expiration.</action>
<action type="fix" dev="ggregory" due-to="Naveed Khan, Gary
Gregory">[javadoc] Document that BloomFilter merge operations are non-atomic
and a filter that throws during a merge should be considered invalid.</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceEntry.toReference(ReferenceStrength,
T, int) now throws IllegalArgumentException instead of Error.</action>
diff --git
a/src/main/java/org/apache/commons/collections4/collection/CompositeCollection.java
b/src/main/java/org/apache/commons/collections4/collection/CompositeCollection.java
index 6623fae13..486e132b1 100644
---
a/src/main/java/org/apache/commons/collections4/collection/CompositeCollection.java
+++
b/src/main/java/org/apache/commons/collections4/collection/CompositeCollection.java
@@ -425,15 +425,16 @@ public class CompositeCollection<E> implements
Collection<E>, Serializable {
* This implementation calls {@code size()} on each collection.
* </p>
*
- * @return total number of elements in all contained containers.
+ * @return total number of elements in all contained containers, or
+ * {@code Integer.MAX_VALUE} if the total exceeds it.
*/
@Override
public int size() {
- int size = 0;
+ long size = 0;
for (final Collection<E> item : all) {
size += item.size();
}
- return size;
+ return (int) Math.min(size, Integer.MAX_VALUE);
}
/**
diff --git
a/src/main/java/org/apache/commons/collections4/map/CompositeMap.java
b/src/main/java/org/apache/commons/collections4/map/CompositeMap.java
index 640bb7498..c23fd1bf9 100644
--- a/src/main/java/org/apache/commons/collections4/map/CompositeMap.java
+++ b/src/main/java/org/apache/commons/collections4/map/CompositeMap.java
@@ -536,11 +536,11 @@ public class CompositeMap<K, V> extends
AbstractIterableMap<K, V> implements Ser
*/
@Override
public int size() {
- int size = 0;
+ long size = 0;
for (int i = composite.length - 1; i >= 0; --i) {
size += composite[i].size();
}
- return size;
+ return (int) Math.min(size, Integer.MAX_VALUE);
}
/**
diff --git
a/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java
b/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java
index f6e707c7e..373f68716 100644
---
a/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java
+++
b/src/main/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMap.java
@@ -903,11 +903,11 @@ public abstract class AbstractMultiValuedMap<K, V>
implements MultiValuedMap<K,
// but this requires that all modifications of the multimap
// (including the wrapped collections and entry/value
// collections) are tracked.
- int size = 0;
+ long size = 0;
for (final Collection<V> col : getMap().values()) {
size += col.size();
}
- return size;
+ return (int) Math.min(size, Integer.MAX_VALUE);
}
@Override
diff --git
a/src/main/java/org/apache/commons/collections4/multiset/AbstractMultiSet.java
b/src/main/java/org/apache/commons/collections4/multiset/AbstractMultiSet.java
index a340117c0..7de3788a5 100644
---
a/src/main/java/org/apache/commons/collections4/multiset/AbstractMultiSet.java
+++
b/src/main/java/org/apache/commons/collections4/multiset/AbstractMultiSet.java
@@ -475,15 +475,16 @@ public abstract class AbstractMultiSet<E> extends
AbstractCollection<E> implemen
/**
* Returns the number of elements in this multiset.
*
- * @return current size of the multiset
+ * @return current size of the multiset, or {@code Integer.MAX_VALUE}
+ * if the total exceeds it
*/
@Override
public int size() {
- int totalSize = 0;
+ long totalSize = 0;
for (final Entry<E> entry : entrySet()) {
totalSize += entry.getCount();
}
- return totalSize;
+ return (int) Math.min(totalSize, Integer.MAX_VALUE);
}
/**
diff --git
a/src/main/java/org/apache/commons/collections4/set/CompositeSet.java
b/src/main/java/org/apache/commons/collections4/set/CompositeSet.java
index 5a640f5c1..290606e29 100644
--- a/src/main/java/org/apache/commons/collections4/set/CompositeSet.java
+++ b/src/main/java/org/apache/commons/collections4/set/CompositeSet.java
@@ -465,15 +465,16 @@ public class CompositeSet<E> implements Set<E>,
Serializable {
* <p>
* This implementation calls {@code size()} on each set.
*
- * @return total number of elements in all contained containers
+ * @return total number of elements in all contained containers, or
+ * {@code Integer.MAX_VALUE} if the total exceeds it
*/
@Override
public int size() {
- int size = 0;
+ long size = 0;
for (final Set<E> item : all) {
size += item.size();
}
- return size;
+ return (int) Math.min(size, Integer.MAX_VALUE);
}
/**
diff --git
a/src/test/java/org/apache/commons/collections4/collection/CompositeCollectionTest.java
b/src/test/java/org/apache/commons/collections4/collection/CompositeCollectionTest.java
index b7116edd7..3dacfb4ca 100644
---
a/src/test/java/org/apache/commons/collections4/collection/CompositeCollectionTest.java
+++
b/src/test/java/org/apache/commons/collections4/collection/CompositeCollectionTest.java
@@ -29,6 +29,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
+import org.apache.commons.collections4.bag.HashBag;
import org.junit.jupiter.api.Test;
/**
@@ -424,6 +425,16 @@ public class CompositeCollectionTest<E> extends
AbstractCollectionTest<E> {
assertEquals(set.size(), c.size());
}
+ @Test
+ void testSizeClampsToIntegerMaxValue() {
+ final HashBag<String> one = new HashBag<>();
+ one.add("A", Integer.MAX_VALUE);
+ final HashBag<String> two = new HashBag<>();
+ two.add("B", Integer.MAX_VALUE);
+ final CompositeCollection<String> composite = new
CompositeCollection<>(one, two);
+ assertEquals(Integer.MAX_VALUE, composite.size());
+ }
+
@Test
@SuppressWarnings("unchecked")
void testToCollection() {
diff --git
a/src/test/java/org/apache/commons/collections4/map/CompositeMapTest.java
b/src/test/java/org/apache/commons/collections4/map/CompositeMapTest.java
index c33bb6d94..e315788c0 100644
--- a/src/test/java/org/apache/commons/collections4/map/CompositeMapTest.java
+++ b/src/test/java/org/apache/commons/collections4/map/CompositeMapTest.java
@@ -21,9 +21,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.AbstractMap;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -235,6 +238,28 @@ public class CompositeMapTest<K, V> extends
AbstractIterableMapTest<K, V> {
assertTrue(pass);
}
+ @Test
+ void testSizeClampsToIntegerMaxValue() {
+ final CompositeMap<String, String> map = new
CompositeMap<>(maxSizeMap(), maxSizeMap());
+ assertEquals(Integer.MAX_VALUE, map.size());
+ }
+
+ /** An empty-iterating map that reports {@code Integer.MAX_VALUE}
mappings. */
+ private static Map<String, String> maxSizeMap() {
+ return new AbstractMap<String, String>() {
+
+ @Override
+ public Set<Map.Entry<String, String>> entrySet() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public int size() {
+ return Integer.MAX_VALUE;
+ }
+ };
+ }
+
// void testCreate() throws Exception {
// resetEmpty();
// writeExternalFormToDisk((java.io.Serializable) map,
"src/test/resources/data/test/CompositeMap.emptyCollection.version4.obj");
diff --git
a/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java
b/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java
index f0f670575..46588a3d1 100644
---
a/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java
+++
b/src/test/java/org/apache/commons/collections4/multimap/AbstractMultiValuedMapTest.java
@@ -1279,6 +1279,23 @@ public abstract class AbstractMultiValuedMapTest<K, V>
extends AbstractObjectTes
assertEquals(getSampleTotalValueCount(), makeFullMap().size());
}
+ @Test
+ void testSizeClampsToIntegerMaxValue() {
+ // bag-valued map: value collection sizes are counts, so the total is
cheap to grow past Integer.MAX_VALUE
+ final AbstractMultiValuedMap<String, String> map = new
AbstractMultiValuedMap<String, String>(new HashMap<>()) {
+
+ @Override
+ protected Collection<String> createCollection() {
+ return new HashBag<>();
+ }
+ };
+ map.put("k1", "v1");
+ map.put("k2", "v2");
+ ((HashBag<String>) map.getMap().get("k1")).add("v1", Integer.MAX_VALUE
- 1);
+ ((HashBag<String>) map.getMap().get("k2")).add("v2", Integer.MAX_VALUE
- 1);
+ assertEquals(Integer.MAX_VALUE, map.size());
+ }
+
@Test
@SuppressWarnings("unchecked")
void testSize_Key() {
diff --git
a/src/test/java/org/apache/commons/collections4/multiset/AbstractMultiSetTest.java
b/src/test/java/org/apache/commons/collections4/multiset/AbstractMultiSetTest.java
index 90b5fa1b8..a3b52d251 100644
---
a/src/test/java/org/apache/commons/collections4/multiset/AbstractMultiSetTest.java
+++
b/src/test/java/org/apache/commons/collections4/multiset/AbstractMultiSetTest.java
@@ -691,6 +691,39 @@ public abstract class AbstractMultiSetTest<T> extends
AbstractCollectionTest<T>
assertEquals(2, multiset.size(), "Should have 2 total item");
}
+ @Test
+ void testMultiSetSizeClampsToIntegerMaxValue() {
+ final List<MultiSet.Entry<String>> entries =
Arrays.asList(maxCountEntry("A"), maxCountEntry("B"));
+ final MultiSet<String> multiset = new AbstractMultiSet<String>() {
+
+ @Override
+ protected Iterator<MultiSet.Entry<String>>
createEntrySetIterator() {
+ return entries.iterator();
+ }
+
+ @Override
+ protected int uniqueElements() {
+ return entries.size();
+ }
+ };
+ assertEquals(Integer.MAX_VALUE, multiset.size());
+ }
+
+ private static MultiSet.Entry<String> maxCountEntry(final String element) {
+ return new AbstractMultiSet.AbstractEntry<String>() {
+
+ @Override
+ public int getCount() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public String getElement() {
+ return element;
+ }
+ };
+ }
+
@Test
@SuppressWarnings("unchecked")
void testMultiSetToArray() {
diff --git
a/src/test/java/org/apache/commons/collections4/set/CompositeSetTest.java
b/src/test/java/org/apache/commons/collections4/set/CompositeSetTest.java
index 1951338a1..0a497bb2d 100644
--- a/src/test/java/org/apache/commons/collections4/set/CompositeSetTest.java
+++ b/src/test/java/org/apache/commons/collections4/set/CompositeSetTest.java
@@ -21,8 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.AbstractSet;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -180,6 +183,29 @@ public class CompositeSetTest<E> extends
AbstractSetTest<E> {
assertFalse(one.contains("3"));
}
+ @Test
+ void testSizeClampsToIntegerMaxValue() {
+ final CompositeSet<String> set = new CompositeSet<>(maxSizeSet());
+ set.addComposited(maxSizeSet());
+ assertEquals(Integer.MAX_VALUE, set.size());
+ }
+
+ /** An empty-iterating set that reports {@code Integer.MAX_VALUE}
elements. */
+ private static Set<String> maxSizeSet() {
+ return new AbstractSet<String>() {
+
+ @Override
+ public Iterator<String> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public int size() {
+ return Integer.MAX_VALUE;
+ }
+ };
+ }
+
@Test
@SuppressWarnings("unchecked")
void testRemoveUnderlying() {