Add overflow checks to money type.
authorNathan Bossart <[email protected]>
Fri, 19 Jul 2024 16:52:32 +0000 (11:52 -0500)
committerNathan Bossart <[email protected]>
Fri, 19 Jul 2024 16:52:32 +0000 (11:52 -0500)
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

src/backend/utils/adt/cash.c
src/test/regress/expected/money.out
src/test/regress/sql/money.sql

index 6515fc8ec6955dc8a9f001c2928ef9231b3c347c..2a63ecfa4e91b4e998c6337b68c16ecee738fac5 100644 (file)
@@ -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()
index fc71a72fed319b76848db757efb4092a54cb3a8d..90140f66767d000f8ed6cd89f58f3785d36354ea 100644 (file)
@@ -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
index 5e746286c9079e0444c509d81e02e0359ce3e83a..1eb471d1e4e704f4e698cf8c9a6581a65069c185 100644 (file)
@@ -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;