From dc35e63ec507edad75e946eddfd6152452c96216 Mon Sep 17 00:00:00 2001 From: Fabiology Date: Thu, 17 Sep 2026 17:35:56 -0400 Subject: [PATCH] Raise division by zero for modulo with a zero divisor agtype_mod left the zero divisor unguarded, so an integer modulo trapped as SIGFPE and surfaced as "floating-point exception" (22P01) on x86_64, and silently returned the dividend on aarch64. The float paths returned NaN. agtype_div already checks for it, and so does PostgreSQL's int8mod. Closes #2514 --- regress/expected/agtype.out | 35 ++++++++++++++++++++++++ regress/sql/agtype.sql | 20 ++++++++++++++ src/backend/utils/adt/agtype_ops.c | 44 ++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/regress/expected/agtype.out b/regress/expected/agtype.out index 0f4775b88..fa6af8dc5 100644 --- a/regress/expected/agtype.out +++ b/regress/expected/agtype.out @@ -601,6 +601,41 @@ SELECT agtype_any_div('-1::numeric', 0); ERROR: division by zero SELECT agtype_any_div('-1.0::numeric', 0); ERROR: division by zero +SELECT agtype_mod('1', '0'); +ERROR: division by zero +SELECT agtype_mod('1', '0.0'); +ERROR: division by zero +SELECT agtype_mod('1.0', '0'); +ERROR: division by zero +SELECT agtype_mod('1.0', '0.0'); +ERROR: division by zero +SELECT agtype_mod('1', '0::numeric'); +ERROR: division by zero +SELECT agtype_mod('1.0', '0::numeric'); +ERROR: division by zero +SELECT agtype_mod('1::numeric', '0'); +ERROR: division by zero +SELECT agtype_mod('1::numeric', '0.0'); +ERROR: division by zero +SELECT agtype_mod('1::numeric', '0::numeric'); +ERROR: division by zero +SELECT agtype_any_mod('1', 0); +ERROR: division by zero +SELECT agtype_any_mod('1.0', 0); +ERROR: division by zero +SELECT agtype_any_mod('-1::numeric', 0); +ERROR: division by zero +SELECT agtype_any_mod('-1.0::numeric', 0); +ERROR: division by zero +-- +-- Should get 0 - INT64_MIN % -1 is well defined +-- +SELECT agtype_mod('-9223372036854775808', '-1'); + agtype_mod +------------ + 0 +(1 row) + -- -- Should get Infinity -- diff --git a/regress/sql/agtype.sql b/regress/sql/agtype.sql index e13edb16d..e351bbafe 100644 --- a/regress/sql/agtype.sql +++ b/regress/sql/agtype.sql @@ -199,6 +199,26 @@ SELECT agtype_any_div('1.0', 0); SELECT agtype_any_div('-1::numeric', 0); SELECT agtype_any_div('-1.0::numeric', 0); +SELECT agtype_mod('1', '0'); +SELECT agtype_mod('1', '0.0'); +SELECT agtype_mod('1.0', '0'); +SELECT agtype_mod('1.0', '0.0'); +SELECT agtype_mod('1', '0::numeric'); +SELECT agtype_mod('1.0', '0::numeric'); +SELECT agtype_mod('1::numeric', '0'); +SELECT agtype_mod('1::numeric', '0.0'); +SELECT agtype_mod('1::numeric', '0::numeric'); + +SELECT agtype_any_mod('1', 0); +SELECT agtype_any_mod('1.0', 0); +SELECT agtype_any_mod('-1::numeric', 0); +SELECT agtype_any_mod('-1.0::numeric', 0); + +-- +-- Should get 0 - INT64_MIN % -1 is well defined +-- +SELECT agtype_mod('-9223372036854775808', '-1'); + -- -- Should get Infinity -- diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index f7f45b467..e0d9dd6e1 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -912,24 +912,64 @@ Datum agtype_mod(PG_FUNCTION_ARGS) if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER) { + if (agtv_rhs->val.int_value == 0) + { + ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + PG_RETURN_NULL(); + } + agtv_result.type = AGTV_INTEGER; - agtv_result.val.int_value = agtv_lhs->val.int_value % - agtv_rhs->val.int_value; + + /* + * INT64_MIN % -1 is zero but overflows the quotient, which traps on + * some platforms. int8mod special cases it the same way. + */ + if (agtv_rhs->val.int_value == -1) + { + agtv_result.val.int_value = 0; + } + else + { + agtv_result.val.int_value = agtv_lhs->val.int_value % + agtv_rhs->val.int_value; + } } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) { + if (agtv_rhs->val.float_value == 0) + { + ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + PG_RETURN_NULL(); + } + agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = fmod(agtv_lhs->val.float_value, agtv_rhs->val.float_value); } else if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_INTEGER) { + if (agtv_rhs->val.int_value == 0) + { + ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + PG_RETURN_NULL(); + } + agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = fmod(agtv_lhs->val.float_value, agtv_rhs->val.int_value); } else if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_FLOAT) { + if (agtv_rhs->val.float_value == 0) + { + ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + PG_RETURN_NULL(); + } + agtv_result.type = AGTV_FLOAT; agtv_result.val.float_value = fmod(agtv_lhs->val.int_value, agtv_rhs->val.float_value);