From 772251744a505e2a28e7c2daf2f5be33a748295a Mon Sep 17 00:00:00 2001 From: Fujii Masao Date: Tue, 1 Sep 2026 11:42:13 +0900 Subject: [PATCH] Fix integer to_char() overflow with V format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When to_char() formatted an integer value with a V pattern, it could return an incorrect result instead of reporting an overflow. V shifts the decimal point by multiplying the input value by a power of ten before formatting it, so, for example, to_char(3, '9V999999999') requires computing 3 * 10^9. This result does not fit in int4, but the integer variant of to_char() performed the multiplication using a plain int32 expression. The intermediate result could therefore overflow, causing the function to output incorrect digits instead of raising "integer out of range". Use dtoi4() and int4mul() for this calculation so that both an out-of-range multiplier and an out-of-range product are detected, as with ordinary integer arithmetic. This also matches the existing int8 implementation, which uses dtoi8() and int8mul() for the same operation. After this change, to_char() with V format either returns the correctly formatted result when the scaled value fits in int4, or raises "integer out of range" when it does not. Backpatch to all supported versions. Reported-by: Andrey Rachitskiy Author: Andrey Rachitskiy Reviewed-by: MiƂosz Bieniek Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/CAB8bMivEfqZxOVdzc3kZDN++XshmkEz2t7dfGBU8+oUm864EZg@mail.gmail.com Backpatch-through: 14 --- src/backend/utils/adt/formatting.c | 16 +++++++++------- src/test/regress/expected/int4.out | 27 +++++++++++++++++++++++++++ src/test/regress/sql/int4.sql | 9 +++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 2ab36407008..580526bd832 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -6510,15 +6510,17 @@ int4_to_char(PG_FUNCTION_ARGS) if (IS_MULTI(&Num)) { - orgnum = DatumGetCString(DirectFunctionCall1(int4out, - Int32GetDatum(value * ((int32) pow((double) 10, (double) Num.multi))))); + double multi = pow((double) 10, (double) Num.multi); + + value = DatumGetInt32(DirectFunctionCall2(int4mul, + Int32GetDatum(value), + DirectFunctionCall1(dtoi4, + Float8GetDatum(multi)))); Num.pre += Num.multi; } - else - { - orgnum = DatumGetCString(DirectFunctionCall1(int4out, - Int32GetDatum(value))); - } + + orgnum = DatumGetCString(DirectFunctionCall1(int4out, + Int32GetDatum(value))); if (*orgnum == '-') { diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out index fbcc0e8d9e6..c510ebe5b63 100644 --- a/src/test/regress/expected/int4.out +++ b/src/test/regress/expected/int4.out @@ -345,6 +345,33 @@ SELECT (-2147483648)::int4 % (-1)::int2; 0 (1 row) +-- check overflow of to_char() with V format +SELECT to_char(2, '9V999999999'); -- 10^9 + to_char +------------- + 2000000000 +(1 row) + +SELECT to_char(3, '9V999999999'); -- 10^9 +ERROR: integer out of range +SELECT to_char(214748364, '999999999V9'); + to_char +------------- + 2147483640 +(1 row) + +SELECT to_char(2147483647, '9V9'); +ERROR: integer out of range +SELECT to_char(-2, '9V999999999'); -- 10^9 + to_char +------------- + -2000000000 +(1 row) + +SELECT to_char((-2147483648)::int4, '9V9'); +ERROR: integer out of range +SELECT to_char(1, '9V9999999999'); -- 10^10 +ERROR: integer out of range -- check rounding when casting from float SELECT x, x::int4 AS int4_value FROM (VALUES (-2.5::float8), diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql index f19077f3da2..4281413e0b6 100644 --- a/src/test/regress/sql/int4.sql +++ b/src/test/regress/sql/int4.sql @@ -120,6 +120,15 @@ SELECT (-2147483648)::int4 * (-1)::int2; SELECT (-2147483648)::int4 / (-1)::int2; SELECT (-2147483648)::int4 % (-1)::int2; +-- check overflow of to_char() with V format +SELECT to_char(2, '9V999999999'); -- 10^9 +SELECT to_char(3, '9V999999999'); -- 10^9 +SELECT to_char(214748364, '999999999V9'); +SELECT to_char(2147483647, '9V9'); +SELECT to_char(-2, '9V999999999'); -- 10^9 +SELECT to_char((-2147483648)::int4, '9V9'); +SELECT to_char(1, '9V9999999999'); -- 10^10 + -- check rounding when casting from float SELECT x, x::int4 AS int4_value FROM (VALUES (-2.5::float8),