Bug 118285 - [14 Regression] GCC rejects some constexpr std::string usages
Summary: [14 Regression] GCC rejects some constexpr std::string usages
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 15.0
: P2 normal
Target Milestone: 14.3
Assignee: Jason Merrill
URL:
Keywords: rejects-valid
: 103924 (view as bug list)
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2025-01-03 16:34 UTC by Giuseppe D'Angelo
Modified: 2025-09-17 04:05 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2025-01-03 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Giuseppe D'Angelo 2025-01-03 16:34:28 UTC
Hi,

This is a spin-off of PR103924. 

The following testcase is rejected by GCC rejected by GCC but accepted by Clang (with libstdc++):

https://gcc.godbolt.org/z/63Wss3Ej8

#include <string>

constexpr void f(std::initializer_list<std::string>) {}
constexpr bool test()
{
    f({"x"});
    return true;
}

static_assert(test());



<source>:10:19: error: non-constant condition for static assertion
   10 | static_assert(test());
      |               ~~~~^~
In file included from /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/string:54,
                 from <source>:1:
<source>:10:19:   in 'constexpr' expansion of 'test()'
<source>:6:6:   in 'constexpr' expansion of '((std::__cxx11::basic_string<char>*)<anonymous>)->std::__cxx11::basic_string<char>::~basic_string()'
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/basic_string.h:809:19:   in 'constexpr' expansion of '((std::__cxx11::basic_string<char>*)this)->std::__cxx11::basic_string<char>::_M_dispose()'
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/basic_string.h:288:22: error: accessing 'std::__cxx11::basic_string<char>::<unnamed union>::_M_allocated_capacity' member instead of initialized 'std::__cxx11::basic_string<char>::<unnamed union>::_M_local_buf' member in constant expression
  288 |           _M_destroy(_M_allocated_capacity);
      |                      ^~~~~~~~~~~~~~~~~~~~~
Compiler returned: 1





I've been trying to minimize the testcase by removing clutter from basic_string: https://gcc.godbolt.org/z/haxvvr1YK


The fun part is that if one deletes the copy constructor (which is actually never called) the code is accepted by GCC.

That's what makes me think this is a compiler bug.
Comment 1 Giuseppe D'Angelo 2025-01-03 16:36:02 UTC
As noted PR103924, the testcase is actually accepted by GCC 13 (in C++23 mode), so this looks like a regression. https://gcc.godbolt.org/z/zMK3ceE5M
Comment 2 Patrick Palka 2025-01-03 18:11:42 UTC
Started with r14-1705-g2764335bd336f2 "c++: build initializer_list<string> in a loop [PR105838]"
Comment 3 Jakub Jelinek 2025-01-28 15:45:51 UTC
Reduced testcase:
namespace std {
template <typename T> struct initializer_list {
  T *_M_array;
  __SIZE_TYPE__ _M_len;
};
}
struct A {
  char *a;
  union { char b[8]; long c; };
  constexpr A (const char *x) : a(b)
  {
    for (int i = 0; i < 8; ++i)
      b[i] = 0;
  }
  constexpr ~A ()
  {
    if (!foo ())
      bar (c);
  }
  constexpr bool foo ()
  {
    char *x = a;
    if (x == b)
      return true;
    return false;
  }
  constexpr void bar (long) {}
};

constexpr void
baz (std::initializer_list<A>)
{
}

constexpr bool
qux ()
{
  baz ({""});
  return true;
}

