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.

This ought to be back-patched, but I'm going to see what the buildfarm
reports about the regression tests first.

Per discussion with Xi Wang, though this is different from the patch he
submitted.
This commit is contained in:
Tom Lane 2012-11-19 12:24:25 -05:00
parent 644a0a6379
commit 1f7cb5c309
9 changed files with 233 additions and 83 deletions

View File

@ -681,18 +681,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;
/*
@ -709,7 +697,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")));
@ -732,30 +721,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);
}
@ -877,18 +863,27 @@ int2div(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
/*
* 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)
{
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;
/*
* 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.
*/
if (arg2 == -1 && arg1 < 0 && result <= 0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
PG_RETURN_INT16(result);
}
@ -1065,18 +1060,27 @@ int42div(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
/*
* 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)
{
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);
}

View File

@ -574,7 +574,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")));
@ -598,18 +599,27 @@ int8div(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
/*
* 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)
{
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;
/*
* 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.
*/
if (arg2 == -1 && arg1 < 0 && result <= 0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
PG_RETURN_INT64(result);
}
@ -838,18 +848,27 @@ int84div(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
/*
* 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)
{
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;
/*
* 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.
*/
if (arg2 == -1 && arg1 < 0 && result <= 0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
PG_RETURN_INT64(result);
}
@ -1026,18 +1045,27 @@ int82div(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
/*
* 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)
{
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;
/*
* 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.
*/
if (arg2 == -1 && arg1 < 0 && result <= 0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
PG_RETURN_INT64(result);
}

View File

@ -255,3 +255,14 @@ SELECT ((-1::int2<<15)+1::int2)::text;
-32767
(1 row)
-- 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)

View File

@ -342,3 +342,24 @@ SELECT ((-1::int4<<31)+1)::text;
-2147483647
(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)

View File

@ -815,3 +815,34 @@ SELECT ((-1::int8<<63)+1)::text;
-9223372036854775807
(1 row)
-- 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)

View File

@ -815,3 +815,34 @@ SELECT ((-1::int8<<63)+1)::text;
-9223372036854775807
(1 row)
-- 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)

View File

@ -87,3 +87,8 @@ SELECT '' AS five, i.f1, i.f1 / int4 '2' AS x FROM INT2_TBL i;
-- corner cases
SELECT (-1::int2<<15)::text;
SELECT ((-1::int2<<15)+1::int2)::text;
-- check sane handling of INT16_MIN overflow cases
SELECT (-32768)::int2 * (-1)::int2;
SELECT (-32768)::int2 / (-1)::int2;
SELECT (-32768)::int2 % (-1)::int2;

View File

@ -127,3 +127,11 @@ SELECT (2 + 2) / 2 AS two;
-- corner case
SELECT (-1::int4<<31)::text;
SELECT ((-1::int4<<31)+1)::text;
-- 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;

View File

@ -194,3 +194,14 @@ SELECT * FROM generate_series('+4567890123456789'::int8, '+4567890123456799'::in
-- corner case
SELECT (-1::int8<<63)::text;
SELECT ((-1::int8<<63)+1)::text;
-- 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;