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
This commit is contained in:
Nathan Bossart 2024-07-19 11:52:32 -05:00
parent aa607980ae
commit 22b0ccd65d
3 changed files with 124 additions and 80 deletions

View File

@ -26,6 +26,7 @@
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/cash.h" #include "utils/cash.h"
#include "utils/float.h"
#include "utils/numeric.h" #include "utils/numeric.h"
#include "utils/pg_locale.h" #include "utils/pg_locale.h"
@ -86,6 +87,82 @@ num_word(Cash value)
return buf; return buf;
} /* num_word() */ } /* 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() /* cash_in()
* Convert a string to a cash data type. * Convert a string to a cash data type.
* Format is [$]###[,]###[.##] * Format is [$]###[,]###[.##]
@ -612,11 +689,8 @@ cash_pl(PG_FUNCTION_ARGS)
{ {
Cash c1 = PG_GETARG_CASH(0); Cash c1 = PG_GETARG_CASH(0);
Cash c2 = PG_GETARG_CASH(1); Cash c2 = PG_GETARG_CASH(1);
Cash result;
result = c1 + c2; PG_RETURN_CASH(cash_pl_cash(c1, c2));
PG_RETURN_CASH(result);
} }
@ -628,11 +702,8 @@ cash_mi(PG_FUNCTION_ARGS)
{ {
Cash c1 = PG_GETARG_CASH(0); Cash c1 = PG_GETARG_CASH(0);
Cash c2 = PG_GETARG_CASH(1); Cash c2 = PG_GETARG_CASH(1);
Cash result;
result = c1 - c2; PG_RETURN_CASH(cash_mi_cash(c1, c2));
PG_RETURN_CASH(result);
} }
@ -664,10 +735,8 @@ cash_mul_flt8(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
float8 f = PG_GETARG_FLOAT8(1); float8 f = PG_GETARG_FLOAT8(1);
Cash result;
result = rint(c * f); PG_RETURN_CASH(cash_mul_float8(c, f));
PG_RETURN_CASH(result);
} }
@ -679,10 +748,8 @@ flt8_mul_cash(PG_FUNCTION_ARGS)
{ {
float8 f = PG_GETARG_FLOAT8(0); float8 f = PG_GETARG_FLOAT8(0);
Cash c = PG_GETARG_CASH(1); Cash c = PG_GETARG_CASH(1);
Cash result;
result = rint(f * c); PG_RETURN_CASH(cash_mul_float8(c, f));
PG_RETURN_CASH(result);
} }
@ -694,15 +761,8 @@ cash_div_flt8(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
float8 f = PG_GETARG_FLOAT8(1); float8 f = PG_GETARG_FLOAT8(1);
Cash result;
if (f == 0.0) PG_RETURN_CASH(cash_div_float8(c, f));
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = rint(c / f);
PG_RETURN_CASH(result);
} }
@ -714,10 +774,8 @@ cash_mul_flt4(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
float4 f = PG_GETARG_FLOAT4(1); float4 f = PG_GETARG_FLOAT4(1);
Cash result;
result = rint(c * (float8) f); PG_RETURN_CASH(cash_mul_float8(c, (float8) f));
PG_RETURN_CASH(result);
} }
@ -729,10 +787,8 @@ flt4_mul_cash(PG_FUNCTION_ARGS)
{ {
float4 f = PG_GETARG_FLOAT4(0); float4 f = PG_GETARG_FLOAT4(0);
Cash c = PG_GETARG_CASH(1); Cash c = PG_GETARG_CASH(1);
Cash result;
result = rint((float8) f * c); PG_RETURN_CASH(cash_mul_float8(c, (float8) f));
PG_RETURN_CASH(result);
} }
@ -745,15 +801,8 @@ cash_div_flt4(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
float4 f = PG_GETARG_FLOAT4(1); float4 f = PG_GETARG_FLOAT4(1);
Cash result;
if (f == 0.0) PG_RETURN_CASH(cash_div_float8(c, (float8) f));
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = rint(c / (float8) f);
PG_RETURN_CASH(result);
} }
@ -765,10 +814,8 @@ cash_mul_int8(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
int64 i = PG_GETARG_INT64(1); int64 i = PG_GETARG_INT64(1);
Cash result;
result = c * i; PG_RETURN_CASH(cash_mul_int64(c, i));
PG_RETURN_CASH(result);
} }
@ -780,10 +827,8 @@ int8_mul_cash(PG_FUNCTION_ARGS)
{ {
int64 i = PG_GETARG_INT64(0); int64 i = PG_GETARG_INT64(0);
Cash c = PG_GETARG_CASH(1); Cash c = PG_GETARG_CASH(1);
Cash result;
result = i * c; PG_RETURN_CASH(cash_mul_int64(c, i));
PG_RETURN_CASH(result);
} }
/* cash_div_int8() /* cash_div_int8()
@ -794,16 +839,8 @@ cash_div_int8(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
int64 i = PG_GETARG_INT64(1); int64 i = PG_GETARG_INT64(1);
Cash result;
if (i == 0) PG_RETURN_CASH(cash_div_int64(c, i));
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = c / i;
PG_RETURN_CASH(result);
} }
@ -815,10 +852,8 @@ cash_mul_int4(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
int32 i = PG_GETARG_INT32(1); int32 i = PG_GETARG_INT32(1);
Cash result;
result = c * i; PG_RETURN_CASH(cash_mul_int64(c, (int64) i));
PG_RETURN_CASH(result);
} }
@ -830,10 +865,8 @@ int4_mul_cash(PG_FUNCTION_ARGS)
{ {
int32 i = PG_GETARG_INT32(0); int32 i = PG_GETARG_INT32(0);
Cash c = PG_GETARG_CASH(1); Cash c = PG_GETARG_CASH(1);
Cash result;
result = i * c; PG_RETURN_CASH(cash_mul_int64(c, (int64) i));
PG_RETURN_CASH(result);
} }
@ -846,16 +879,8 @@ cash_div_int4(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
int32 i = PG_GETARG_INT32(1); int32 i = PG_GETARG_INT32(1);
Cash result;
if (i == 0) PG_RETURN_CASH(cash_div_int64(c, (int64) i));
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = c / i;
PG_RETURN_CASH(result);
} }
@ -867,10 +892,8 @@ cash_mul_int2(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
int16 s = PG_GETARG_INT16(1); int16 s = PG_GETARG_INT16(1);
Cash result;
result = c * s; PG_RETURN_CASH(cash_mul_int64(c, (int64) s));
PG_RETURN_CASH(result);
} }
/* int2_mul_cash() /* int2_mul_cash()
@ -881,10 +904,8 @@ int2_mul_cash(PG_FUNCTION_ARGS)
{ {
int16 s = PG_GETARG_INT16(0); int16 s = PG_GETARG_INT16(0);
Cash c = PG_GETARG_CASH(1); Cash c = PG_GETARG_CASH(1);
Cash result;
result = s * c; PG_RETURN_CASH(cash_mul_int64(c, (int64) s));
PG_RETURN_CASH(result);
} }
/* cash_div_int2() /* cash_div_int2()
@ -896,15 +917,8 @@ cash_div_int2(PG_FUNCTION_ARGS)
{ {
Cash c = PG_GETARG_CASH(0); Cash c = PG_GETARG_CASH(0);
int16 s = PG_GETARG_INT16(1); int16 s = PG_GETARG_INT16(1);
Cash result;
if (s == 0) PG_RETURN_CASH(cash_div_int64(c, (int64) s));
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
result = c / s;
PG_RETURN_CASH(result);
} }
/* cashlarger() /* cashlarger()

View File

@ -528,3 +528,22 @@ SELECT '-92233720368547758.08'::money::numeric;
-92233720368547758.08 -92233720368547758.08
(1 row) (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

View File

@ -135,3 +135,14 @@ SELECT '12345678901234567'::money::numeric;
SELECT '-12345678901234567'::money::numeric; SELECT '-12345678901234567'::money::numeric;
SELECT '92233720368547758.07'::money::numeric; SELECT '92233720368547758.07'::money::numeric;
SELECT '-92233720368547758.08'::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;