From 76eb993a7bbe54418d1f06b9a5b641ecbdabfc12 Mon Sep 17 00:00:00 2001
From: Jim Meyering <meyering@meta.com>
Date: Sun, 19 Apr 2026 09:52:26 -0700
Subject: [PATCH] regex: avoid a UBSAN failure: remove an unnecessary
 DEBUG_ASSERT

* lib/regex_internal.c (re_node_set_insert): Remove the DEBUG_ASSERT
and instead return early for an attempt to insert an ELEM that is
already present in the set.  Relax the function's comment that says
there should be no duplicate.  This function is called from many
places and has been working fine.  With its nontrivial backrefs,
the sample regexp apparently elicits enough backtracking retries
and state-set merges to trigger this duplicate insertion attempt.
Reported by Bruno Haible in
https://lists.gnu.org/r/bug-gnulib/2026-04/msg00138.html
---
 ChangeLog            | 13 +++++++++++++
 lib/regex_internal.c |  8 +++++---
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 2f6f99e51b..96f180c446 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2026-04-19  Jim Meyering  <meyering@meta.com>
+
+	regex: avoid a UBSAN failure: remove an unnecessary DEBUG_ASSERT
+	* lib/regex_internal.c (re_node_set_insert): Remove the DEBUG_ASSERT
+	and instead return early for an attempt to insert an ELEM that is
+	already present in the set.  Relax the function's comment that says
+	there should be no duplicate.  This function is called from many
+	places and has been working fine.  With its nontrivial backrefs,
+	the sample regexp apparently elicits enough backtracking retries
+	and state-set merges to trigger this duplicate insertion attempt.
+	Reported by Bruno Haible in
+	https://lists.gnu.org/r/bug-gnulib/2026-04/msg00138.html
+
 2026-04-19  Bruno Haible  <bruno@clisp.org>

 	regex tests: Add a test case that triggers an assertion failure.
diff --git a/lib/regex_internal.c b/lib/regex_internal.c
index 4b9b80f6b9..af4b41bcdb 100644
--- a/lib/regex_internal.c
+++ b/lib/regex_internal.c
@@ -1241,8 +1241,8 @@ re_node_set_merge (re_node_set *dest, const re_node_set *src)
 }

 /* Insert the new element ELEM to the re_node_set* SET.
-   SET should not already have ELEM.
-   Return true if successful.  */
+   SET is not expected to already contain ELEM, but tolerate
+   duplicates as a no-op.  Return true if successful.  */

 static bool
 __attribute_warn_unused_result__
@@ -1286,7 +1286,9 @@ re_node_set_insert (re_node_set *set, Idx elem)
     {
       for (idx = set->nelem; set->elems[idx - 1] > elem; idx--)
 	set->elems[idx] = set->elems[idx - 1];
-      DEBUG_ASSERT (set->elems[idx - 1] < elem);
+      /* Already in set.  Return early.  */
+      if (__glibc_unlikely (set->elems[idx - 1] == elem))
+	return true;
     }

   /* Insert the new element.  */
-- 
2.54.0.rc2.9.ge895506107

