Fix integer-overflow edge case detection in interval_mul and pgbench.
authorTom Lane <[email protected]>
Thu, 7 Nov 2019 16:22:52 +0000 (11:22 -0500)
committerTom Lane <[email protected]>
Thu, 7 Nov 2019 16:23:06 +0000 (11:23 -0500)
This patch adopts the overflow check logic introduced by commit cbdb8b4c0
into two more places.  interval_mul() failed to notice if it computed a
new microseconds value that was one more than INT64_MAX, and pgbench's
double-to-int64 logic had the same sorts of edge-case problems that
cbdb8b4c0 fixed in the core code.

To make this easier to get right in future, put the guts of the checks
into new macros in c.h, and add commentary about how to use the macros
correctly.

Back-patch to all supported branches, as we did with the previous fix.

Yuya Watari

Discussion: https://postgr.es/m/CAJ2pMkbkkFw2hb9Qb1Zj8d06EhWAQXFLy73St4qWv6aX=vqnjw@mail.gmail.com

src/backend/utils/adt/float.c
src/backend/utils/adt/int8.c
src/backend/utils/adt/timestamp.c
src/include/c.h
src/test/regress/expected/interval.out
src/test/regress/sql/interval.sql

index 00ae55106dd9cfddeec27db9bb08be719bde4d07..a40c4d2197b188bed51d9f3f3aefcb55829ddce4 100644 (file)
@@ -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")));
index ade9b81386df67a5250d1914ba6a7ff959404e8c..2788853c1bf7d65d82b2a732c8a2bb22161a3ac7 100644 (file)
@@ -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")));
index d24ab8560c8fc5d6d9c948ca7514b05a1413672b..13b24af80ced7d63d6c3081ca8bed01a660f4970 100644 (file)
 
 #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")));
index cb369692e6a7e5d076b05ff9dbc4781071fd7bb5..3071404bcba38999730f32bf71cf3a5be726b7e7 100644 (file)
@@ -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
  * ----------------------------------------------------------------
index 4a7afd953314c3e3ef04f3634c35a266b61e08c7..2d9259f4b9225dc955a688632977049b9e9f30a7 100644 (file)
@@ -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
index 3d1725632dd309ecf0f44dcbeb143edfcf1e0e5d..9922b1aeaf6890c56606e205c63fa0a198f97f8b 100644 (file)
@@ -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