This is an automated email from the ASF dual-hosted git repository. XiaoHongbo-Hope pushed a commit to branch release-2.1 in repository https://gitbox.apache.org/repos/asf/paimon.git
commit 2d21a16da75a76c357c26b2b627c68f1680d19c0 Author: YangJie <[email protected]> AuthorDate: Fri Sep 11 03:09:08 2026 -0400 [common] Range-check the digit guard in DateTimeUtils (#9649) --- .../org/apache/paimon/utils/DateTimeUtils.java | 26 +++++++++++++++++----- .../org/apache/paimon/utils/DateTimeUtilsTest.java | 26 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java b/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java index d202a5bc47..13f422e8fb 100644 --- a/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java +++ b/paimon-common/src/main/java/org/apache/paimon/utils/DateTimeUtils.java @@ -349,15 +349,31 @@ public class DateTimeUtils { + milli; } + /** + * Whether the string is a non-negative decimal integer that fits in an {@code int}. Callers + * hand the string straight to {@link Integer#parseInt}, so the range matters as much as the + * characters. + */ private static boolean isInteger(String s) { - boolean isInt = s.length() > 0; + if (s.isEmpty()) { + return false; + } + // Accumulate with overflow checking rather than a digit-count limit: a zero-padded + // component such as "00000002024" is still in range, so what matters is the value, not + // how many characters it took to write. Bailing out the moment the running value passes + // Integer.MAX_VALUE keeps the accumulator itself from overflowing a long. + long value = 0; for (int i = 0; i < s.length(); i++) { - if (s.charAt(i) < '0' || s.charAt(i) > '9') { - isInt = false; - break; + char c = s.charAt(i); + if (c < '0' || c > '9') { + return false; + } + value = value * 10 + (c - '0'); + if (value > Integer.MAX_VALUE) { + return false; } } - return isInt; + return true; } private static boolean isIllegalDate(int y, int m, int d) { diff --git a/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java b/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java index 6a0f88a367..a0c86be483 100644 --- a/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/utils/DateTimeUtilsTest.java @@ -33,6 +33,32 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; /** Test for {@link DateTimeUtils}. */ public class DateTimeUtilsTest { + @Test + public void testParseDateAndTimeOverflowReturnsNull() { + // A component too large for an int is an invalid date or time, not a crash: the + // contract of parseDate/parseTime is null for unparseable input. 2147483648 is + // Integer.MAX_VALUE + 1, the smallest ten-digit value that does not fit. + assertThat(DateTimeUtils.parseDate("2147483648-01-01")).isNull(); + assertThat(DateTimeUtils.parseDate("2147483647-01-01")) + .isNull(); // in range, but not a year + assertThat(DateTimeUtils.parseDate("99999999999-01-01")).isNull(); + assertThat(DateTimeUtils.parseDate("2024-99999999999-01")).isNull(); + assertThat(DateTimeUtils.parseDate("2024-01-99999999999")).isNull(); + assertThat(DateTimeUtils.parseTime("99999999999:00:00")).isNull(); + + // Sanity: valid values still parse. + assertThat(DateTimeUtils.parseDate("2024-01-15")).isNotNull(); + assertThat(DateTimeUtils.parseTime("12:30:00")).isNotNull(); + + // Zero-padded components whose value still fits an int must keep parsing: the range + // guard has to judge the value, not the digit count, or previously accepted padded + // dates/times would silently turn into NULL. + assertThat(DateTimeUtils.parseDate("00000002024-01-15")) + .isEqualTo(DateTimeUtils.parseDate("2024-01-15")); + assertThat(DateTimeUtils.parseTime("00000000012:30:00")) + .isEqualTo(DateTimeUtils.parseTime("12:30:00")); + } + @Test public void testFormatLocalDateTime() { LocalDateTime time = LocalDateTime.of(2023, 8, 30, 12, 30, 59, 999_999_999);
