diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index 00ae55106dd9cfddeec27db9bb08be719bde4d07..a40c4d2197b188bed51d9f3f3aefcb55829ddce4 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -1157,15 +1157,8 @@ dtoi4(PG_FUNCTION_ARGS) */ num = rint(num); - /* - * Range check. We must be careful here that the boundary values are - * expressed exactly in the float domain. We expect PG_INT32_MIN to be an - * exact power of 2, so it will be represented exactly; but PG_INT32_MAX - * isn't, and might get rounded off, so avoid using it. - */ - if (num < (float8) PG_INT32_MIN || - num >= -((float8) PG_INT32_MIN) || - isnan(num)) + /* Range check */ + if (isnan(num) || !FLOAT8_FITS_IN_INT32(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1189,15 +1182,8 @@ dtoi2(PG_FUNCTION_ARGS) */ num = rint(num); - /* - * Range check. We must be careful here that the boundary values are - * expressed exactly in the float domain. We expect PG_INT16_MIN to be an - * exact power of 2, so it will be represented exactly; but PG_INT16_MAX - * isn't, and might get rounded off, so avoid using it. - */ - if (num < (float8) PG_INT16_MIN || - num >= -((float8) PG_INT16_MIN) || - isnan(num)) + /* Range check */ + if (isnan(num) || !FLOAT8_FITS_IN_INT16(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); @@ -1245,15 +1231,8 @@ ftoi4(PG_FUNCTION_ARGS) */ num = rint(num); - /* - * Range check. We must be careful here that the boundary values are - * expressed exactly in the float domain. We expect PG_INT32_MIN to be an - * exact power of 2, so it will be represented exactly; but PG_INT32_MAX - * isn't, and might get rounded off, so avoid using it. - */ - if (num < (float4) PG_INT32_MIN || - num >= -((float4) PG_INT32_MIN) || - isnan(num)) + /* Range check */ + if (isnan(num) || !FLOAT4_FITS_IN_INT32(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -1277,15 +1256,8 @@ ftoi2(PG_FUNCTION_ARGS) */ num = rint(num); - /* - * Range check. We must be careful here that the boundary values are - * expressed exactly in the float domain. We expect PG_INT16_MIN to be an - * exact power of 2, so it will be represented exactly; but PG_INT16_MAX - * isn't, and might get rounded off, so avoid using it. - */ - if (num < (float4) PG_INT16_MIN || - num >= -((float4) PG_INT16_MIN) || - isnan(num)) + /* Range check */ + if (isnan(num) || !FLOAT4_FITS_IN_INT16(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("smallint out of range"))); diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index ade9b81386df67a5250d1914ba6a7ff959404e8c..2788853c1bf7d65d82b2a732c8a2bb22161a3ac7 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -1351,15 +1351,8 @@ dtoi8(PG_FUNCTION_ARGS) */ num = rint(num); - /* - * Range check. We must be careful here that the boundary values are - * expressed exactly in the float domain. We expect PG_INT64_MIN to be an - * exact power of 2, so it will be represented exactly; but PG_INT64_MAX - * isn't, and might get rounded off, so avoid using it. - */ - if (num < (float8) PG_INT64_MIN || - num >= -((float8) PG_INT64_MIN) || - isnan(num)) + /* Range check */ + if (isnan(num) || !FLOAT8_FITS_IN_INT64(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); @@ -1393,15 +1386,8 @@ ftoi8(PG_FUNCTION_ARGS) */ num = rint(num); - /* - * Range check. We must be careful here that the boundary values are - * expressed exactly in the float domain. We expect PG_INT64_MIN to be an - * exact power of 2, so it will be represented exactly; but PG_INT64_MAX - * isn't, and might get rounded off, so avoid using it. - */ - if (num < (float4) PG_INT64_MIN || - num >= -((float4) PG_INT64_MIN) || - isnan(num)) + /* Range check */ + if (isnan(num) || !FLOAT4_FITS_IN_INT64(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index d24ab8560c8fc5d6d9c948ca7514b05a1413672b..13b24af80ced7d63d6c3081ca8bed01a660f4970 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -44,14 +44,6 @@ #define SAMESIGN(a,b) (((a) < 0) == ((b) < 0)) -#ifndef INT64_MAX -#define INT64_MAX INT64CONST(0x7FFFFFFFFFFFFFFF) -#endif - -#ifndef INT64_MIN -#define INT64_MIN (-INT64CONST(0x7FFFFFFFFFFFFFFF) - 1) -#endif - /* Set at postmaster start */ TimestampTz PgStartTime; @@ -3417,7 +3409,7 @@ interval_mul(PG_FUNCTION_ARGS) result->day += (int32) month_remainder_days; #ifdef HAVE_INT64_TIMESTAMP result_double = rint(span->time * factor + sec_remainder * USECS_PER_SEC); - if (result_double > INT64_MAX || result_double < INT64_MIN) + if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("interval out of range"))); diff --git a/src/include/c.h b/src/include/c.h index cb369692e6a7e5d076b05ff9dbc4781071fd7bb5..3071404bcba38999730f32bf71cf3a5be726b7e7 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -879,6 +879,31 @@ typedef NameData *Name; #define STATIC_IF_INLINE #endif /* PG_USE_INLINE */ +/* + * Macros for range-checking float values before converting to integer. + * We must be careful here that the boundary values are expressed exactly + * in the float domain. PG_INTnn_MIN is an exact power of 2, so it will + * be represented exactly; but PG_INTnn_MAX isn't, and might get rounded + * off, so avoid using that. + * The input must be rounded to an integer beforehand, typically with rint(), + * else we might draw the wrong conclusion about close-to-the-limit values. + * These macros will do the right thing for Inf, but not necessarily for NaN, + * so check isnan(num) first if that's a possibility. + */ +#define FLOAT4_FITS_IN_INT16(num) \ + ((num) >= (float4) PG_INT16_MIN && (num) < -((float4) PG_INT16_MIN)) +#define FLOAT4_FITS_IN_INT32(num) \ + ((num) >= (float4) PG_INT32_MIN && (num) < -((float4) PG_INT32_MIN)) +#define FLOAT4_FITS_IN_INT64(num) \ + ((num) >= (float4) PG_INT64_MIN && (num) < -((float4) PG_INT64_MIN)) +#define FLOAT8_FITS_IN_INT16(num) \ + ((num) >= (float8) PG_INT16_MIN && (num) < -((float8) PG_INT16_MIN)) +#define FLOAT8_FITS_IN_INT32(num) \ + ((num) >= (float8) PG_INT32_MIN && (num) < -((float8) PG_INT32_MIN)) +#define FLOAT8_FITS_IN_INT64(num) \ + ((num) >= (float8) PG_INT64_MIN && (num) < -((float8) PG_INT64_MIN)) + + /* ---------------------------------------------------------------- * Section 8: random stuff * ---------------------------------------------------------------- diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 4a7afd953314c3e3ef04f3634c35a266b61e08c7..2d9259f4b9225dc955a688632977049b9e9f30a7 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -232,6 +232,9 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'); ERROR: interval out of range LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'... ^ +-- Test edge-case overflow detection in interval multiplication +select extract(epoch from '256 microseconds'::interval * (2^55)::float8); +ERROR: interval out of range SELECT r1.*, r2.* FROM INTERVAL_TBL_OF r1, INTERVAL_TBL_OF r2 WHERE r1.f1 > r2.f1 diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 3d1725632dd309ecf0f44dcbeb143edfcf1e0e5d..9922b1aeaf6890c56606e205c63fa0a198f97f8b 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -73,6 +73,9 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days'); INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years'); INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'); +-- Test edge-case overflow detection in interval multiplication +select extract(epoch from '256 microseconds'::interval * (2^55)::float8); + SELECT r1.*, r2.* FROM INTERVAL_TBL_OF r1, INTERVAL_TBL_OF r2 WHERE r1.f1 > r2.f1