aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Eisentraut <peter_e@gmx.net>2016-09-08 12:00:00 -0400
committerPeter Eisentraut <peter_e@gmx.net>2016-09-14 12:00:00 -0500
commit656df624c0d7b50e1714f2a3a14e143e63799a80 (patch)
treedf553488b904b9b63f8e396c7477c0931d1bcdd3
parent0dac5b5174bde3d6fb4b444a2aa4ca1f0091e258 (diff)
downloadpostgresql-656df624c0d7b50e1714f2a3a14e143e63799a80.tar.gz
postgresql-656df624c0d7b50e1714f2a3a14e143e63799a80.zip
Add overflow checks to money type input function
The money type input function did not have any overflow checks at all. There were some regression tests that purported to check for overflow, but they actually checked for the overflow behavior of the int8 type before casting to money. Remove those unnecessary checks and add some that actually check the money input function. Reviewed-by: Fabien COELHO <coelho@cri.ensmp.fr>
-rw-r--r--src/backend/utils/adt/cash.c53
-rw-r--r--src/test/regress/expected/money.out98
-rw-r--r--src/test/regress/sql/money.sql30
3 files changed, 165 insertions, 16 deletions
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index b336185df7e..a146b0a0bc8 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -189,13 +189,30 @@ cash_in(PG_FUNCTION_ARGS)
printf("cashin- string is '%s'\n", s);
#endif
+ /*
+ * We accumulate the absolute amount in "value" and then apply the sign at
+ * the end. (The sign can appear before or after the digits, so it would
+ * be more complicated to do otherwise.) Because of the larger range of
+ * negative signed integers, we build "value" in the negative and then
+ * flip the sign at the end, catching most-negative-number overflow if
+ * necessary.
+ */
+
for (; *s; s++)
{
/* we look for digits as long as we have found less */
/* than the required number of decimal places */
if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
{
- value = (value * 10) + (*s - '0');
+ Cash newvalue = (value * 10) - (*s - '0');
+
+ if (newvalue / 10 != value)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type money",
+ str)));
+
+ value = newvalue;
if (seen_dot)
dec++;
@@ -214,11 +231,27 @@ cash_in(PG_FUNCTION_ARGS)
/* round off if there's another digit */
if (isdigit((unsigned char) *s) && *s >= '5')
- value++;
+ value--; /* remember we build the value in the negative */
+
+ if (value > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type money",
+ str)));
/* adjust for less than required decimal places */
for (; dec < fpoint; dec++)
- value *= 10;
+ {
+ Cash newvalue = value * 10;
+
+ if (newvalue / 10 != value)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type money",
+ str)));
+
+ value = newvalue;
+ }
/*
* should only be trailing digits followed by whitespace, right paren,
@@ -247,7 +280,19 @@ cash_in(PG_FUNCTION_ARGS)
str)));
}
- result = value * sgn;
+ /* If the value is supposed to be positive, flip the sign, but check for
+ * the most negative number. */
+ if (sgn > 0)
+ {
+ result = -value;
+ if (result < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type money",
+ str)));
+ }
+ else
+ result = value;
#ifdef CASHDEBUG
printf("cashin- result is " INT64_FORMAT "\n", result);
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index 538235c4cc2..5695f875006 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -185,6 +185,96 @@ SELECT * FROM money_data;
$123.46
(1 row)
+-- input checks
+SELECT '1234567890'::money;
+ money
+-------------------
+ $1,234,567,890.00
+(1 row)
+
+SELECT '12345678901234567'::money;
+ money
+----------------------------
+ $12,345,678,901,234,567.00
+(1 row)
+
+SELECT '123456789012345678'::money;
+ERROR: value "123456789012345678" is out of range for type money
+LINE 1: SELECT '123456789012345678'::money;
+ ^
+SELECT '9223372036854775807'::money;
+ERROR: value "9223372036854775807" is out of range for type money
+LINE 1: SELECT '9223372036854775807'::money;
+ ^
+SELECT '-12345'::money;
+ money
+-------------
+ -$12,345.00
+(1 row)
+
+SELECT '-1234567890'::money;
+ money
+--------------------
+ -$1,234,567,890.00
+(1 row)
+
+SELECT '-12345678901234567'::money;
+ money
+-----------------------------
+ -$12,345,678,901,234,567.00
+(1 row)
+
+SELECT '-123456789012345678'::money;
+ERROR: value "-123456789012345678" is out of range for type money
+LINE 1: SELECT '-123456789012345678'::money;
+ ^
+SELECT '-9223372036854775808'::money;
+ERROR: value "-9223372036854775808" is out of range for type money
+LINE 1: SELECT '-9223372036854775808'::money;
+ ^
+-- special characters
+SELECT '(1)'::money;
+ money
+--------
+ -$1.00
+(1 row)
+
+SELECT '($123,456.78)'::money;
+ money
+--------------
+ -$123,456.78
+(1 row)
+
+-- documented minimums and maximums
+SELECT '-92233720368547758.08'::money;
+ money
+-----------------------------
+ -$92,233,720,368,547,758.08
+(1 row)
+
+SELECT '92233720368547758.07'::money;
+ money
+----------------------------
+ $92,233,720,368,547,758.07
+(1 row)
+
+SELECT '-92233720368547758.09'::money;
+ERROR: value "-92233720368547758.09" is out of range for type money
+LINE 1: SELECT '-92233720368547758.09'::money;
+ ^
+SELECT '92233720368547758.08'::money;
+ERROR: value "92233720368547758.08" is out of range for type money
+LINE 1: SELECT '92233720368547758.08'::money;
+ ^
+-- rounding
+SELECT '-92233720368547758.085'::money;
+ERROR: value "-92233720368547758.085" is out of range for type money
+LINE 1: SELECT '-92233720368547758.085'::money;
+ ^
+SELECT '92233720368547758.075'::money;
+ERROR: value "92233720368547758.075" is out of range for type money
+LINE 1: SELECT '92233720368547758.075'::money;
+ ^
-- Cast int4/int8 to money
SELECT 1234567890::money;
money
@@ -198,10 +288,6 @@ SELECT 12345678901234567::money;
$12,345,678,901,234,567.00
(1 row)
-SELECT 123456789012345678::money;
-ERROR: bigint out of range
-SELECT 9223372036854775807::money;
-ERROR: bigint out of range
SELECT (-12345)::money;
money
-------------
@@ -220,10 +306,6 @@ SELECT (-12345678901234567)::money;
-$12,345,678,901,234,567.00
(1 row)
-SELECT (-123456789012345678)::money;
-ERROR: bigint out of range
-SELECT (-9223372036854775808)::money;
-ERROR: bigint out of range
SELECT 1234567890::int4::money;
money
-------------------
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 09b9476b706..561ccb527f8 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -57,16 +57,38 @@ DELETE FROM money_data;
INSERT INTO money_data VALUES ('$123.459');
SELECT * FROM money_data;
+-- input checks
+SELECT '1234567890'::money;
+SELECT '12345678901234567'::money;
+SELECT '123456789012345678'::money;
+SELECT '9223372036854775807'::money;
+SELECT '-12345'::money;
+SELECT '-1234567890'::money;
+SELECT '-12345678901234567'::money;
+SELECT '-123456789012345678'::money;
+SELECT '-9223372036854775808'::money;
+
+-- special characters
+SELECT '(1)'::money;
+SELECT '($123,456.78)'::money;
+
+-- documented minimums and maximums
+SELECT '-92233720368547758.08'::money;
+SELECT '92233720368547758.07'::money;
+
+SELECT '-92233720368547758.09'::money;
+SELECT '92233720368547758.08'::money;
+
+-- rounding
+SELECT '-92233720368547758.085'::money;
+SELECT '92233720368547758.075'::money;
+
-- Cast int4/int8 to money
SELECT 1234567890::money;
SELECT 12345678901234567::money;
-SELECT 123456789012345678::money;
-SELECT 9223372036854775807::money;
SELECT (-12345)::money;
SELECT (-1234567890)::money;
SELECT (-12345678901234567)::money;
-SELECT (-123456789012345678)::money;
-SELECT (-9223372036854775808)::money;
SELECT 1234567890::int4::money;
SELECT 12345678901234567::int8::money;
SELECT (-1234567890)::int4::money;