https://gcc.gnu.org/g:8b93d6bf0706bdf7f5116c79a051eb31f0fbab22

commit r17-4696-g8b93d6bf0706bdf7f5116c79a051eb31f0fbab22
Author: Steven Sudit <[email protected]>
Date:   Sat Sep 26 13:49:21 2026 -0400

    c++: Don't re-base a folded string builtin on an offset argument [PR127218]
    
    Since r10-5927 (PR c++/80265) cxx_eval_builtin_function_call replaces
    the string arguments of the str/mem builtins with the address of a
    STRING_CST before folding, and for the pointer-returning ones (memchr,
    strchr, strrchr, strstr) it afterwards puts the original argument back
    as the base of the folded POINTER_PLUS_EXPR.  That re-basing is
    unconditional, but the replacement only happens when the evaluated
    argument is a bare ADDR_EXPR (offset 0).  When the argument already
    carries an offset, for instance memchr (a + 1, c, n), the argument is
    passed to fold as-is, fold_builtin_call_array merges the offset into
    the result (a + 10), and the re-basing then rewrites that result to
    (a + 1) + 10, counting the offset twice.
    
      constexpr const char a[] = "abcdefghijabxdefghijaaa";
      static_assert (__builtin_memchr (a + 1, 'a', 22) == a + 10, "");
    
    fails since GCC 10 in every dialect; &a[1] is unaffected because the
    evaluated argument is an ADDR_EXPR.  C++26 exposes this to ordinary
    code: P2738 makes void* -> T* casts constant expressions, so the front
    end now folds char_traits<char>::find on constant haystacks and
    std::string_view::find (c, pos) returns pos too much.
    
    Fix by clearing STRRET when the argument it refers to didn't end up as
    the address of a STRING_CST, so the folded result is re-based only when
    the replacement happened; otherwise it is already relative to the
    evaluated argument.
    
    Assisted-by: Claude Fable 5.1 (Anthropic)
    
            PR c++/127218
    
    gcc/cp/ChangeLog:
    
            * constexpr.cc (cxx_eval_builtin_function_call): Clear STRRET
            when the argument it refers to is not replaced by the address of
            a STRING_CST, so the folded result is re-based on the original
            argument only when that replacement happened.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp0x/constexpr-memchr2.C: New test.
    
    Signed-off-by: Steven Sudit <[email protected]>

Diff:
---
 gcc/cp/constexpr.cc                            |  8 +++++++
 gcc/testsuite/g++.dg/cpp0x/constexpr-memchr2.C | 30 ++++++++++++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index e1b64880a883..31ef5bf1111a 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3077,6 +3077,14 @@ cxx_eval_builtin_function_call (const constexpr_ctx 
*ctx, tree t, tree fun,
            arg = oarg;
        }
 
+      /* If the argument STRRET refers to didn't end up as the address of a
+        STRING_CST, the folded result is already relative to it and must not
+        be re-based on the original argument below.  */
+      if (i == strret - 1
+         && (TREE_CODE (arg) != ADDR_EXPR
+             || TREE_CODE (TREE_OPERAND (arg, 0)) != STRING_CST))
+       strret = 0;
+
       args[i] = arg;
     }
   if (bos)
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-memchr2.C 
b/gcc/testsuite/g++.dg/cpp0x/constexpr-memchr2.C
new file mode 100644
index 000000000000..9f5704631382
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-memchr2.C
@@ -0,0 +1,30 @@
+// PR c++/127218
+// { dg-do compile { target c++11 } }
+// The pointer-returning string builtins must not count a nonzero offset in
+// their first argument twice when the front end folds them.
+
+constexpr const char a[] = "abcdefghijabxdefghijaaa";
+static_assert (__builtin_memchr (a, 'x', sizeof (a) - 1) == a + 12, "");
+static_assert (__builtin_memchr (a + 1, 'a', sizeof (a) - 2) == a + 10, "");
+static_assert (__builtin_memchr (a + 2, 'a', sizeof (a) - 3) == a + 10, "");
+static_assert (__builtin_memchr (a + 1, 'x', sizeof (a) - 2) == a + 12, "");
+static_assert (__builtin_memchr (a + 1, 'q', sizeof (a) - 2) == nullptr, "");
+static_assert (__builtin_memchr (&a[1], 'a', sizeof (a) - 2) == a + 10, "");
+static_assert (__builtin_strchr (a + 1, 'a') == a + 10, "");
+static_assert (__builtin_strchr (a + 1, '\0') == a + 23, "");
+static_assert (__builtin_strrchr (a + 1, 'b') == a + 11, "");
+static_assert (__builtin_strrchr (a + 3, 'a') == a + 22, "");
+static_assert (__builtin_strstr (a + 1, "ab") == a + 10, "");
+static_assert (__builtin_strstr (a + 1, "") == a + 1, "");
+static_assert (__builtin_strstr (a + 1, "zz") == nullptr, "");
+static_assert (__builtin_strlen (a + 1) == 22, "");
+
+constexpr const void *f1 (const char *p, int q, unsigned long n)
+{ return __builtin_memchr (p, q, n); }
+constexpr const char *f2 (const char *p, int q) { return __builtin_strchr (p, 
q); }
+constexpr const char *f3 (const char *p, int q) { return __builtin_strrchr (p, 
q); }
+constexpr const char *f4 (const char *p, const char *q) { return 
__builtin_strstr (p, q); }
+static_assert (f1 (a + 1, 'a', sizeof (a) - 2) == a + 10, "");
+static_assert (f2 (a + 1, 'a') == a + 10, "");
+static_assert (f3 (a + 1, 'b') == a + 11, "");
+static_assert (f4 (a + 1, "ab") == a + 10, "");

Reply via email to