Skip to content

Commit 9d46add

Browse files
kevinwilfongamitkdutta
authored andcommitted
Fix silent overflow in DateTimeEncoding.pack
1 parent 6ee450c commit 9d46add

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

presto-common/src/main/java/com/facebook/presto/common/type/DateTimeEncoding.java

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import static com.facebook.presto.common.type.TimeZoneKey.getTimeZoneKey;
1717
import static com.facebook.presto.common.type.TimeZoneKey.getTimeZoneKeyForOffset;
18+
import static java.lang.String.format;
1819
import static java.util.Objects.requireNonNull;
1920

2021
public final class DateTimeEncoding
@@ -25,9 +26,14 @@ private DateTimeEncoding()
2526

2627
private static final int TIME_ZONE_MASK = 0xFFF;
2728
private static final int MILLIS_SHIFT = 12;
29+
private static final long MAX_MILLIS = 0x7FFFFFFFFFFFFL;
30+
private static final long MIN_MILLIS = (MAX_MILLIS + 1) * -1;
2831

2932
private static long pack(long millisUtc, short timeZoneKey)
3033
{
34+
if (millisUtc > MAX_MILLIS || millisUtc < MIN_MILLIS) {
35+
throw new ArithmeticException(format("TimestampWithTimeZone overflow: %s ms", millisUtc));
36+
}
3137
return (millisUtc << MILLIS_SHIFT) | (timeZoneKey & TIME_ZONE_MASK);
3238
}
3339

presto-main/src/main/java/com/facebook/presto/operator/scalar/DateTimeFunctions.java

+28
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import static com.facebook.presto.operator.scalar.QuarterOfYearDateTimeField.QUARTER_OF_YEAR;
5151
import static com.facebook.presto.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
5252
import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED;
53+
import static com.facebook.presto.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE;
5354
import static com.facebook.presto.spi.function.SqlFunctionVisibility.HIDDEN;
5455
import static com.facebook.presto.type.DateTimeOperators.modulo24Hour;
5556
import static com.facebook.presto.util.DateTimeZoneIndex.extractZoneOffsetMinutes;
@@ -128,6 +129,9 @@ public static long currentTime(SqlFunctionProperties properties)
128129
catch (IllegalArgumentException e) {
129130
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
130131
}
132+
catch (ArithmeticException e) {
133+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
134+
}
131135
}
132136

133137
@Description("current time without time zone")
@@ -164,6 +168,9 @@ public static long currentTimestamp(SqlFunctionProperties properties)
164168
catch (IllegalArgumentException e) {
165169
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
166170
}
171+
catch (ArithmeticException e) {
172+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
173+
}
167174
}
168175

169176
@Description("current timestamp without time zone")
@@ -215,6 +222,9 @@ public static long fromUnixTime(@SqlType(StandardTypes.DOUBLE) double unixTime,
215222
catch (IllegalArgumentException e) {
216223
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
217224
}
225+
catch (ArithmeticException e) {
226+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
227+
}
218228
}
219229

220230
@ScalarFunction("from_unixtime")
@@ -231,6 +241,9 @@ public static long fromUnixTime(@SqlType(StandardTypes.DOUBLE) double unixTime,
231241
catch (IllegalArgumentException e) {
232242
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
233243
}
244+
catch (ArithmeticException e) {
245+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
246+
}
234247
}
235248

236249
@ScalarFunction("to_unixtime")
@@ -308,6 +321,9 @@ public static long fromISO8601Timestamp(SqlFunctionProperties properties, @SqlTy
308321
catch (IllegalArgumentException e) {
309322
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
310323
}
324+
catch (ArithmeticException e) {
325+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
326+
}
311327
}
312328

313329
@ScalarFunction("from_iso8601_date")
@@ -352,6 +368,9 @@ public static long timestampAtTimeZone(@SqlType(StandardTypes.TIMESTAMP_WITH_TIM
352368
catch (IllegalArgumentException e) {
353369
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
354370
}
371+
catch (ArithmeticException e) {
372+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
373+
}
355374
}
356375

357376
@ScalarFunction(value = "at_timezone", visibility = HIDDEN)
@@ -369,6 +388,9 @@ public static long timestampAtTimeZone(@SqlType(StandardTypes.TIMESTAMP_WITH_TIM
369388
catch (IllegalArgumentException e) {
370389
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
371390
}
391+
catch (ArithmeticException e) {
392+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
393+
}
372394
}
373395

374396
@Description("truncate to the specified precision in the session timezone")
@@ -642,6 +664,9 @@ public static long parseDatetime(SqlFunctionProperties properties, @SqlType("var
642664
catch (IllegalArgumentException e) {
643665
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e);
644666
}
667+
catch (ArithmeticException e) {
668+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
669+
}
645670
}
646671

647672
private static DateTime parseDateTimeHelper(DateTimeFormatter formatter, String datetimeString)
@@ -1451,6 +1476,9 @@ private static long timeAtTimeZone(SqlFunctionProperties properties, long timeWi
14511476
catch (IllegalArgumentException e) {
14521477
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, e.getMessage(), e);
14531478
}
1479+
catch (ArithmeticException e) {
1480+
throw new PrestoException(NUMERIC_VALUE_OUT_OF_RANGE, e.getMessage(), e);
1481+
}
14541482
}
14551483

14561484
// HACK WARNING!

presto-main/src/test/java/com/facebook/presto/operator/scalar/TestDateTimeFunctionsBase.java

+55
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,24 @@ public void testFromUnixTimeWithTimeZone()
248248
assertFunction(format("from_unixtime(7200, '%s')", zoneId), TIMESTAMP_WITH_TIME_ZONE, toTimestampWithTimeZone(expected));
249249
}
250250

