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-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new 764b42f87 re-assert CharRange start <= end invariant in readObject 
(#1693)
764b42f87 is described below

commit 764b42f876be88b595e8e7deb5dce37566df9efa
Author: alhuda <[email protected]>
AuthorDate: Tue Jun 9 01:31:51 2026 +0530

    re-assert CharRange start <= end invariant in readObject (#1693)
    
    Co-authored-by: alhudz <[email protected]>
---
 .../java/org/apache/commons/lang3/CharRange.java   | 19 +++++++
 .../commons/lang3/CharRangeReadObjectTest.java     | 63 ++++++++++++++++++++++
 2 files changed, 82 insertions(+)

diff --git a/src/main/java/org/apache/commons/lang3/CharRange.java 
b/src/main/java/org/apache/commons/lang3/CharRange.java
index e38efeb32..88bb0aa4a 100644
--- a/src/main/java/org/apache/commons/lang3/CharRange.java
+++ b/src/main/java/org/apache/commons/lang3/CharRange.java
@@ -16,6 +16,9 @@
  */
 package org.apache.commons.lang3;
 
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
 import java.io.Serializable;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -343,6 +346,22 @@ public Iterator<Character> iterator() {
         return new CharacterIterator(this);
     }
 
+    /**
+     * Re-asserts the {@code start <= end} invariant after default 
deserialization. The constructor reverses reversed endpoints, so a legitimately 
serialized
+     * instance always has {@code start <= end}; a stream that violates this 
did not come from the constructor and is rejected.
+     *
+     * @param in See {@link Serializable}.
+     * @throws IOException            See {@link Serializable}.
+     * @throws ClassNotFoundException See {@link Serializable}.
+     * @throws InvalidObjectException If {@code start} is greater than {@code 
end}.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        in.defaultReadObject();
+        if (start > end) {
+            throw new InvalidObjectException("CharRange start is greater than 
end.");
+        }
+    }
+
     /**
      * Gets a string representation of the character range.
      *
diff --git 
a/src/test/java/org/apache/commons/lang3/CharRangeReadObjectTest.java 
b/src/test/java/org/apache/commons/lang3/CharRangeReadObjectTest.java
new file mode 100644
index 000000000..b79be865f
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/CharRangeReadObjectTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.lang3;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that a serialized {@link CharRange} can't carry an inverted ({@code 
start > end}) range.
+ */
+class CharRangeReadObjectTest {
+
+    private static Object deserialize(final byte[] bytes) throws IOException, 
ClassNotFoundException {
+        try (ObjectInputStream ois = new ObjectInputStream(new 
ByteArrayInputStream(bytes))) {
+            return ois.readObject();
+        }
+    }
+
+    @Test
+    void testForgedInvertedRangeIsRejected() throws Exception {
+        // Negative control: the factory reverses reversed endpoints, so no 
inverted range can be built directly.
+        assertEquals(CharRange.isIn('a', 'e'), CharRange.isIn('e', 'a'));
+        final CharRange seed = CharRange.isIn('a', 'e');
+        FieldUtils.writeDeclaredField(seed, "start", 'z', true);
+        FieldUtils.writeDeclaredField(seed, "end", 'a', true);
+        assertThrows(InvalidObjectException.class, () -> 
deserialize(SerializationUtils.serialize(seed)));
+        assertThrows(SerializationException.class, () -> 
SerializationUtils.roundtrip(seed));
+    }
+
+    @Test
+    void testRoundTripPreservesRange() throws Exception {
+        final CharRange range = CharRange.isIn('a', 'e');
+        final CharRange roundtrip = SerializationUtils.roundtrip(range);
+        assertEquals(range, roundtrip);
+        assertTrue(roundtrip.contains('c'));
+        assertFalse(roundtrip.contains('z'));
+    }
+}

Reply via email to