From eb865dbb1b0dd2f78c5f516ce2b003d183207d2c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 19 Nov 2012 21:21:48 -0500 Subject: [PATCH] Improve handling of INT_MIN / -1 and related cases. Some platforms throw an exception for this division, rather than returning a necessarily-overflowed result. Since we were testing for overflow after the fact, an exception isn't nice. We can avoid the problem by treating division by -1 as negation. Add some regression tests so that we'll find out if any compilers try to optimize away the overflow check conditions. Back-patch of commit 1f7cb5c30983752ff8de833de30afcaee63536d0. Per discussion with Xi Wang, though this is different from the patch he submitted. --- src/backend/utils/adt/int.c | 108 +++++++++--------- src/backend/utils/adt/int8.c | 90 ++++++++++----- src/test/regress/expected/int2.out | 11 ++ src/test/regress/expected/int4.out | 21 ++++ .../expected/int8-exp-three-digits.out | 31 +++++ src/test/regress/expected/int8.out | 31 +++++ src/test/regress/sql/int2.sql | 4 + src/test/regress/sql/int4.sql | 8 ++ src/test/regress/sql/int8.sql | 11 ++ 9 files changed, 232 insertions(+), 83 deletions(-) diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index 435a135cc1..392670f114 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -680,18 +680,6 @@ int4mul(PG_FUNCTION_ARGS) int32 arg2 = PG_GETARG_INT32(1); int32 result; -#ifdef WIN32 - - /* - * Win32 doesn't throw a catchable exception for SELECT -2147483648 * - * (-1); -- INT_MIN - */ - if (arg2 == -1 && arg1 == INT_MIN) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); -#endif - result = arg1 * arg2; /* @@ -708,7 +696,8 @@ int4mul(PG_FUNCTION_ARGS) if (!(arg1 >= (int32) SHRT_MIN && arg1 <= (int32) SHRT_MAX && arg2 >= (int32) SHRT_MIN && arg2 <= (int32) SHRT_MAX) && arg2 != 0 && - (result / arg2 != arg1 || (arg2 == -1 && arg1 < 0 && result < 0))) + ((arg2 == -1 && arg1 < 0 && result < 0) || + result / arg2 != arg1)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("integer out of range"))); @@ -731,30 +720,27 @@ int4div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } -#ifdef WIN32 - /* - * Win32 doesn't throw a catchable exception for SELECT -2147483648 / - * (-1); -- INT_MIN + * INT_MIN / -1 is problematic, since the result can't be represented on a + * two's-complement machine. Some machines produce INT_MIN, some produce + * zero, some throw an exception. We can dodge the problem by recognizing + * that division by -1 is the same as negation. */ - if (arg2 == -1 && arg1 == INT_MIN) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); -#endif + if (arg2 == -1) + { + result = -arg1; + /* overflow check (needed for INT_MIN) */ + if (arg1 != 0 && SAMESIGN(result, arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); + } + + /* No overflow is possible */ result = arg1 / arg2; - /* - * Overflow check. The only possible overflow case is for arg1 = INT_MIN, - * arg2 = -1, where the correct result is -INT_MIN, which can't be - * represented on a two's-complement machine. Most machines produce - * INT_MIN but it seems some produce zero. - */ - if (arg2 == -1 && arg1 < 0 && result <= 0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); PG_RETURN_INT32(result); } @@ -876,18 +862,27 @@ int2div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - result = arg1 / arg2; - /* - * Overflow check. The only possible overflow case is for arg1 = - * SHRT_MIN, arg2 = -1, where the correct result is -SHRT_MIN, which can't - * be represented on a two's-complement machine. Most machines produce - * SHRT_MIN but it seems some produce zero. + * SHRT_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce SHRT_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. */ - if (arg2 == -1 && arg1 < 0 && result <= 0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("smallint out of range"))); + if (arg2 == -1) + { + result = -arg1; + /* overflow check (needed for SHRT_MIN) */ + if (arg1 != 0 && SAMESIGN(result, arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + PG_RETURN_INT16(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + PG_RETURN_INT16(result); } @@ -1064,18 +1059,27 @@ int42div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - result = arg1 / arg2; - /* - * Overflow check. The only possible overflow case is for arg1 = INT_MIN, - * arg2 = -1, where the correct result is -INT_MIN, which can't be - * represented on a two's-complement machine. Most machines produce - * INT_MIN but it seems some produce zero. + * INT_MIN / -1 is problematic, since the result can't be represented on a + * two's-complement machine. Some machines produce INT_MIN, some produce + * zero, some throw an exception. We can dodge the problem by recognizing + * that division by -1 is the same as negation. */ - if (arg2 == -1 && arg1 < 0 && result <= 0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("integer out of range"))); + if (arg2 == -1) + { + result = -arg1; + /* overflow check (needed for INT_MIN) */ + if (arg1 != 0 && SAMESIGN(result, arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + PG_RETURN_INT32(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + PG_RETURN_INT32(result); } diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 6520d01bdb..e6daf5884c 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -576,7 +576,8 @@ int8mul(PG_FUNCTION_ARGS) if (arg1 != (int64) ((int32) arg1) || arg2 != (int64) ((int32) arg2)) { if (arg2 != 0 && - (result / arg2 != arg1 || (arg2 == -1 && arg1 < 0 && result < 0))) + ((arg2 == -1 && arg1 < 0 && result < 0) || + result / arg2 != arg1)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range"))); @@ -600,18 +601,27 @@ int8div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - result = arg1 / arg2; - /* - * Overflow check. The only possible overflow case is for arg1 = - * INT64_MIN, arg2 = -1, where the correct result is -INT64_MIN, which - * can't be represented on a two's-complement machine. Most machines - * produce INT64_MIN but it seems some produce zero. + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. */ - if (arg2 == -1 && arg1 < 0 && result <= 0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("bigint out of range"))); + if (arg2 == -1) + { + result = -arg1; + /* overflow check (needed for INT64_MIN) */ + if (arg1 != 0 && SAMESIGN(result, arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + PG_RETURN_INT64(result); } @@ -840,18 +850,27 @@ int84div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - result = arg1 / arg2; - /* - * Overflow check. The only possible overflow case is for arg1 = - * INT64_MIN, arg2 = -1, where the correct result is -INT64_MIN, which - * can't be represented on a two's-complement machine. Most machines - * produce INT64_MIN but it seems some produce zero. + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. */ - if (arg2 == -1 && arg1 < 0 && result <= 0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("bigint out of range"))); + if (arg2 == -1) + { + result = -arg1; + /* overflow check (needed for INT64_MIN) */ + if (arg1 != 0 && SAMESIGN(result, arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + PG_RETURN_INT64(result); } @@ -1028,18 +1047,27 @@ int82div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - result = arg1 / arg2; - /* - * Overflow check. The only possible overflow case is for arg1 = - * INT64_MIN, arg2 = -1, where the correct result is -INT64_MIN, which - * can't be represented on a two's-complement machine. Most machines - * produce INT64_MIN but it seems some produce zero. + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. */ - if (arg2 == -1 && arg1 < 0 && result <= 0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("bigint out of range"))); + if (arg2 == -1) + { + result = -arg1; + /* overflow check (needed for INT64_MIN) */ + if (arg1 != 0 && SAMESIGN(result, arg1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + PG_RETURN_INT64(result); + } + + /* No overflow is possible */ + + result = arg1 / arg2; + PG_RETURN_INT64(result); } diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out index 61ac956cb7..f4af51f46e 100644 --- a/src/test/regress/expected/int2.out +++ b/src/test/regress/expected/int2.out @@ -244,3 +244,14 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i; | -32767 | -16383 (5 rows) +-- check sane handling of INT16_MIN overflow cases +SELECT (-32768)::int2 * (-1)::int2; +ERROR: smallint out of range +SELECT (-32768)::int2 / (-1)::int2; +ERROR: smallint out of range +SELECT (-32768)::int2 % (-1)::int2; + ?column? +---------- + 0 +(1 row) + diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out index a21bbda2c9..3cfd1d0cd6 100644 --- a/src/test/regress/expected/int4.out +++ b/src/test/regress/expected/int4.out @@ -331,3 +331,24 @@ SELECT (2 + 2) / 2 AS two; 2 (1 row) +-- check sane handling of INT_MIN overflow cases +SELECT (-2147483648)::int4 * (-1)::int4; +ERROR: integer out of range +SELECT (-2147483648)::int4 / (-1)::int4; +ERROR: integer out of range +SELECT (-2147483648)::int4 % (-1)::int4; + ?column? +---------- + 0 +(1 row) + +SELECT (-2147483648)::int4 * (-1)::int2; +ERROR: integer out of range +SELECT (-2147483648)::int4 / (-1)::int2; +ERROR: integer out of range +SELECT (-2147483648)::int4 % (-1)::int2; + ?column? +---------- + 0 +(1 row) + diff --git a/src/test/regress/expected/int8-exp-three-digits.out b/src/test/regress/expected/int8-exp-three-digits.out index 62d975bce1..da970f55e8 100644 --- a/src/test/regress/expected/int8-exp-three-digits.out +++ b/src/test/regress/expected/int8-exp-three-digits.out @@ -802,3 +802,34 @@ SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::in 4567890123456799 (6 rows) +-- check sane handling of INT64_MIN overflow cases +SELECT (-9223372036854775808)::int8 * (-1)::int8; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int8; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int8; + ?column? +---------- + 0 +(1 row) + +SELECT (-9223372036854775808)::int8 * (-1)::int4; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int4; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int4; + ?column? +---------- + 0 +(1 row) + +SELECT (-9223372036854775808)::int8 * (-1)::int2; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int2; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int2; + ?column? +---------- + 0 +(1 row) + diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out index c8e2dad683..7b735560a8 100644 --- a/src/test/regress/expected/int8.out +++ b/src/test/regress/expected/int8.out @@ -802,3 +802,34 @@ SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::in 4567890123456799 (6 rows) +-- check sane handling of INT64_MIN overflow cases +SELECT (-9223372036854775808)::int8 * (-1)::int8; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int8; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int8; + ?column? +---------- + 0 +(1 row) + +SELECT (-9223372036854775808)::int8 * (-1)::int4; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int4; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int4; + ?column? +---------- + 0 +(1 row) + +SELECT (-9223372036854775808)::int8 * (-1)::int2; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 / (-1)::int2; +ERROR: bigint out of range +SELECT (-9223372036854775808)::int8 % (-1)::int2; + ?column? +---------- + 0 +(1 row) + diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql index 65c89e4abd..b185f16c0a 100644 --- a/src/test/regress/sql/int2.sql +++ b/src/test/regress/sql/int2.sql @@ -86,3 +86,7 @@ SELECT '' AS five, i.f1, i.f1 / int2 '2' AS x FROM INT2_TBL i; SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i; +-- check sane handling of INT16_MIN overflow cases +SELECT (-32768)::int2 * (-1)::int2; +SELECT (-32768)::int2 / (-1)::int2; +SELECT (-32768)::int2 % (-1)::int2; diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql index 5212c68795..9c24188dc0 100644 --- a/src/test/regress/sql/int4.sql +++ b/src/test/regress/sql/int4.sql @@ -125,3 +125,11 @@ SELECT 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 AS ten; SELECT 2 + 2 / 2 AS three; SELECT (2 + 2) / 2 AS two; + +-- check sane handling of INT_MIN overflow cases +SELECT (-2147483648)::int4 * (-1)::int4; +SELECT (-2147483648)::int4 / (-1)::int4; +SELECT (-2147483648)::int4 % (-1)::int4; +SELECT (-2147483648)::int4 * (-1)::int2; +SELECT (-2147483648)::int4 / (-1)::int2; +SELECT (-2147483648)::int4 % (-1)::int2; diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql index 648563c374..9e951a52bd 100644 --- a/src/test/regress/sql/int8.sql +++ b/src/test/regress/sql/int8.sql @@ -190,3 +190,14 @@ SELECT q1, q1 << 2 AS "shl", q1 >> 3 AS "shr" FROM INT8_TBL; SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8); SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8, 0); SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::int8, 2); + +-- check sane handling of INT64_MIN overflow cases +SELECT (-9223372036854775808)::int8 * (-1)::int8; +SELECT (-9223372036854775808)::int8 / (-1)::int8; +SELECT (-9223372036854775808)::int8 % (-1)::int8; +SELECT (-9223372036854775808)::int8 * (-1)::int4; +SELECT (-9223372036854775808)::int8 / (-1)::int4; +SELECT (-9223372036854775808)::int8 % (-1)::int4; +SELECT (-9223372036854775808)::int8 * (-1)::int2; +SELECT (-9223372036854775808)::int8 / (-1)::int2; +SELECT (-9223372036854775808)::int8 % (-1)::int2; -- 2.40.0