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 ddf3bebbe Re-validate entries in PredicatedMap/PredicatedCollection 
readObject (#682)
ddf3bebbe is described below

commit ddf3bebbe6a1b9b19e1b7352bb8f075e4f700cec
Author: Dexter.k <[email protected]>
AuthorDate: Wed Jun 17 11:24:45 2026 +0000

    Re-validate entries in PredicatedMap/PredicatedCollection readObject (#682)
    
    * re-validate entries on deserialization in predicated decorators
    
    * preserve cause and check null invariants in predicated readObject
    
    * throw InvalidObjectException for null fields in readObject
---
 .../collection/PredicatedCollection.java           | 25 ++++++++++++++++++++++
 .../commons/collections4/map/PredicatedMap.java    |  9 ++++++++
 .../collection/PredicatedCollectionTest.java       | 24 +++++++++++++++++++++
 .../collections4/map/PredicatedMapTest.java        | 25 ++++++++++++++++++++++
 4 files changed, 83 insertions(+)

diff --git 
a/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java
 
b/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java
index 24743f0c6..8727fef66 100644
--- 
a/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java
+++ 
b/src/main/java/org/apache/commons/collections4/collection/PredicatedCollection.java
@@ -16,6 +16,9 @@
  */
 package org.apache.commons.collections4.collection;
 
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -409,6 +412,28 @@ public class PredicatedCollection<E> extends 
AbstractCollectionDecorator<E> {
         return decorated().addAll(coll);
     }
 
+    /**
+     * Deserializes the collection in using a custom routine.
+     *
+     * @param in  the input stream
+     * @throws IOException if an error occurs while reading from the stream
+     * @throws ClassNotFoundException if an object read from the stream cannot 
be loaded
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        in.defaultReadObject();
+        if (decorated() == null) {
+            throw new InvalidObjectException("Null collection");
+        }
+        if (predicate == null) {
+            throw new InvalidObjectException("Null predicate");
+        }
+        try {
+            decorated().forEach(this::validate);
+        } catch (final IllegalArgumentException ex) {
+            throw (InvalidObjectException) new 
InvalidObjectException(ex.getMessage()).initCause(ex);
+        }
+    }
+
     /**
      * Validates the object being added to ensure it matches the predicate.
      * <p>
diff --git 
a/src/main/java/org/apache/commons/collections4/map/PredicatedMap.java 
b/src/main/java/org/apache/commons/collections4/map/PredicatedMap.java
index 66b904ffe..d2c0b4d46 100644
--- a/src/main/java/org/apache/commons/collections4/map/PredicatedMap.java
+++ b/src/main/java/org/apache/commons/collections4/map/PredicatedMap.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;
@@ -155,6 +156,14 @@ public class PredicatedMap<K, V>
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
         in.defaultReadObject();
         map = (Map<K, V>) in.readObject(); // (1)
+        if (map == null) {
+            throw new InvalidObjectException("Null map");
+        }
+        try {
+            map.forEach(this::validate);
+        } catch (final IllegalArgumentException ex) {
+            throw (InvalidObjectException) new 
InvalidObjectException(ex.getMessage()).initCause(ex);
+        }
     }
 
     /**
diff --git 
a/src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionTest.java
 
b/src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionTest.java
index 0ab9d4fee..c79847236 100644
--- 
a/src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/collection/PredicatedCollectionTest.java
@@ -19,12 +19,18 @@ package org.apache.commons.collections4.collection;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 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.Arrays;
 import java.util.Collection;
 import java.util.List;
 
 import org.apache.commons.collections4.Predicate;
+import org.apache.commons.collections4.functors.NotNullPredicate;
 import org.apache.commons.collections4.functors.TruePredicate;
 import org.junit.jupiter.api.Test;
 
@@ -76,6 +82,24 @@ public class PredicatedCollectionTest<E> extends 
AbstractCollectionTest<E> {
         return decorateCollection(new ArrayList<>(), truePredicate);
     }
 
+    @Test
+    void testDeserializeRejectsElementFailingPredicate() throws Exception {
+        final PredicatedCollection<E> coll = (PredicatedCollection<E>) 
decorateCollection(
+                new ArrayList<>(), NotNullPredicate.<E>notNullPredicate());
+        // a crafted stream can carry an element that never passed add(); 
mimic it by
+        // writing one straight into the decorated collection
+        coll.decorated().add(null);
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+            oos.writeObject(coll);
+        }
+        assertThrows(InvalidObjectException.class, () -> {
+            try (ObjectInputStream ois = new ObjectInputStream(new 
ByteArrayInputStream(out.toByteArray()))) {
+                ois.readObject();
+            }
+        });
+    }
+
     public Collection<E> makeTestCollection() {
         return decorateCollection(new ArrayList<>(), testPredicate);
     }
diff --git 
a/src/test/java/org/apache/commons/collections4/map/PredicatedMapTest.java 
b/src/test/java/org/apache/commons/collections4/map/PredicatedMapTest.java
index 5173e02f0..462b223b4 100644
--- a/src/test/java/org/apache/commons/collections4/map/PredicatedMapTest.java
+++ b/src/test/java/org/apache/commons/collections4/map/PredicatedMapTest.java
@@ -21,12 +21,18 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 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.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
 import org.apache.commons.collections4.IterableMap;
 import org.apache.commons.collections4.Predicate;
+import org.apache.commons.collections4.functors.NotNullPredicate;
 import org.apache.commons.collections4.functors.TruePredicate;
 import org.junit.jupiter.api.Test;
 
@@ -62,6 +68,25 @@ public class PredicatedMapTest<K, V> extends 
AbstractIterableMapTest<K, V> {
         return decorateMap(new HashMap<>(), testPredicate, testPredicate);
     }
 
+    @Test
+    @SuppressWarnings("unchecked")
+    void testDeserializeRejectsEntryFailingPredicate() throws Exception {
+        final PredicatedMap<K, V> map = (PredicatedMap<K, V>) decorateMap(new 
HashMap<>(),
+                NotNullPredicate.notNullPredicate(), null);
+        // a crafted stream can carry an entry that never passed put(); mimic 
it by
+        // writing one straight into the decorated map
+        map.decorated().put(null, (V) "value");
+        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
     @SuppressWarnings("unchecked")
     void testEntrySet() {

Reply via email to