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 6cc1adac1 validate order and uniqueness invariants in decorator
readObject (#684)
6cc1adac1 is described below
commit 6cc1adac1596cd9324aab53d8f31eb5c2d610d8d
Author: Dexter.k <[email protected]>
AuthorDate: Fri Jun 19 12:35:31 2026 +0000
validate order and uniqueness invariants in decorator readObject (#684)
ListOrderedMap, ListOrderedSet and SetUniqueList restore the order/dedup
structure and the backing collection from the stream as independent fields;
re-check they agree on read and throw InvalidObjectException for a crafted
stream that would build a decorator the constructors forbid.
---
.../commons/collections4/list/SetUniqueList.java | 18 +++++++++++++++++
.../commons/collections4/map/ListOrderedMap.java | 5 +++++
.../commons/collections4/set/ListOrderedSet.java | 18 +++++++++++++++++
.../collections4/list/SetUniqueListTest.java | 23 ++++++++++++++++++++++
.../collections4/map/ListOrderedMapTest.java | 23 ++++++++++++++++++++++
.../collections4/set/ListOrderedSetTest.java | 23 ++++++++++++++++++++++
6 files changed, 110 insertions(+)
diff --git
a/src/main/java/org/apache/commons/collections4/list/SetUniqueList.java
b/src/main/java/org/apache/commons/collections4/list/SetUniqueList.java
index 30bba7f9a..286ebe45d 100644
--- a/src/main/java/org/apache/commons/collections4/list/SetUniqueList.java
+++ b/src/main/java/org/apache/commons/collections4/list/SetUniqueList.java
@@ -16,6 +16,9 @@
*/
package org.apache.commons.collections4.list;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
@@ -427,4 +430,19 @@ public class SetUniqueList<E> extends
AbstractSerializableListDecorator<E> {
return ListUtils.unmodifiableList(new SetUniqueList<>(superSubList,
subSet));
}
+ /**
+ * Deserializes the list and re-checks the no-duplicate invariant the
+ * constructors guarantee.
+ *
+ * @param in the input stream
+ * @throws IOException if an error occurs while reading from the stream
+ * @throws ClassNotFoundException if a class read from the stream cannot
be loaded
+ */
+ private void readObject(final ObjectInputStream in) throws IOException,
ClassNotFoundException {
+ in.defaultReadObject();
+ if (set.size() != size() || !new HashSet<>(decorated()).equals(set)) {
+ throw new InvalidObjectException("Inconsistent SetUniqueList
deserialized: backing list does not match the uniqueness set");
+ }
+ }
+
}
diff --git
a/src/main/java/org/apache/commons/collections4/map/ListOrderedMap.java
b/src/main/java/org/apache/commons/collections4/map/ListOrderedMap.java
index 248c0fcb5..f8ddedc77 100644
--- a/src/main/java/org/apache/commons/collections4/map/ListOrderedMap.java
+++ b/src/main/java/org/apache/commons/collections4/map/ListOrderedMap.java
@@ -17,6 +17,7 @@
package org.apache.commons.collections4.map;
import java.io.IOException;
+import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
@@ -25,6 +26,7 @@ import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
@@ -687,6 +689,9 @@ public class ListOrderedMap<K, V>
private void readObject(final ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
map = (Map<K, V>) in.readObject(); // (1)
+ if (insertOrder.size() != map.size() || !new
HashSet<>(insertOrder).equals(map.keySet())) {
+ throw new InvalidObjectException("Inconsistent ListOrderedMap
deserialized: key order does not match the map keys");
+ }
}
/**
diff --git
a/src/main/java/org/apache/commons/collections4/set/ListOrderedSet.java
b/src/main/java/org/apache/commons/collections4/set/ListOrderedSet.java
index f318a64b9..5a50de4a4 100644
--- a/src/main/java/org/apache/commons/collections4/set/ListOrderedSet.java
+++ b/src/main/java/org/apache/commons/collections4/set/ListOrderedSet.java
@@ -16,6 +16,9 @@
*/
package org.apache.commons.collections4.set;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -413,4 +416,19 @@ public class ListOrderedSet<E>
return setOrder.toString();
}
+ /**
+ * Deserializes the set and re-checks that the iteration order matches the
+ * decorated set, as the constructors guarantee.
+ *
+ * @param in the input stream
+ * @throws IOException if an error occurs while reading from the stream
+ * @throws ClassNotFoundException if a class read from the stream cannot
be loaded
+ */
+ private void readObject(final ObjectInputStream in) throws IOException,
ClassNotFoundException {
+ in.defaultReadObject();
+ if (setOrder.size() != size() || !new
HashSet<>(setOrder).equals(decorated())) {
+ throw new InvalidObjectException("Inconsistent ListOrderedSet
deserialized: iteration order does not match the set");
+ }
+ }
+
}
diff --git
a/src/test/java/org/apache/commons/collections4/list/SetUniqueListTest.java
b/src/test/java/org/apache/commons/collections4/list/SetUniqueListTest.java
index 390b66de2..d03ac8a3e 100644
--- a/src/test/java/org/apache/commons/collections4/list/SetUniqueListTest.java
+++ b/src/test/java/org/apache/commons/collections4/list/SetUniqueListTest.java
@@ -22,6 +22,11 @@ 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 java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -101,6 +106,24 @@ public class SetUniqueListTest<E> extends
AbstractListTest<E> {
return new SetUniqueList<>(new ArrayList<>(), new HashSet<>());
}
+ @Test
+ void testDeserializeRejectsDuplicateInBackingList() throws Exception {
+ final SetUniqueList<String> list = SetUniqueList.setUniqueList(new
ArrayList<>());
+ list.add("alpha");
+ list.add("beta");
+ // push a duplicate straight onto the decorated list, bypassing the
uniqueness set
+ list.decorated().add("alpha");
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+ oos.writeObject(list);
+ }
+ assertThrows(InvalidObjectException.class, () -> {
+ try (ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(out.toByteArray()))) {
+ ois.readObject();
+ }
+ });
+ }
+
@Test
@SuppressWarnings("unchecked")
void testAdd() {
diff --git
a/src/test/java/org/apache/commons/collections4/map/ListOrderedMapTest.java
b/src/test/java/org/apache/commons/collections4/map/ListOrderedMapTest.java
index b9d504617..9b35f81df 100644
--- a/src/test/java/org/apache/commons/collections4/map/ListOrderedMapTest.java
+++ b/src/test/java/org/apache/commons/collections4/map/ListOrderedMapTest.java
@@ -21,6 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -166,6 +171,24 @@ public class ListOrderedMapTest<K, V> extends
AbstractOrderedMapTest<K, V> {
return ListOrderedMap.listOrderedMap(new HashMap<>());
}
+ @Test
+ void testDeserializeRejectsKeyOrderMismatch() throws Exception {
+ final ListOrderedMap<String, String> map = new ListOrderedMap<>();
+ map.put("one", "1");
+ map.put("two", "2");
+ // drop a key straight from the backing map; the insert-order list
still names it
+ map.decorated().remove("one");
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+ oos.writeObject(map);
+ }
+ assertThrows(InvalidObjectException.class, () -> {
+ try (ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(out.toByteArray()))) {
+ ois.readObject();
+ }
+ });
+ }
+
@Test
void testCOLLECTIONS_474_nonNullValues() {
final Object key1 = new Object();
diff --git
a/src/test/java/org/apache/commons/collections4/set/ListOrderedSetTest.java
b/src/test/java/org/apache/commons/collections4/set/ListOrderedSetTest.java
index f7afc09e7..c08b1bb8d 100644
--- a/src/test/java/org/apache/commons/collections4/set/ListOrderedSetTest.java
+++ b/src/test/java/org/apache/commons/collections4/set/ListOrderedSetTest.java
@@ -21,6 +21,11 @@ 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 java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -92,6 +97,24 @@ public class ListOrderedSetTest<E>
return set;
}
+ @Test
+ void testDeserializeRejectsOrderMismatch() throws Exception {
+ final ListOrderedSet<String> set = ListOrderedSet.listOrderedSet(new
HashSet<>());
+ set.add("red");
+ set.add("green");
+ // remove an element from the decorated set only; the order list keeps
naming it
+ set.decorated().remove("red");
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+ oos.writeObject(set);
+ }
+ assertThrows(InvalidObjectException.class, () -> {
+ try (ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(out.toByteArray()))) {
+ ois.readObject();
+ }
+ });
+ }
+
@Test
void testDecorator() {
assertThrows(NullPointerException.class, () ->
ListOrderedSet.listOrderedSet((List<E>) null));