251+
@Test
252+
public void testFromUnixTimeWithTimeZoneOverflow()
253+
{
254+
String zoneId = "Asia/Shanghai";
255+
256+
// Test the largest possible valid value.
257+
DateTime expected = new DateTime(73326, 9, 12, 4, 14, 45, DateTimeZone.forID(zoneId));
258+
assertFunction(format("from_unixtime(2251799813685, '%s')", zoneId), TIMESTAMP_WITH_TIME_ZONE, toTimestampWithTimeZone(expected));
259+
260+
// Test the smallest possible valid value.
261+
expected = new DateTime(-69387, 4, 22, 11, 50, 58, DateTimeZone.forID(zoneId));
262+
assertFunction(format("from_unixtime(-2251799813685, '%s')", zoneId), TIMESTAMP_WITH_TIME_ZONE, toTimestampWithTimeZone(expected));
263+
264+
// Test the values just outside the range of valid values.
265+
assertNumericOverflow(format("from_unixtime(2251799813686, '%s')", zoneId), "TimestampWithTimeZone overflow: 2251799813686000 ms");
266+
assertNumericOverflow(format("from_unixtime(-2251799813686, '%s')", zoneId), "TimestampWithTimeZone overflow: -2251799813686000 ms");
267+
}
268+
251269
@Test
252270
public void testToUnixTime()
253271
{
@@ -271,6 +289,24 @@ public void testFromISO8601()
271289
assertFunction("from_iso8601_date('" + DATE_ISO8601_STRING + "')", DateType.DATE, toDate(DATE));
272290
}
273291

292+
@Test
293+
public void testFromISO8601Overflow()
294+
{
295+
String zoneId = "Z";
296+
297+
// Test the largest possible valid value.
298+
DateTime expected = new DateTime(73326, 9, 11, 20, 14, 45, 247, DateTimeZone.forID(zoneId));
299+
assertFunction(format("from_iso8601_timestamp('73326-09-11T20:14:45.247%s')", zoneId), TIMESTAMP_WITH_TIME_ZONE, toTimestampWithTimeZone(expected));
300+
301+
// Test the smallest possible valid value.
302+
expected = new DateTime(-69387, 12, 31, 23, 59, 59, 999, DateTimeZone.forID(zoneId));
303+
assertFunction(format("from_iso8601_timestamp('-69387-12-31T23:59:59.999%s')", zoneId), TIMESTAMP_WITH_TIME_ZONE, toTimestampWithTimeZone(expected));
304+
305+
// Test the values just outside the range of valid values.
306+
assertNumericOverflow(format("from_iso8601_timestamp('73326-09-11T20:14:45.248%s')", zoneId), "TimestampWithTimeZone overflow: 2251799813685248 ms");
307+
assertNumericOverflow(format("from_iso8601_timestamp('-69388-01-01T00:00:00.000%s')", zoneId), "TimestampWithTimeZone overflow: -2251841040000000 ms");
308+
}
309+
274310
@Test
275311
public void testToIso8601()
276312
{
@@ -764,6 +800,25 @@ public void testParseDatetime()
764800
toTimestampWithTimeZone(new DateTime(1960, 1, 22, 3, 4, 0, 0, DateTimeZone.forOffsetHours(5))));
765801
}
766802

803+
@Test
804+
public void testParseDatetimeOverflow()
805+
{
806+
String zoneId = "Z";
807+
String pattern = "yyyy/MM/dd HH:mm:ss.SSS Z";
808+
809+
// Test the largest possible valid value.
810+
DateTime expected = new DateTime(73326, 9, 11, 20, 14, 45, 247, DateTimeZone.forID(zoneId));
811+
assertFunction(format("parse_datetime('73326/09/11 20:14:45.247 %s', '%s')", zoneId, pattern), TIMESTAMP_WITH_TIME_ZONE, toTimestampWithTimeZone(expected));
812+
813+
// Test the smallest possible valid value.
814+
expected = new DateTime(-69387, 12, 31, 23, 59, 59, 999, DateTimeZone.forID(zoneId));
815+
assertFunction(format("parse_datetime('-69387/12/31 23:59:59.999 %s', '%s')", zoneId, pattern), TIMESTAMP_WITH_TIME_ZONE, toTimestampWithTimeZone(expected));
816+
817+
// Test the values just outside the range of valid values.
818+
assertNumericOverflow(format("parse_datetime('73326/09/11 20:14:45.248 %s', '%s')", zoneId, pattern), "TimestampWithTimeZone overflow: 2251799813685248 ms");
819+
assertNumericOverflow(format("parse_datetime('-69388/01/01 00:00:00.000 %s', '%s')", zoneId, pattern), "TimestampWithTimeZone overflow: -2251841040000000 ms");
820+
}
821+
767822
@Test
768823
public void testFormatDatetime()
769824
{

0 commit comments

Comments
 (0)