Subject: Re-add recently-removed tests for ltree and intarray

From: Michael Paquier <[email-masked]>
Date: Fri, 15 May 2026 08:38:17 +0900

Hi all,

Some of you may have noticed that some regression tests have been
removed due to some noise in the buildfarm, as of commit 906ea101d0d5.

We did not have time to do something for this release, unfortunately.
It is possible to reproduce the incompatibility by setting
max_stack_depth to a low value, where the first new query of ltree and
intarray would fail, when written in their original shape.

Tom had the idea to switch these two unstable tests to use a balanced
binary tree instead, so as they don't eat the stack still are able to
cover the recent fixes pushed into the tree.

And this investigation has led me to the attached, to-be-backpatched
down to v14.  Even under a low max_stack_depth, these new tests are
stable.  I could not see an issue for the two tests added at the
bottom of ltree.

Opinions or comments?

Thanks,
--
Michael
From d11a1dc0e8dc8d4838ca81fe8d4aa935cc1ed2df Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email-masked]>
Date: Thu, 14 May 2026 13:40:00 +0900
Subject: [PATCH] Re-add regression tests for ltree and intarray

These tests have been removed by 906ea101d0d5, due to some of them being
unstable in the buildfarm with low max_stack_depth values.  These tests
are reworked to be more portable.

The tests to cover the findoprnd() overflows use a balanced tree to
avoid using too much stack, per a suggestion by Tom Lane.

Backpatch-through: 14
---
 contrib/intarray/expected/_int.out | 15 +++++++++++++++
 contrib/intarray/sql/_int.sql      | 13 +++++++++++++
 contrib/ltree/expected/ltree.out   | 24 ++++++++++++++++++++++++
 contrib/ltree/sql/ltree.sql        | 20 ++++++++++++++++++++
 4 files changed, 72 insertions(+)

diff --git a/contrib/intarray/expected/_int.out b/contrib/intarray/expected/_int.out
index d0e68d0447fb..fb4086a95caf 100644
--- a/contrib/intarray/expected/_int.out
+++ b/contrib/intarray/expected/_int.out
@@ -398,6 +398,21 @@ SELECT '1&(2&(4&(5|!6)))'::query_int;
  1 & 2 & 4 & ( 5 | !6 )
 (1 row)
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := '1';
+BEGIN
+  FOR i IN 1..15 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('0|' || e)::query_int;
+END;
+$$;
+ERROR:  query_int expression is too complex
+CONTEXT:  SQL statement "SELECT ('0|' || e)::query_int"
+PL/pgSQL function inline_code_block line 8 at PERFORM
 -- test non-error-throwing input
 SELECT str as "query_int",
        pg_input_is_valid(str,'query_int') as ok,
diff --git a/contrib/intarray/sql/_int.sql b/contrib/intarray/sql/_int.sql
index 5668ab407045..0d30914725e1 100644
--- a/contrib/intarray/sql/_int.sql
+++ b/contrib/intarray/sql/_int.sql
@@ -75,6 +75,19 @@ SELECT '1&2&4&5&6'::query_int;
 SELECT '1&(2&(4&(5|6)))'::query_int;
 SELECT '1&(2&(4&(5|!6)))'::query_int;
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := '1';
+BEGIN
+  FOR i IN 1..15 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('0|' || e)::query_int;
+END;
+$$;
+
 -- test non-error-throwing input
 
 SELECT str as "query_int",
diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index d2a566284755..15b9131a7506 100644
--- a/contrib/ltree/expected/ltree.out
+++ b/contrib/ltree/expected/ltree.out
@@ -1283,6 +1283,21 @@ SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_rw%*'::ltxtquery;
  f
 (1 row)
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := 'a';
+BEGIN
+  FOR i IN 1..14 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('b|' || e)::ltxtquery;
+END;
+$$;
+ERROR:  ltxtquery is too large
+CONTEXT:  SQL statement "SELECT ('b|' || e)::ltxtquery"
+PL/pgSQL function inline_code_block line 8 at PERFORM
 --arrays
 SELECT '{1.2.3}'::ltree[] @> '1.2.3.4';
  ?column? 