static_assert (qux (), "");
Comment 4 Jakub Jelinek 2025-01-28 15:56:23 UTC
With minor tweaks to that:
--- pr118285-3.C	2025-01-28 16:30:15.798692471 +0100
+++ pr118285-5.C	2025-01-28 16:46:09.572904490 +0100
@@ -6,21 +6,21 @@ template <typename T> struct initializer
 }
 struct A {
   char *a;
-  union { char b[8]; long c; };
-  constexpr A (const char *x) : a(b)
+  union U { char b[8]; long c; } u;
+  constexpr A (const char *x) : a(u.b)
   {
     for (int i = 0; i < 8; ++i)
-      b[i] = 0;
+      u.b[i] = i;
   }
   constexpr ~A ()
   {
     if (!foo ())
-      bar (c);
+      bar (u.c);
   }
   constexpr bool foo ()
   {
     char *x = a;
-    if (x == b)
+    if (x == u.b)
       return true;
     return false;
   }

so that it avoids using anonymous union just in case and initializes to nonzero the b stuff, if I cxx_eval_constant_expression the whole *this in A::~A(), I get
{.a=(char *) &D.2743.u.b, .u={.b={0, 1, 2, 3, 4, 5, 6, 7}}}
when evaluated as rvalue and
D.2730[0]
when evaluated as lvalue.
So this is about mismatch with what value the a member has been initialized, so no wonder that foo () returns false.
Comment 5 Jakub Jelinek 2025-01-28 16:32:47 UTC
The extra VAR_DECLs whose addresses are recorded in the initializers are created in
#0  make_node (code=VAR_DECL) at ../../gcc/tree.cc:1244
#1  0x0000000001609ce1 in build_decl (loc=4611686018427387913, code=VAR_DECL, name=<tree 0x0>, type=<record_type 0x7fffea2f37e0 A>) at ../../gcc/tree.cc:5421
#2  0x0000000000860eaf in build_local_temp (type=<record_type 0x7fffea2f37e0 A>) at ../../gcc/cp/tree.cc:562
#3  0x00000000008613e5 in build_aggr_init_expr (type=<record_type 0x7fffea2f37e0 A>, init=<call_expr 0x7fffea2f8500>) at ../../gcc/cp/tree.cc:672
#4  0x0000000000861a09 in build_cplus_new (type=<record_type 0x7fffea2f37e0 A>, init=<call_expr 0x7fffea2f8500>, complain=3) at ../../gcc/cp/tree.cc:724
#5  0x000000000042d0fb in convert_like_internal (convs=0x43c0f40, expr=<call_expr 0x7fffea2f8500>, fn=<tree 0x0>, argnum=0, issue_conversion_warnings=true, c_cast_p=false, 
    nested_p=false, complain=3) at ../../gcc/cp/call.cc:8764
#6  0x000000000042f91c in convert_like (convs=0x43c0f40, expr=<array_ref 0x7fffea301bd0>, fn=<tree 0x0>, argnum=0, issue_conversion_warnings=true, c_cast_p=false, nested_p=false, 
    complain=3) at ../../gcc/cp/call.cc:9322
#7  0x000000000042f98d in convert_like (convs=0x43c0f40, expr=<array_ref 0x7fffea301bd0>, complain=3) at ../../gcc/cp/call.cc:9335
#8  0x0000000000442b05 in perform_implicit_conversion_flags (type=<record_type 0x7fffea2f37e0 A>, expr=<array_ref 0x7fffea301bd0>, complain=3, flags=133)
    at ../../gcc/cp/call.cc:13927
#9  0x000000000049d18c in cxx_eval_vec_init_1 (ctx=0x7fffffffab20, atype=<array_type 0x7fffea2fa0a8>, init=<target_expr 0x7fffea2e4b60>, value_init=false, lval=vc_prvalue, 
    non_constant_p=0x7fffffffd26f, overflow_p=0x7fffffffd26e) at ../../gcc/cp/constexpr.cc:5638
#10 0x000000000049d8ed in cxx_eval_vec_init (ctx=0x7fffffffab20, t=<vec_init_expr 0x7fffea2f1208>, lval=vc_prvalue, non_constant_p=0x7fffffffd26f, overflow_p=0x7fffffffd26e)
    at ../../gcc/cp/constexpr.cc:5726
#11 0x00000000004a82c9 in cxx_eval_constant_expression (ctx=0x7fffffffab20, t=<vec_init_expr 0x7fffea2f1208>, lval=vc_prvalue, non_constant_p=0x7fffffffd26f, 
    overflow_p=0x7fffffffd26e, jump_target=0x0) at ../../gcc/cp/constexpr.cc:8215

