Commits

Tom Lane  committed 1f7cb5c

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.

  • Participants
  • Parent commits 644a0a6

Comments (0)

Files changed (9)

File src/backend/utils/adt/int.c

 	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;
 
 	/*
 	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")));
 		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);
 }
 
 		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);
 }
 
 		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);
 }
 

File src/backend/utils/adt/int8.c

 	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")));
 		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);
 }
 
 		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);
 }
 
 		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);
 }
 

File src/test/regress/expected/int2.out

  -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)
+

File src/test/regress/expected/int4.out

  -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)
+

File src/test/regress/expected/int8-exp-three-digits.out

  -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)
+

File src/test/regress/expected/int8.out

  -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)
+

File src/test/regress/sql/int2.sql

 -- 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;

File src/test/regress/sql/int4.sql

 -- 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;

File src/test/regress/sql/int8.sql

 -- 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;