From 8b354159c6c72b0d9564afa3b12f48371cbea767 Mon Sep 17 00:00:00 2001 From: Morten Haraldsen Date: Tue, 23 Jan 2024 14:59:06 +0100 Subject: [PATCH] Fix bug where parsing the rare occurence of leap-second without time-offset (#23) * Added error checking and throwing DateTimeParseException to include the parse position where the error occurred * Add fuzzer tests * Fix bug where parsing the rare occurence of leap-second without time-offset in date-time would fail --------- Co-authored-by: Morten Haraldsen --- pom.xml | 6 +++ .../com/ethlo/time/internal/EthloITU.java | 7 ++- .../java/com/ethlo/time/CorrectnessTest.java | 6 +++ .../time/fuzzer/ParseDateTimeFuzzTest.java | 42 +++++++++++++++ .../time/fuzzer/ParseLenientFuzzTest.java | 51 +++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java create mode 100644 src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java diff --git a/pom.xml b/pom.xml index 324a4af..324c446 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,12 @@ limitations under the License. 5.10.1 test + + com.code-intelligence + jazzer-junit + 0.22.1 + test + diff --git a/src/main/java/com/ethlo/time/internal/EthloITU.java b/src/main/java/com/ethlo/time/internal/EthloITU.java index 1983ca7..2035092 100644 --- a/src/main/java/com/ethlo/time/internal/EthloITU.java +++ b/src/main/java/com/ethlo/time/internal/EthloITU.java @@ -308,7 +308,6 @@ private static Object handleTime(int year, int month, int day, int hour, int min if (remaining == 2) { final int seconds = parsePositiveInt(chars, 17, 19); - leapSecondCheck(year, month, day, hour, minute, 0, 0, null); if (raw) { return new DateTime(Field.SECOND, year, month, day, hour, minute, seconds, 0, null, 0); @@ -389,10 +388,10 @@ else if (c == PLUS || c == MINUS) } final int second = parsePositiveInt(chars, 17, 19); - leapSecondCheck(year, month, day, hour, minute, second, fractions, offset); if (!raw) { + leapSecondCheck(year, month, day, hour, minute, second, fractions, offset); return OffsetDateTime.of(year, month, day, hour, minute, second, fractions, offset.toZoneOffset()); } return fractionDigits > 0 ? DateTime.of(year, month, day, hour, minute, second, fractions, offset, fractionDigits) : DateTime.of(year, month, day, hour, minute, second, offset); @@ -415,8 +414,8 @@ private static void leapSecondCheck(int year, int month, int day, int hour, int final boolean isValidLeapYearMonth = leapSecondHandler.isValidLeapSecondDate(needle); if (isValidLeapYearMonth || needle.isAfter(leapSecondHandler.getLastKnownLeapSecond())) { - final int utcHour = hour - (offset.getTotalSeconds() / 3_600); - final int utcMinute = minute - ((offset.getTotalSeconds() % 3_600) / 60); + final int utcHour = hour - offset.getTotalSeconds() / 3_600; + final int utcMinute = minute - (offset.getTotalSeconds() % 3_600) / 60; if (((month == Month.DECEMBER.getValue() && day == 31) || (month == Month.JUNE.getValue() && day == 30)) && utcHour == 23 && utcMinute == 59) diff --git a/src/test/java/com/ethlo/time/CorrectnessTest.java b/src/test/java/com/ethlo/time/CorrectnessTest.java index 4962a9c..d04a052 100644 --- a/src/test/java/com/ethlo/time/CorrectnessTest.java +++ b/src/test/java/com/ethlo/time/CorrectnessTest.java @@ -470,4 +470,10 @@ void testTemporalAccessorNanos() assertThat(parsed.getLong(ChronoField.NANO_OF_SECOND)).isEqualTo(987654321); } + + @Test + void testParseLeapSecondWhenNoTimeOffsetPresent() + { + ITU.parseLenient("3011-10-02T22:00:60.003"); + } } diff --git a/src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java b/src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java new file mode 100644 index 0000000..312c4cb --- /dev/null +++ b/src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java @@ -0,0 +1,42 @@ +package com.ethlo.time.fuzzer; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed 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 + * + * http://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. + * #L% + */ + +import java.time.DateTimeException; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.ethlo.time.ITU; + +public class ParseDateTimeFuzzTest +{ + @com.code_intelligence.jazzer.junit.FuzzTest(maxDuration = "2m") + void parse(FuzzedDataProvider data) + { + try + { + ITU.parseDateTime(data.consumeRemainingAsAsciiString()); + } + catch (DateTimeException ignored) + { + + } + } +} diff --git a/src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java b/src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java new file mode 100644 index 0000000..18fcb90 --- /dev/null +++ b/src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java @@ -0,0 +1,51 @@ +package com.ethlo.time.fuzzer; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed 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 + * + * http://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. + * #L% + */ + +import java.time.DateTimeException; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.ethlo.time.DateTime; +import com.ethlo.time.ITU; + +public class ParseLenientFuzzTest +{ + @com.code_intelligence.jazzer.junit.FuzzTest(maxDuration = "2m") + void parse(FuzzedDataProvider data) + { + DateTime d = null; + try + { + d = ITU.parseLenient(data.consumeRemainingAsString()); + } + catch (DateTimeException ignored) + { + + } + + if (d != null) + { + d.toInstant(); + System.out.println(d); + d.toString(); + } + } +}