https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127218
Bug ID: 127218
Summary: [13/14/15/16/17 Regression] constexpr
memchr/strchr/strrchr/strstr count a nonzero offset in
the first argument twice
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: stevensudit at gmail dot com
Target Milestone: ---
Created attachment 65501
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65501&action=edit
Reduced testcase (proposed g++.dg/cpp0x/constexpr-memchr2.C)
The C++ front end's constant evaluator returns the wrong pointer from
__builtin_memchr, __builtin_strchr, __builtin_strrchr and __builtin_strstr
whenever the first argument is a constant array plus a nonzero offset: the
offset is added twice.
constexpr const char a[] = "abcdefghijabxdefghijaaa";
static_assert (__builtin_memchr (a, 'x', sizeof (a) - 1) == a + 12, "");
// OK
static_assert (__builtin_memchr (a + 1, 'a', sizeof (a) - 2) == a + 10, "");
// fails: a + 11
static_assert (__builtin_memchr (a + 2, 'a', sizeof (a) - 3) == a + 10, "");
// fails: a + 12
static_assert (__builtin_memchr (&a[1], 'a', sizeof (a) - 2) == a + 10, "");
// OK (ADDR_EXPR)
static_assert (__builtin_strchr (a + 1, 'a') == a + 10, "");
// fails
static_assert (__builtin_strrchr (a + 1, 'b') == a + 11, "");
// fails
static_assert (__builtin_strstr (a + 1, "ab") == a + 10, "");
// fails
static_assert (__builtin_strlen (a + 1) == 22, "");
// OK
Fails identically with -std=c++11 through -std=c++26; the "+0" forms, the
&a[1] form, the nullptr results and the length-returning builtins are
correct. The attached constexpr-memchr2.C has 18 static_asserts of which
the 13 with an offset first argument fail:
$ g++ -std=c++11 -fsyntax-only constexpr-memchr2.C
constexpr-memchr2.C:8:62: error: static assertion failed
constexpr-memchr2.C:9:62: error: static assertion failed
constexpr-memchr2.C:10:62: error: static assertion failed
constexpr-memchr2.C:13:46: error: static assertion failed
constexpr-memchr2.C:14:47: error: static assertion failed
constexpr-memchr2.C:15:47: error: static assertion failed
constexpr-memchr2.C:16:47: error: static assertion failed
constexpr-memchr2.C:17:47: error: static assertion failed
constexpr-memchr2.C:18:45: error: static assertion failed
constexpr-memchr2.C:27:48: error: static assertion failed
constexpr-memchr2.C:28:32: error: static assertion failed
constexpr-memchr2.C:29:32: error: static assertion failed
constexpr-memchr2.C:30:33: error: static assertion failed
Compiler Explorer x86-64 builds: gcc 9.5 accepts the whole file; gcc 10.1,
13.4, 14.4, 15.2, 16.2 and trunk (2026-09-04) reject those 13 lines.
Locally reproduced with Ubuntu gcc 13.3.0 and 15.2.0 (same 13 lines).
clang 23 accepts the memchr, strchr and strlen lines (it does not
constant-evaluate strrchr or strstr at all, so those are not a cross-check).
Compiler Explorer gcc 16.2:
Target: x86_64-linux-gnu
Configured with: ../gcc-16.2.0/configure
--prefix=/opt/compiler-explorer/gcc-build/staging --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu --disable-bootstrap
--enable-multiarch --with-abi=m64 --with-multilib-list=m32,m64,mx32
--enable-multilib --enable-clocale=gnu
--enable-languages=c,c++,fortran,ada,objc,obj-c++,go,d,m2,rust,cobol,algol68
--enable-ld=yes --enable-gold=yes --enable-libstdcxx-time=yes
--enable-linker-build-id --enable-lto --enable-plugins --enable-threads=posix
--with-pkgversion=Compiler-Explorer-Build-gcc--binutils-2.44
Thread model: posix
gcc version 16.2.0 (Compiler-Explorer-Build-gcc--binutils-2.44)
This became user-visible in C++26 mode with libstdc++: P2738 makes void* ->
T* casts constant expressions, so the front end now folds
char_traits<char>::find on a constant haystack instead of leaving memchr to
the middle end, and std::string_view::find (c, pos) returns pos too much:
#include <cstdio>
#include <string_view>
int main ()
{
constexpr std::string_view s = "abcdefghijabxdefghijaaa";
static constexpr const char a[] = "abcdefghijabxdefghijaaa";
const char *p = static_cast<const char *> (__builtin_memchr (a + 1, 'a',
sizeof (a) - 2));
std::printf ("__builtin_memchr (a + 1, 'a', n) - a = %td (expected 10)\n",
p - a);
std::printf ("s.find ('a', 1) = %zu (expected 10)\n", s.find ('a', 1));
}
g++ 16.2 -std=c++26 -O0: 11 / 10 (the cast is folded by the front end)
g++ 16.2 -std=c++26 -O2: 11 / 11 (cp_fold folds the inlined find too)
g++ 16.2 -std=c++23 -O2: 10 / 10
g++ 9.5 -std=c++17 -O2: 10 / 10
(Compiler Explorer, 2026-09-04; trunk and Ubuntu 15.2.0 behave like 16.2.)
Cause
Introduced by r10-5927-g69dc042f91c70 (PR c++/80265, "constexpr
__builtin_mem*"). cxx_eval_builtin_function_call replaces string arguments
of the str/mem builtins with the address of a STRING_CST before folding, and
for the pointer-returning builtins (strret) afterwards puts the original
first argument back as the base of the folded POINTER_PLUS_EXPR
(gcc/cp/constexpr.cc:3055 on current trunk):
if (strret)
{
/* memchr returns a pointer into the first argument, but we replaced the
argument above with a STRING_CST; put it back it now. */
tree op = CALL_EXPR_ARG (t, strret-1);
STRIP_NOPS (new_call);
if (TREE_CODE (new_call) == POINTER_PLUS_EXPR)
TREE_OPERAND (new_call, 0) = op;
else if (TREE_CODE (new_call) == ADDR_EXPR)
new_call = op;
}
The replacement only happens when the evaluated argument is a bare ADDR_EXPR
(offset 0); an argument such as a + 1 is passed to fold unchanged,
fold_builtin_call_array merges the offset into the result (a + 10), and the
unconditional re-basing then turns that into (a + 1) + 10. &a[1] is
unaffected because the evaluated argument is an ADDR_EXPR, so the
replacement path is taken and the re-basing is correct.
The existing tests (constexpr-memchr.C, constexpr-strchr.C,
constexpr-strstr.C) never pass an offset first argument, which is why this
was not caught.
Fix
Remember which argument was replaced (strret_op) and re-base only in that
case. Patch with a testsuite addition attached; it applies to trunk
(r17-3939-g08794c636095, 2026-09-04) and to 16.2.0. The new test passes with
-std=c++11/14/17/20/23/26 on a patched 16.2.0 and fails on the 13 lines
above on stock 16.2.0. A full bootstrap and regression test of the patch
have not been run.