From ccbcc7325d6096741b48f536c0eceea73e858ac1 Mon Sep 17 00:00:00 2001 From: Ewan Young Date: Tue, 25 Aug 2026 22:43:10 +0800 Subject: [PATCH v1] Fix right() with the most negative integer A negative n means "return all but the first |n| characters", so text_right() negates n before clipping. Negating PG_INT32_MIN overflows; with -fwrapv the result is PG_INT32_MIN again, still negative, and pg_mbcharcliplen() then returns an offset of zero, so the whole string is returned where the correct answer is an empty string: SELECT right('abcdef', (-2147483648)::int4); -- 'abcdef', want '' SELECT right('abcdef', -2147483647); -- '', correct Clamp to PG_INT32_MAX instead. Any n whose absolute value is at least the string's length skips all of it, and a text value cannot be longer than PG_INT32_MAX, so this gives the same answer for every other input. Note that erroring out, as text_format_string_conversion() does for a width of INT_MIN a few hundred lines away, would not be right here: unlike a format width, an out-of-range skip count has a well-defined result. text_left() is not affected. Its negative case computes the character length plus n rather than negating n, and since the length is non-negative and bounded by the varlena size limit that sum cannot overflow. --- src/backend/utils/adt/varlena.c | 12 +++++++++++- src/test/regress/expected/text.out | 8 ++++++++ src/test/regress/sql/text.sql | 3 +++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index a09a9e5d5bb..3117069cf1a 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -4714,7 +4714,17 @@ text_right(PG_FUNCTION_ARGS) int off; if (n < 0) - n = -n; + { + /* + * Negating PG_INT32_MIN would overflow, so clamp instead. Any n whose + * absolute value is at least the string's length skips the whole + * string, and len can't exceed PG_INT32_MAX, so this is equivalent. + */ + if (unlikely(n == PG_INT32_MIN)) + n = PG_INT32_MAX; + else + n = -n; + } else n = pg_mbstrlen_with_len(p, len) - n; off = pg_mbcharcliplen(p, len, n); diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out index 3f9982388ba..9ef23f4ddea 100644 --- a/src/test/regress/expected/text.out +++ b/src/test/regress/expected/text.out @@ -118,6 +118,14 @@ select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) ord 5 | ahoj | ahoj (11 rows) +-- the most negative value must skip the whole string, same as any other n +-- whose absolute value exceeds its length +select left('ahoj', (-2147483648)::int4), right('ahoj', (-2147483648)::int4); + left | right +------+------- + | +(1 row) + select quote_literal(''); quote_literal --------------- diff --git a/src/test/regress/sql/text.sql b/src/test/regress/sql/text.sql index 540e551254d..3a9be863bb7 100644 --- a/src/test/regress/sql/text.sql +++ b/src/test/regress/sql/text.sql @@ -37,6 +37,9 @@ select concat_ws('',10,20,null,30); select concat_ws(NULL,10,20,null,30) is null; select reverse('abcde'); select i, left('ahoj', i), right('ahoj', i) from generate_series(-5, 5) t(i) order by i; +-- the most negative value must skip the whole string, same as any other n +-- whose absolute value exceeds its length +select left('ahoj', (-2147483648)::int4), right('ahoj', (-2147483648)::int4); select quote_literal(''); select quote_literal('abc'''); select quote_literal(e'\\'); -- 2.47.3