From 377d27b9ada54226bb30484947b13dc5338b7708 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Thu, 13 Jun 2024 22:39:25 -0400 Subject: [PATCH 2/4] Handle overflow in money arithmetic --- src/backend/utils/adt/cash.c | 141 +++++++++++++++++++--------- src/test/regress/expected/money.out | 90 ++++++++++++++++++ src/test/regress/sql/money.sql | 47 ++++++++++ 3 files changed, 234 insertions(+), 44 deletions(-) diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index f6f095a57b..8767d3b688 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/numeric.h" #include "utils/pg_locale.h" @@ -86,6 +87,79 @@ num_word(Cash value) return buf; } /* num_word() */ +static Cash +cash_mul_float8(Cash c, float8 f) +{ + float8 fresult; + + if (unlikely(isinf(f) || isnan(f))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("invalid float value"))); + + fresult = rint(float8_mul((float8) c, f)); + + if (unlikely(!FLOAT8_FITS_IN_INT64(fresult))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); + + return (Cash) fresult; +} + +static Cash +cash_div_float8(Cash c, float8 f) +{ + float8 fresult; + + if (unlikely(f == 0.0)) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + if (unlikely(isinf(f) || isnan(f))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("invalid float value"))); + + fresult = rint(float8_div((float8) c, f)); + + if (unlikely(!FLOAT8_FITS_IN_INT64(fresult))) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); + + return (Cash) fresult; +} + +static Cash +cash_mul_int64(Cash c, int64 i) +{ + Cash result; + + if (pg_mul_s64_overflow(c, i, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); + + return result; +} + +static Cash +cash_div_int64(Cash c, int64 i) +{ + Cash result; + + if (i == 0) + ereport(ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + + result = c / i; + + return result; +} + + /* cash_in() * Convert a string to a cash data type. * Format is [$]###[,]###[.##] @@ -617,7 +691,10 @@ cash_pl(PG_FUNCTION_ARGS) Cash c2 = PG_GETARG_CASH(1); Cash result; - result = c1 + c2; + if (pg_add_s64_overflow(c1, c2, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); PG_RETURN_CASH(result); } @@ -633,7 +710,10 @@ cash_mi(PG_FUNCTION_ARGS) Cash c2 = PG_GETARG_CASH(1); Cash result; - result = c1 - c2; + if (pg_sub_s64_overflow(c1, c2, &result)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); PG_RETURN_CASH(result); } @@ -669,7 +749,7 @@ cash_mul_flt8(PG_FUNCTION_ARGS) float8 f = PG_GETARG_FLOAT8(1); Cash result; - result = rint(c * f); + result = cash_mul_float8(c, f); PG_RETURN_CASH(result); } @@ -684,7 +764,7 @@ flt8_mul_cash(PG_FUNCTION_ARGS) Cash c = PG_GETARG_CASH(1); Cash result; - result = rint(f * c); + result = cash_mul_float8(c, f); PG_RETURN_CASH(result); } @@ -699,12 +779,7 @@ cash_div_flt8(PG_FUNCTION_ARGS) 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); + result = cash_div_float8(c, (float8) f); PG_RETURN_CASH(result); } @@ -719,7 +794,7 @@ cash_mul_flt4(PG_FUNCTION_ARGS) float4 f = PG_GETARG_FLOAT4(1); Cash result; - result = rint(c * (float8) f); + result = cash_mul_float8(c, f); PG_RETURN_CASH(result); } @@ -734,7 +809,7 @@ flt4_mul_cash(PG_FUNCTION_ARGS) Cash c = PG_GETARG_CASH(1); Cash result; - result = rint((float8) f * c); + result = cash_mul_float8(c, f); PG_RETURN_CASH(result); } @@ -750,12 +825,7 @@ cash_div_flt4(PG_FUNCTION_ARGS) 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); + result = cash_div_float8(c, (float8) f); PG_RETURN_CASH(result); } @@ -770,7 +840,7 @@ cash_mul_int8(PG_FUNCTION_ARGS) int64 i = PG_GETARG_INT64(1); Cash result; - result = c * i; + result = cash_mul_int64(c, i); PG_RETURN_CASH(result); } @@ -785,7 +855,7 @@ int8_mul_cash(PG_FUNCTION_ARGS) Cash c = PG_GETARG_CASH(1); Cash result; - result = i * c; + result = cash_mul_int64(c, i); PG_RETURN_CASH(result); } @@ -799,13 +869,7 @@ cash_div_int8(PG_FUNCTION_ARGS) 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; - + result = cash_div_int64(c, i); PG_RETURN_CASH(result); } @@ -820,7 +884,7 @@ cash_mul_int4(PG_FUNCTION_ARGS) int32 i = PG_GETARG_INT32(1); Cash result; - result = c * i; + result = cash_mul_int64(c, (int64) i); PG_RETURN_CASH(result); } @@ -835,7 +899,7 @@ int4_mul_cash(PG_FUNCTION_ARGS) Cash c = PG_GETARG_CASH(1); Cash result; - result = i * c; + result = cash_mul_int64(c, (int64) i); PG_RETURN_CASH(result); } @@ -851,13 +915,7 @@ cash_div_int4(PG_FUNCTION_ARGS) 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; - + result = cash_div_int64(c, (int64) i); PG_RETURN_CASH(result); } @@ -872,7 +930,7 @@ cash_mul_int2(PG_FUNCTION_ARGS) int16 s = PG_GETARG_INT16(1); Cash result; - result = c * s; + result = cash_mul_int64(c, (int64) s); PG_RETURN_CASH(result); } @@ -886,7 +944,7 @@ int2_mul_cash(PG_FUNCTION_ARGS) Cash c = PG_GETARG_CASH(1); Cash result; - result = s * c; + result = cash_mul_int64(c, (int64) s); PG_RETURN_CASH(result); } @@ -901,12 +959,7 @@ cash_div_int2(PG_FUNCTION_ARGS) 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; + result = cash_div_int64(c, (int64) s); PG_RETURN_CASH(result); } diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out index 7fd4e31804..d1212fdf43 100644 --- a/src/test/regress/expected/money.out +++ b/src/test/regress/expected/money.out @@ -528,3 +528,93 @@ SELECT '-92233720368547758.08'::money::numeric; -92233720368547758.08 (1 row) +-- Test 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::int8; +ERROR: money out of range +SELECT '-92233720368547758.08'::money * 2::int8; +ERROR: money out of range +SELECT 2::int8 * '92233720368547758.07'::money ; +ERROR: money out of range +SELECT 2::int8 * '-92233720368547758.08'::money; +ERROR: money out of range +SELECT '92233720368547758.07'::money * 2::int4; +ERROR: money out of range +SELECT '-92233720368547758.08'::money * 2::int4; +ERROR: money out of range +SELECT 2::int4 * '92233720368547758.07'::money ; +ERROR: money out of range +SELECT 2::int4 * '-92233720368547758.08'::money; +ERROR: money out of range +SELECT '92233720368547758.07'::money * 2::int2; +ERROR: money out of range +SELECT '-92233720368547758.08'::money * 2::int2; +ERROR: money out of range +SELECT 2::int2 * '92233720368547758.07'::money ; +ERROR: money out of range +SELECT 2::int2 * '-92233720368547758.08'::money; +ERROR: money out of range +SELECT '92233720368547758.07'::money * 2::float8; +ERROR: money out of range +SELECT '-92233720368547758.08'::money * 2::float8; +ERROR: money out of range +SELECT 2::float8 * '92233720368547758.07'::money ; +ERROR: money out of range +SELECT 2::float8 * '-92233720368547758.08'::money; +ERROR: money out of range +SELECT '92233720368547758.07'::money * 2::float4; +ERROR: money out of range +SELECT '-92233720368547758.08'::money * 2::float4; +ERROR: money out of range +SELECT 2::float4 * '92233720368547758.07'::money ; +ERROR: money out of range +SELECT 2::float4 * '-92233720368547758.08'::money; +ERROR: money out of range +SELECT '1'::money / 1.175494e-38::float8; +ERROR: money out of range +SELECT '-1'::money / 1.175494e-38::float8; +ERROR: money out of range +SELECT '1'::money / 1.175494e-38::float4; +ERROR: money out of range +SELECT '-1'::money / 1.175494e-38::float4; +ERROR: money out of range +-- Test invalid multipliers/divisors +SELECT '42'::money * 'inf'::float8; +ERROR: invalid float value +SELECT '42'::money * '-inf'::float8; +ERROR: invalid float value +SELECT '42'::money * 'nan'::float8; +ERROR: invalid float value +SELECT 'inf'::float8 * '42'::money; +ERROR: invalid float value +SELECT '-inf'::float8 * '42'::money; +ERROR: invalid float value +SELECT 'nan'::float8 * '42'::money; +ERROR: invalid float value +SELECT '42'::money / 'inf'::float8; +ERROR: invalid float value +SELECT '42'::money / '-inf'::float8; +ERROR: invalid float value +SELECT '42'::money / 'nan'::float8; +ERROR: invalid float value +SELECT '42'::money * 'inf'::float4; +ERROR: invalid float value +SELECT '42'::money * '-inf'::float4; +ERROR: invalid float value +SELECT '42'::money * 'nan'::float4; +ERROR: invalid float value +SELECT 'inf'::float4 * '42'::money; +ERROR: invalid float value +SELECT '-inf'::float4 * '42'::money; +ERROR: invalid float value +SELECT 'nan'::float4 * '42'::money; +ERROR: invalid float value +SELECT '42'::money / 'inf'::float4; +ERROR: invalid float value +SELECT '42'::money / '-inf'::float4; +ERROR: invalid float value +SELECT '42'::money / 'nan'::float4; +ERROR: invalid float value diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql index 81c92dd960..4422d2816b 100644 --- a/src/test/regress/sql/money.sql +++ b/src/test/regress/sql/money.sql @@ -135,3 +135,50 @@ SELECT '12345678901234567'::money::numeric; SELECT '-12345678901234567'::money::numeric; SELECT '92233720368547758.07'::money::numeric; SELECT '-92233720368547758.08'::money::numeric; + +-- Test overflow checks +SELECT '92233720368547758.07'::money + '0.01'::money; +SELECT '-92233720368547758.08'::money - '0.01'::money; +SELECT '92233720368547758.07'::money * 2::int8; +SELECT '-92233720368547758.08'::money * 2::int8; +SELECT 2::int8 * '92233720368547758.07'::money ; +SELECT 2::int8 * '-92233720368547758.08'::money; +SELECT '92233720368547758.07'::money * 2::int4; +SELECT '-92233720368547758.08'::money * 2::int4; +SELECT 2::int4 * '92233720368547758.07'::money ; +SELECT 2::int4 * '-92233720368547758.08'::money; +SELECT '92233720368547758.07'::money * 2::int2; +SELECT '-92233720368547758.08'::money * 2::int2; +SELECT 2::int2 * '92233720368547758.07'::money ; +SELECT 2::int2 * '-92233720368547758.08'::money; +SELECT '92233720368547758.07'::money * 2::float8; +SELECT '-92233720368547758.08'::money * 2::float8; +SELECT 2::float8 * '92233720368547758.07'::money ; +SELECT 2::float8 * '-92233720368547758.08'::money; +SELECT '92233720368547758.07'::money * 2::float4; +SELECT '-92233720368547758.08'::money * 2::float4; +SELECT 2::float4 * '92233720368547758.07'::money ; +SELECT 2::float4 * '-92233720368547758.08'::money; +SELECT '1'::money / 1.175494e-38::float8; +SELECT '-1'::money / 1.175494e-38::float8; +SELECT '1'::money / 1.175494e-38::float4; +SELECT '-1'::money / 1.175494e-38::float4; +-- Test invalid multipliers/divisors +SELECT '42'::money * 'inf'::float8; +SELECT '42'::money * '-inf'::float8; +SELECT '42'::money * 'nan'::float8; +SELECT 'inf'::float8 * '42'::money; +SELECT '-inf'::float8 * '42'::money; +SELECT 'nan'::float8 * '42'::money; +SELECT '42'::money / 'inf'::float8; +SELECT '42'::money / '-inf'::float8; +SELECT '42'::money / 'nan'::float8; +SELECT '42'::money * 'inf'::float4; +SELECT '42'::money * '-inf'::float4; +SELECT '42'::money * 'nan'::float4; +SELECT 'inf'::float4 * '42'::money; +SELECT '-inf'::float4 * '42'::money; +SELECT 'nan'::float4 * '42'::money; +SELECT '42'::money / 'inf'::float4; +SELECT '42'::money / '-inf'::float4; +SELECT '42'::money / 'nan'::float4; -- 2.34.1