aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2019-11-07 11:22:52 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2019-11-07 11:23:00 -0500
commitb49b7f94489a12d0d7d299b2871c4881fa3c5d49 (patch)
tree614593f315f64efa1b86e4df4df1f5ec5b8024cd
parentfb53c4c07fec36846b522213599438176414fec0 (diff)
downloadpostgresql-b49b7f94489a12d0d7d299b2871c4881fa3c5d49.tar.gz
postgresql-b49b7f94489a12d0d7d299b2871c4881fa3c5d49.zip
Fix integer-overflow edge case detection in interval_mul and pgbench.
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
-rw-r--r--src/backend/utils/adt/float.c44
-rw-r--r--src/backend/utils/adt/int8.c22
-rw-r--r--src/backend/utils/adt/timestamp.c2
-rw-r--r--src/bin/pgbench/pgbench.c4
-rw-r--r--src/include/c.h24
-rw-r--r--src/test/regress/expected/interval.out3
-rw-r--r--src/test/regress/sql/interval.sql3
7 files changed, 45 insertions, 57 deletions
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 52e4ee52fe3..da2e1851171 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -1362,15 +1362,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 (unlikely(num < (float8) PG_INT32_MIN ||
- num >= -((float8) PG_INT32_MIN) ||
- isnan(num)))
+ /* Range check */
+ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1394,15 +1387,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 (unlikely(num < (float8) PG_INT16_MIN ||
- num >= -((float8) PG_INT16_MIN) ||
- isnan(num)))
+ /* Range check */
+ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
@@ -1450,15 +1436,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 (unlikely(num < (float4) PG_INT32_MIN ||
- num >= -((float4) PG_INT32_MIN) ||
- isnan(num)))
+ /* Range check */
+ if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
@@ -1482,15 +1461,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 (unlikely(num < (float4) PG_INT16_MIN ||
- num >= -((float4) PG_INT16_MIN) ||
- isnan(num)))
+ /* Range check */
+ if (unlikely(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 38f3a0d969d..a2ce01cad30 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -1213,15 +1213,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 (unlikely(num < (float8) PG_INT64_MIN ||
- num >= -((float8) PG_INT64_MIN) ||
- isnan(num)))
+ /* Range check */
+ if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT64(num)))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
@@ -1255,15 +1248,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 (unlikely(num < (float4) PG_INT64_MIN ||
- num >= -((float4) PG_INT64_MIN) ||
- isnan(num)))
+ /* Range check */
+ if (unlikely(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 4dac27e8491..e2beae42733 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -3200,7 +3200,7 @@ interval_mul(PG_FUNCTION_ARGS)
/* cascade units down */
result->day += (int32) month_remainder_days;
result_double = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
- if (result_double > PG_INT64_MAX || result_double < PG_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/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 6eb912d3079..bb3e6f794cd 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1617,9 +1617,9 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
}
else if (pval->type == PGBT_DOUBLE)
{
- double dval = pval->u.dval;
+ double dval = rint(pval->u.dval);
- if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
+ if (isnan(dval) || !FLOAT8_FITS_IN_INT64(dval))
{
fprintf(stderr, "double to int overflow for %f\n", dval);
return false;
diff --git a/src/include/c.h b/src/include/c.h
index 6b5e71782b8..24699ae2919 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -983,6 +983,30 @@ extern void ExceptionalCondition(const char *conditionName,
*_start++ = 0; \
} while (0)
+/*
+ * 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 f88f34550ad..f772909e49c 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 bc5537d1b9c..eb1e84f053e 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