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 a9ae10e31 Fraction.readObject does not re-assert denominator != 0 
(#1688)
a9ae10e31 is described below

commit a9ae10e31066a183050b356ecf667b23ace21a61
Author: Gary Gregory <[email protected]>
AuthorDate: Wed Jun 3 13:44:18 2026 -0400

    Fraction.readObject does not re-assert denominator != 0 (#1688)
---
 .../org/apache/commons/lang3/math/Fraction.java    | 19 +++++++-------
 .../commons/lang3/math/FractionReadObjectTest.java | 29 +++++++++++++++++++++-
 2 files changed, 38 insertions(+), 10 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/math/Fraction.java 
b/src/main/java/org/apache/commons/lang3/math/Fraction.java
index b0a9476f4..a1dd4a1ae 100644
--- a/src/main/java/org/apache/commons/lang3/math/Fraction.java
+++ b/src/main/java/org/apache/commons/lang3/math/Fraction.java
@@ -121,6 +121,12 @@ private static int addAndCheck(final int x, final int y) {
         return (int) s;
     }
 
+    private static void checkDenominator(final int denominator) {
+        if (denominator == 0) {
+            throw new ArithmeticException("The denominator must not be zero");
+        }
+    }
+
     /**
      * Creates a {@link Fraction} instance from a {@code double} value.
      * <p>
@@ -194,9 +200,7 @@ public static Fraction getFraction(double value) {
      * @throws ArithmeticException if the denominator is {@code zero} or the 
denominator is {@code negative} and the numerator is {@code Integer#MIN_VALUE}
      */
     public static Fraction getFraction(int numerator, int denominator) {
-        if (denominator == 0) {
-            throw new ArithmeticException("The denominator must not be zero");
-        }
+        checkDenominator(denominator);
         if (denominator < 0) {
             if (numerator == Integer.MIN_VALUE || denominator == 
Integer.MIN_VALUE) {
                 throw new ArithmeticException("overflow: can't negate");
@@ -223,9 +227,7 @@ public static Fraction getFraction(int numerator, int 
denominator) {
      * @throws ArithmeticException if the resulting numerator exceeds {@code 
Integer.MAX_VALUE}
      */
     public static Fraction getFraction(final int whole, final int numerator, 
final int denominator) {
-        if (denominator == 0) {
-            throw new ArithmeticException("The denominator must not be zero");
-        }
+        checkDenominator(denominator);
         if (denominator < 0) {
             throw new ArithmeticException("The denominator must not be 
negative");
         }
@@ -313,9 +315,7 @@ public static Fraction getFraction(String str) {
      * @throws ArithmeticException if the denominator is {@code zero}
      */
     public static Fraction getReducedFraction(int numerator, int denominator) {
-        if (denominator == 0) {
-            throw new ArithmeticException("The denominator must not be zero");
-        }
+        checkDenominator(denominator);
         if (numerator == 0) {
             return ZERO; // normalize zero.
         }
@@ -855,6 +855,7 @@ public Fraction pow(final int power) {
      */
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
         in.defaultReadObject();
+        checkDenominator(denominator);
         if (hashCode != hash(denominator, numerator)) {
             throw new InvalidObjectException("Fraction hashCode does not match 
numerator/denominator.");
         }
diff --git 
a/src/test/java/org/apache/commons/lang3/math/FractionReadObjectTest.java 
b/src/test/java/org/apache/commons/lang3/math/FractionReadObjectTest.java
index ecbc944c6..62537f818 100644
--- a/src/test/java/org/apache/commons/lang3/math/FractionReadObjectTest.java
+++ b/src/test/java/org/apache/commons/lang3/math/FractionReadObjectTest.java
@@ -21,9 +21,12 @@
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.io.ByteArrayInputStream;
 import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import org.apache.commons.lang3.SerializationException;
 import org.apache.commons.lang3.SerializationUtils;
@@ -36,6 +39,16 @@
  */
 public class FractionReadObjectTest {
 
+    private static Object deserialize(final byte[] bytes) throws Exception {
+        try (ObjectInputStream ois = new ObjectInputStream(new 
ByteArrayInputStream(bytes))) {
+            return ois.readObject();
+        }
+    }
+
+    private static void setInt(final Object target, final String name, final 
int value) throws Exception {
+        FieldUtils.writeDeclaredField(target, name, value, true);
+    }
+
     @Test
     public void testBadHashCodeStreamIsRejected() throws Exception {
         final Fraction fraction = Fraction.getFraction(3, 7);
@@ -46,7 +59,22 @@ public void testBadHashCodeStreamIsRejected() throws 
Exception {
                 "Bad hashCode in stream must be rejected with 
InvalidObjectException");
         assertInstanceOf(InvalidObjectException.class, ex.getCause());
         assertEquals("java.io.InvalidObjectException: Fraction hashCode does 
not match numerator/denominator.", ex.getMessage());
+    }
 
+    /**
+     * Forged stream: numerator=0, denominator=0, hashCode=hash(0,0). The 
public factory refuses denominator 0 (negative control); readObject does not, 
so the
+     * forged Fraction(0,0) deserializes and divides by zero on {@code 
intValue()}.
+     */
+    @Test
+    void testForgedZeroDenominatorDividesByZero() throws Exception {
+        // Negative control: no public factory can build a zero-denominator 
Fraction.
+        assertThrows(ArithmeticException.class, () -> Fraction.getFraction(0, 
0));
+        final Fraction seed = Fraction.getFraction(1, 2);
+        setInt(seed, "numerator", 0);
+        setInt(seed, "denominator", 0);
+        setInt(seed, "hashCode", Objects.hash(Integer.valueOf(0), 
Integer.valueOf(0)));
+        assertThrows(ArithmeticException.class, () -> 
deserialize(SerializationUtils.serialize(seed)));
+        assertThrows(ArithmeticException.class, () -> 
SerializationUtils.roundtrip(seed));
     }
 
     @Test
@@ -65,6 +93,5 @@ public void testRoundTripPreservesHashCode() throws Exception 
{
         final Fraction roundtrip = SerializationUtils.roundtrip(fraction);
         assertEquals(fraction.hashCode(), roundtrip.hashCode(), "Round-trip 
serialization must preserve the correct hashCode");
         assertEquals(fraction, roundtrip);
-
     }
 }

Reply via email to