now on
namespace std {
template <typename T> struct initializer_list {
  T *_M_array;
  __SIZE_TYPE__ _M_len;
};
}
struct A {
  char *a;
  union { char b[8]; long c; };
  constexpr A (const char *x) : a(b)
  {
    for (int i = 0; i < 8; ++i)
      b[i] = i + x[0];
  }
  constexpr ~A ()
  {
    if (!foo ())
      bar (c);
  }
  constexpr bool foo ()
  {
    char *x = a;
    if (x == b)
      return true;
    return false;
  }
  constexpr void bar (long) {}
};

constexpr void
baz (std::initializer_list<A>)
{
}

constexpr bool
qux ()
{
  baz ({"1", "2"});
  return true;
}

constexpr bool a = qux ();

So, instead of constructing it right into the A array it goes through some temporary.
Comment 6 GCC Commits 2025-01-28 22:21:00 UTC
The trunk branch has been updated by Jason Merrill <jason@gcc.gnu.org>:

https://gcc.gnu.org/g:c3b0b98b3ba57840ab2cfbc9d44d001c1e9167bf

commit r15-7260-gc3b0b98b3ba57840ab2cfbc9d44d001c1e9167bf
Author: Jason Merrill <jason@redhat.com>
Date:   Tue Jan 28 13:11:50 2025 -0500

    c++: constexpr VEC_INIT_EXPR [PR118285]
    
    cxx_eval_vec_init_1 was doing the wrong thing for an array of
    self-referential class type; just evaluating the TARGET_EXPR initializer
    creates a new object that refers to the TARGET_EXPR_SLOT, if we want it to
    refer properly to the initialization target we need to provide it.
    
            PR c++/118285
    
    gcc/cp/ChangeLog:
    
            * constexpr.cc (cxx_eval_vec_init_1): Build INIT_EXPR for
            initializing a class.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp0x/initlist-opt7.C: New test.
Comment 7 Jason Merrill 2025-01-28 22:21:19 UTC
Fixed.
Comment 8 Jason Merrill 2025-01-28 22:23:02 UTC
*** Bug 103924 has been marked as a duplicate of this bug. ***
Comment 9 Jason Merrill 2025-01-28 22:23:32 UTC
Oops, only fixed on trunk so far.
Comment 10 GCC Commits 2025-02-27 17:27:06 UTC
The releases/gcc-14 branch has been updated by Jason Merrill <jason@gcc.gnu.org>:

https://gcc.gnu.org/g:3003d404b436c2dbd5a3a282c25b797d887237d9

commit r14-11346-g3003d404b436c2dbd5a3a282c25b797d887237d9
Author: Jason Merrill <jason@redhat.com>
Date:   Tue Jan 28 13:11:50 2025 -0500

    c++: constexpr VEC_INIT_EXPR [PR118285]
    
    cxx_eval_vec_init_1 was doing the wrong thing for an array of
    self-referential class type; just evaluating the TARGET_EXPR initializer
    creates a new object that refers to the TARGET_EXPR_SLOT, if we want it to
    refer properly to the initialization target we need to provide it.
    
            PR c++/118285
    
    gcc/cp/ChangeLog:
    
            * constexpr.cc (cxx_eval_vec_init_1): Build INIT_EXPR for
            initializing a class.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp0x/initlist-opt7.C: New test.
Comment 11 GCC Commits 2025-02-27 17:27:22 UTC
The releases/gcc-14 branch has been updated by Jason Merrill <jason@gcc.gnu.org>:

https://gcc.gnu.org/g:a72e782528749bf553158039783ce79fa760e3a4

commit r14-11349-ga72e782528749bf553158039783ce79fa760e3a4
Author: Jason Merrill <jason@redhat.com>
Date:   Tue Jan 28 17:39:41 2025 -0500

    c++: disable initializer_list transformation
    
    PRs 118673 and 118285 are wrong-code bugs with the transformation to build
    an initializer_list backing array from a static array of the initializer
    expressions; let's disable that transformation on the GCC 14 branch.
    
            PR c++/118673
            PR c++/118285
    
    gcc/cp/ChangeLog:
    
            * call.cc (convert_like_internal) [ck_list]: Stop using
            maybe_init_list_as_array for GCC 14.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/tree-ssa/initlist-opt5.C: Add xfail.
Comment 12 Jason Merrill 2025-02-27 17:30:22 UTC
Fixed for 14.3/15.