postgres.git / summary / log / commit / refs

commit    9efc2f9d8964ec927ccdeb4d461ff8070b0e6255
Author:   Fujii Masao <fujii@postgresql.org>
Date:     Tue Sep 01 02:42:13 2026 +0000

    Fix integer to_char() overflow with V format
    
    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 <pl0h0yp1@gmail.com>
    Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
    Reviewed-by: MiƂosz Bieniek <bieniek.milosz@proton.me>
    Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
    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 effad4c37dd..ef5ec3227a1 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -6616,15 +6616,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 b1a15888ef8..fca591fad84 100644 --- a/src/test/regress/expected/int4.out +++ b/src/test/regress/expected/int4.out @@ -370,6 +370,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 e9d89e8111f..d27f5500e01 100644 --- a/src/test/regress/sql/int4.sql +++ b/src/test/regress/sql/int4.sql @@ -126,6 +126,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), [parent: ca8d4bdb047f]