@@ -8202,3 +8217,12 @@ FROM (VALUES ('.2.3', 'ltree'),
  !tree & aWdf@* | ltxtquery | t  |                |                                    |                          | 
 (8 rows)
 
+-- Test for overflow of lquery_level.totallen.
+SELECT (repeat('x', 255) || repeat('|' || repeat('x', 255), 256))::lquery;
+ERROR:  lquery level is too large
+DETAIL:  Total size of level exceeds the maximum allowed (65535 bytes).
+--- Test for overflow of lquery_level.numvar, with a set of single-char
+--- variants in one level.
+SELECT (repeat('a|', 65535) || 'a')::lquery;
+ERROR:  lquery level has too many variants
+DETAIL:  Number of variants exceeds the maximum allowed (65535).
diff --git a/contrib/ltree/sql/ltree.sql b/contrib/ltree/sql/ltree.sql
index 77e6958c62a7..d0fade9d17d8 100644
--- a/contrib/ltree/sql/ltree.sql
+++ b/contrib/ltree/sql/ltree.sql
@@ -253,6 +253,19 @@ SELECT 'tree.awdfg'::ltree @ 'tree & aWdfg@'::ltxtquery;
 SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_qw%*'::ltxtquery;
 SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_rw%*'::ltxtquery;
 
+-- Test for overflow of the int16 "left" field in findoprnd().
+-- This query uses a balanced binary tree to avoid using too much stack.
+DO $$
+DECLARE
+  e text := 'a';
+BEGIN
+  FOR i IN 1..14 LOOP
+    e := '(' || e || '&' || e || ')';
+  END LOOP;
+  PERFORM ('b|' || e)::ltxtquery;
+END;
+$$;
+
 --arrays
 
 SELECT '{1.2.3}'::ltree[] @> '1.2.3.4';
@@ -457,3 +470,10 @@ FROM (VALUES ('.2.3', 'ltree'),
              ('!tree & aWdf@*','ltxtquery'))
       AS a(str,typ),
      LATERAL pg_input_error_info(a.str, a.typ) as errinfo;
+
+-- Test for overflow of lquery_level.totallen.
+SELECT (repeat('x', 255) || repeat('|' || repeat('x', 255), 256))::lquery;
+
+--- Test for overflow of lquery_level.numvar, with a set of single-char
+--- variants in one level.
+SELECT (repeat('a|', 65535) || 'a')::lquery;
-- 
2.54.0

---

From: Tom Lane <[email-masked]>
Date: Thu, 14 May 2026 22:09:29 -0400

> intarray would fail, when written in their original shape.

Just to add a little more color to this --- what we discovered after
there was time for some investigation was that:

(a) the stack-overflow failure occurred in the findoprnd() function
of intarray/_int_bool.c or ltree/ltxtquery_io.c.

(b) the failure only appeared on buildfarm members running on ppc64
or s390x.  I determined by examining assembly code that ppc64 uses
about 3X as much stack per call level in this function as x86_64;
probably s390x is similar.  That was enough to overrun our default
max_stack_depth on these architectures, even though the same case
passed on the machines we'd tested on.

(c) even with minimum max_stack_depth, the test passed using gcc
but not clang.  Again examining assembly code, gcc is smart enough
to collapse the tail-recursion calls in findoprnd() into looping,
causing the original test case's right-deep query tree to consume
essentially zero stack space.  clang doesn't do that, at least not
on those arches at default optimization level.  You can make gcc
fail too with -O0.

So it'd be good to verify on a few oddball platforms that Michael's
new attempt is OK.  It should theoretically work, but ...

			regards, tom lane

---

From: John Naylor <[email-masked]>
Date: Fri, 15 May 2026 09:47:04 +0700

> passed on the machines we'd tested on.

FWIW, I tried to reproduce with the former new tests un-reverted, and
didn't see stack overflow on the following, so unless I fat-fingered
that I wonder if there's something more specific on the previously
failing members:

ppc64le / gcc 8.5 / Linux kernel 4.18
S390X / gcc 13.3 / Linux kernel 6.8

-- 
John Naylor
Amazon Web Services

---

From: Tom Lane <[email-masked]>
Date: Thu, 14 May 2026 22:49:38 -0400

> S390X / gcc 13.3 / Linux kernel 6.8

Hm, did you use -O0 ?

			regards, tom lane

---

From: Michael Paquier <[email-masked]>
Date: Fri, 15 May 2026 13:59:35 +0900

>> S390X / gcc 13.3 / Linux kernel 6.8

Yeah, that should matter.  I don't immediately see why the new tests
should fail at hand..  And unfortunately I don't have these
environments at hand to double-check things, so I think that I am
going to take a bet on HEAD.  Then if things work, do a backpatch.
--
Michael

---

From: John Naylor <[email-masked]>
Date: Fri, 15 May 2026 13:33:53 +0700

> > S390X / gcc 13.3 / Linux kernel 6.8

I just now tried -O0 on yesterday's master with ppc64le, with the
previous new tests re-added, and it did fail. Then, pulled in master
with the tests just now committed, and it passed.

-- 
John Naylor
Amazon Web Services

---

From: Michael Paquier <[email-masked]>
Date: Fri, 15 May 2026 15:54:59 +0900

> with the tests just now committed, and it passed.

イエイ。

Thanks for checking.  3 animals running on ppc have currently
passed with the new tests on HEAD, all passing.  They are not the ones
that have failed previously, so I'm still holding a bit longer..
--
Michael