aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Bossart <nathan@postgresql.org>2024-07-19 11:52:32 -0500
committerNathan Bossart <nathan@postgresql.org>2024-07-19 11:52:32 -0500
commite8dfe0430fad922948f56889a84af41da532c853 (patch)
tree525723f2e5af399d0732e1238a66d1989cf14197
parentd42f60ccf07d89c12645efa8a41c57320d01c184 (diff)
downloadpostgresql-e8dfe0430fad922948f56889a84af41da532c853.tar.gz
postgresql-e8dfe0430fad922948f56889a84af41da532c853.zip
Add overflow checks to money type.
None of the arithmetic functions for the the money type handle overflow. This commit introduces several helper functions with overflow checking and makes use of them in the money type's arithmetic functions. Fixes bug #18240. Reported-by: Alexander Lakhin Author: Joseph Koshakow Discussion: https://postgr.es/m/18240-c5da758d7dc1ecf0%40postgresql.org Discussion: https://postgr.es/m/CAAvxfHdBPOyEGS7s%2Bxf4iaW0-cgiq25jpYdWBqQqvLtLe_t6tw%40mail.gmail.com Backpatch-through: 12
-rw-r--r--src/backend/utils/adt/cash.c174
-rw-r--r--src/test/regress/expected/money.out19
-rw-r--r--src/test/regress/sql/money.sql11
3 files changed, 124 insertions, 80 deletions
diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c
index d093ce80386..387bef50d62 100644
--- a/src/backend/utils/adt/cash.c
+++ b/src/backend/utils/adt/cash.c
@@ -26,6 +26,7 @@
#include "libpq/pqformat.h"
#include "utils/builtins.h"
#include "utils/cash.h"
+#include "utils/float.h"
#include "utils/int8.h"
#include "utils/numeric.h"
#include "utils/pg_locale.h"
@@ -87,6 +88,82 @@ num_word(Cash value)
return buf;
} /* num_word() */
+static inline Cash
+cash_pl_cash(Cash c1, Cash c2)
+{
+ Cash res;
+
+ if (unlikely(pg_add_s64_overflow(c1, c2, &res)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("money out of range")));
+
+ return res;
+}
+
+static inline Cash
+cash_mi_cash(Cash c1, Cash c2)
+{
+ Cash res;
+
+ if (unlikely(pg_sub_s64_overflow(c1, c2, &res)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("money out of range")));
+
+ return res;
+}
+
+static inline Cash
+cash_mul_float8(Cash c, float8 f)
+{
+ float8 res = rint(float8_mul((float8) c, f));
+
+ if (unlikely(isnan(res) || !FLOAT8_FITS_IN_INT64(res)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("money out of range")));
+
+ return (Cash) res;
+}
+
+static inline Cash
+cash_div_float8(Cash c, float8 f)
+{
+ float8 res = rint(float8_div((float8) c, f));
+
+ if (unlikely(isnan(res) || !FLOAT8_FITS_IN_INT64(res)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("money out of range")));
+
+ return (Cash) res;
+}
+
+static inline Cash
+cash_mul_int64(Cash c, int64 i)
+{
+ Cash res;
+
+ if (unlikely(pg_mul_s64_overflow(c, i, &res)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("money out of range")));
+
+ return res;
+}
+
+static inline Cash
+cash_div_int64(Cash c, int64 i)
+{
+ if (unlikely(i == 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero")));
+
+ return c / i;
+}
+
/* cash_in()
* Convert a string to a cash data type.
* Format is [$]###[,]###[.##]
@@ -612,11 +689,8 @@ cash_pl(PG_FUNCTION_ARGS)
{
Cash c1 = PG_GETARG_CASH(0);
Cash c2 = PG_GETARG_CASH(1);
- Cash result;
-
- result = c1 + c2;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_pl_cash(c1, c2));
}
@@ -628,11 +702,8 @@ cash_mi(PG_FUNCTION_ARGS)
{
Cash c1 = PG_GETARG_CASH(0);
Cash c2 = PG_GETARG_CASH(1);
- Cash result;
-
- result = c1 - c2;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mi_cash(c1, c2));
}
@@ -664,10 +735,8 @@ cash_mul_flt8(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
float8 f = PG_GETARG_FLOAT8(1);
- Cash result;
- result = rint(c * f);
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_float8(c, f));
}
@@ -679,10 +748,8 @@ flt8_mul_cash(PG_FUNCTION_ARGS)
{
float8 f = PG_GETARG_FLOAT8(0);
Cash c = PG_GETARG_CASH(1);
- Cash result;
- result = rint(f * c);
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_float8(c, f));
}
@@ -694,15 +761,8 @@ cash_div_flt8(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
float8 f = PG_GETARG_FLOAT8(1);
- Cash result;
- if (f == 0.0)
- ereport(ERROR,
- (errcode(ERRCODE_DIVISION_BY_ZERO),
- errmsg("division by zero")));
-
- result = rint(c / f);
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_div_float8(c, f));
}
@@ -714,10 +774,8 @@ cash_mul_flt4(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
float4 f = PG_GETARG_FLOAT4(1);
- Cash result;
- result = rint(c * (float8) f);
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_float8(c, (float8) f));
}
@@ -729,10 +787,8 @@ flt4_mul_cash(PG_FUNCTION_ARGS)
{
float4 f = PG_GETARG_FLOAT4(0);
Cash c = PG_GETARG_CASH(1);
- Cash result;
- result = rint((float8) f * c);
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_float8(c, (float8) f));
}
@@ -745,15 +801,8 @@ cash_div_flt4(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
float4 f = PG_GETARG_FLOAT4(1);
- Cash result;
-
- if (f == 0.0)
- ereport(ERROR,
- (errcode(ERRCODE_DIVISION_BY_ZERO),
- errmsg("division by zero")));
- result = rint(c / (float8) f);
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_div_float8(c, (float8) f));
}
@@ -765,10 +814,8 @@ cash_mul_int8(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
int64 i = PG_GETARG_INT64(1);
- Cash result;
- result = c * i;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_int64(c, i));
}
@@ -780,10 +827,8 @@ int8_mul_cash(PG_FUNCTION_ARGS)
{
int64 i = PG_GETARG_INT64(0);
Cash c = PG_GETARG_CASH(1);
- Cash result;
- result = i * c;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_int64(c, i));
}
/* cash_div_int8()
@@ -794,16 +839,8 @@ cash_div_int8(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
int64 i = PG_GETARG_INT64(1);
- Cash result;
-
- if (i == 0)
- ereport(ERROR,
- (errcode(ERRCODE_DIVISION_BY_ZERO),
- errmsg("division by zero")));
- result = c / i;
-
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_div_int64(c, i));
}
@@ -815,10 +852,8 @@ cash_mul_int4(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
int32 i = PG_GETARG_INT32(1);
- Cash result;
- result = c * i;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_int64(c, (int64) i));
}
@@ -830,10 +865,8 @@ int4_mul_cash(PG_FUNCTION_ARGS)
{
int32 i = PG_GETARG_INT32(0);
Cash c = PG_GETARG_CASH(1);
- Cash result;
- result = i * c;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_int64(c, (int64) i));
}
@@ -846,16 +879,8 @@ cash_div_int4(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
int32 i = PG_GETARG_INT32(1);
- Cash result;
-
- if (i == 0)
- ereport(ERROR,
- (errcode(ERRCODE_DIVISION_BY_ZERO),
- errmsg("division by zero")));
-
- result = c / i;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_div_int64(c, (int64) i));
}
@@ -867,10 +892,8 @@ cash_mul_int2(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
int16 s = PG_GETARG_INT16(1);
- Cash result;
- result = c * s;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_int64(c, (int64) s));
}
/* int2_mul_cash()
@@ -881,10 +904,8 @@ int2_mul_cash(PG_FUNCTION_ARGS)
{
int16 s = PG_GETARG_INT16(0);
Cash c = PG_GETARG_CASH(1);
- Cash result;
- result = s * c;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_mul_int64(c, (int64) s));
}
/* cash_div_int2()
@@ -896,15 +917,8 @@ cash_div_int2(PG_FUNCTION_ARGS)
{
Cash c = PG_GETARG_CASH(0);
int16 s = PG_GETARG_INT16(1);
- Cash result;
- if (s == 0)
- ereport(ERROR,
- (errcode(ERRCODE_DIVISION_BY_ZERO),
- errmsg("division by zero")));
-
- result = c / s;
- PG_RETURN_CASH(result);
+ PG_RETURN_CASH(cash_div_int64(c, (int64) s));
}
/* cashlarger()
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index fc71a72fed3..90140f66767 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -503,3 +503,22 @@ SELECT '-92233720368547758.08'::money::numeric;
-92233720368547758.08
(1 row)
+-- overflow checks
+SELECT '92233720368547758.07'::money + '0.01'::money;
+ERROR: money out of range
+SELECT '-92233720368547758.08'::money - '0.01'::money;
+ERROR: money out of range
+SELECT '92233720368547758.07'::money * 2::float8;
+ERROR: money out of range
+SELECT '-1'::money / 1.175494e-38::float4;
+ERROR: money out of range
+SELECT '92233720368547758.07'::money * 2::int4;
+ERROR: money out of range
+SELECT '1'::money / 0::int2;
+ERROR: division by zero
+SELECT '42'::money * 'inf'::float8;
+ERROR: money out of range
+SELECT '42'::money * '-inf'::float8;
+ERROR: money out of range
+SELECT '42'::money * 'nan'::float4;
+ERROR: money out of range
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index 5e746286c90..1eb471d1e4e 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -129,3 +129,14 @@ SELECT '12345678901234567'::money::numeric;
SELECT '-12345678901234567'::money::numeric;
SELECT '92233720368547758.07'::money::numeric;
SELECT '-92233720368547758.08'::money::numeric;
+
+-- overflow checks
+SELECT '92233720368547758.07'::money + '0.01'::money;
+SELECT '-92233720368547758.08'::money - '0.01'::money;
+SELECT '92233720368547758.07'::money * 2::float8;
+SELECT '-1'::money / 1.175494e-38::float4;
+SELECT '92233720368547758.07'::money * 2::int4;
+SELECT '1'::money / 0::int2;
+SELECT '42'::money * 'inf'::float8;
+SELECT '42'::money * '-inf'::float8;
+SELECT '42'::money * 'nan'::float4;