Skip to content

Fix PGInterval.setSeconds to reject out of range and NaN values - #4194

Merged
sehrope merged 2 commits into
pgjdbc:masterfrom
sehrope:fix-pg-internal-set-seconds-silent-wrap
Jun 17, 2026
Merged

sehrope merged 2 commits into
pgjdbc:masterfrom
sehrope:fix-pg-internal-set-seconds-silent-wrap

Conversation

@sehrope

@sehrope sehrope commented Jun 17, 2026

Copy link
Copy Markdown
Member

PGInterval.setSeconds validated its argument against the long range in microseconds, but wholeSeconds is an int and the cast after division truncated unchecked. Values beyond the int range of whole seconds silently wrapped, e.g. new PGInterval(0, 0, 0, 0, 0, 3e9) produced a negative interval with no exception.

Whole seconds is now computed as a long and checked against the int range before being stored, so out of range values throw IllegalArgumentException instead of wrapping. NaN is rejected explicitly (it previously passed the comparisons and Math.round mapped it to a zero interval). Infinite values are caught by the range check since Math.round saturates at the long bounds.

Two slight behavioral changes are:

  1. isNull is cleared only after validation succeeds, so a rejected value no longer mutates the object.
  2. The IllegalStateException error message text is updated a bit

I don't consider either to be a breaking change though.

Second commits adds some tests for the for the boundary, out of range, NaN, and infinite cases (which fail without the fix).

PGInterval.setSeconds validated its argument against the long range in
microseconds, but the wholeSeconds field is an int and the cast after
division truncated without any check. Values beyond the int range of
whole seconds, about 68 years, silently wrapped. For example
new PGInterval(0, 0, 0, 0, 0, 3000000000.0) produced a negative
interval with no exception. The old error message also described a
bound that the code never enforced on the stored field.

The whole seconds value is now computed as a long and checked against
the int range before being stored, so out of range values throw
IllegalArgumentException instead of wrapping. NaN is rejected
explicitly because it passed the old comparisons and Math.round mapped
it to zero, turning NaN input into a zero interval. Infinite values are
caught by the range check since Math.round saturates at the long
bounds. The isNull flag is now only cleared after validation succeeds
so a rejected value no longer mutates the object.
@vlsi

vlsi commented Jun 17, 2026

Copy link
Copy Markdown
Member

LGTM.
Have you considered adding Integer.MAX_VALUE + 0.4 and Integer.MIN_VALUE - 0.4 tests as well?

The boundary tests verify that Integer.MIN_VALUE and Integer.MAX_VALUE
whole seconds are stored exactly. The rejection tests cover values just
past those bounds, values around 95 years of seconds that previously
wrapped the int wholeSeconds field to a negative interval, and NaN and
infinite inputs, all of which must throw IllegalArgumentException.
@sehrope
sehrope force-pushed the fix-pg-internal-set-seconds-silent-wrap branch from e7ee6f0 to dc007f7 Compare June 17, 2026 14:07
@sehrope

sehrope commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

I added two more tests right around the int max / min values:

diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
index 6b0c139c..d07b9f57 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java
@@ -185,6 +185,14 @@ void setSecondsAtWholeSecondsBoundary() {
     pgi.setSeconds((double) Integer.MIN_VALUE);
     assertEquals(Integer.MIN_VALUE, pgi.getWholeSeconds());
     assertEquals(0, pgi.getMicroSeconds());
+
+    pgi.setSeconds(Integer.MIN_VALUE - .456);
+    assertEquals(Integer.MIN_VALUE, pgi.getWholeSeconds());
+    assertEquals(-456000, pgi.getMicroSeconds());
+
+    pgi.setSeconds(Integer.MAX_VALUE + .456);
+    assertEquals(Integer.MAX_VALUE, pgi.getWholeSeconds());
+    assertEquals(456000, pgi.getMicroSeconds());
   }
 
   @Test

I'll merge this after it cycles.

@sehrope
sehrope merged commit dc007f7 into pgjdbc:master Jun 17, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants