On 9/23/26 3:42 AM, Jakub Jelinek wrote:
Hi!
The following testcase misbehaves, because for std::destroy_at of aggregates
(i.e. CLOBBER with CLOBBER_OBJECT_END) we record during constant evaluation
just a normal CONSTRUCTOR, so for std::is_within_lifetime it appears as if
it is normally within lifetime, even when it is not.
For scalar subobjects and complete objects we use void_node, this patch
uses void_node also for the aggregates (but doesn't do that on
CLOBBER_OBJECT_START and CLOBBER_STORAGE_START, that breaks too much stuff
and the lifetime is just beginning there, not ending). I've just had
to tweak cxx_eval_store_expression so that it is able to handle stores
into previously outside of lifetime subobjects by making those within
lifetime.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
OK.
2026-09-23 Jakub Jelinek <[email protected]>
PR c++/127385
* constexpr.cc (cxx_eval_store_expression): Handle the case of
*valp == void_node like if *valp is NULL.
(cxx_eval_store_expression): For CLOBBER_OBJECT_END or above
store *valp = void_node even for aggregate types.
* g++.dg/cpp26/within-lifetime14.C: New test.
--- a/gcc/cp/constexpr.cc 2026-09-21 14:31:23.000000000 +0200
+++ b/gcc/cp/constexpr.cc 2026-09-22 15:52:09.649180741 +0200
@@ -8635,7 +8635,7 @@ cxx_eval_store_expression (const constex
bool empty_base = false;
while (!refs->is_empty ())
{
- if (*valp == NULL_TREE)
+ if (*valp == NULL_TREE || *valp == void_node)
{
*valp = build_constructor (type, NULL);
CONSTRUCTOR_NO_CLEARING (*valp) = no_zero_init;
@@ -8984,7 +8984,7 @@ cxx_eval_store_expression (const constex
}
else if (TREE_CLOBBER_P (init))
{
- if (AGGREGATE_TYPE_P (type))
+ if (AGGREGATE_TYPE_P (type) && CLOBBER_KIND (init) < CLOBBER_OBJECT_END)
{
if (*valp && TREE_CODE (*valp) == CONSTRUCTOR)
CONSTRUCTOR_ELTS (*valp) = nullptr;
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime14.C 2026-09-22
16:24:27.672059837 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime14.C 2026-09-22
17:57:58.477173326 +0200
@@ -0,0 +1,195 @@
+// P3450R1 - Extend std::is_within_lifetime
+// { dg-do compile { target c++20 } }
+
+#include "../cpp2a/construct_at.h"
+
+namespace std {
+ template <class T>
+ consteval bool
+ is_within_lifetime (const T *p) noexcept
+ {
+ return __builtin_is_within_lifetime (p);
+ }
+
+ template <class T>
+ constexpr void
+ start_lifetime (T &r) noexcept
+ {
+ __builtin_start_lifetime (__builtin_addressof (r));
+ }
+}
+
+struct A { int a, b; };
+struct B { A c, d[2]; };
+union C { B e; int f; };
+struct D { C g; B h; };
+struct E { D i[3]; };
+
+consteval int
+foo ()
+{
+ E j;
+ if (!std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::destroy_at (&j.i[1]);
+ if (std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (j.i[1]);
+ if (!std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (j.i[1].g);
+ if (!std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (j.i[1].h);
+ if (!std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (j.i[1].h.d);
+ std::start_lifetime (j.i[1].h.d[0]);
+ if (std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ j.i[1].h.d[0].a = 42;
+ if (!std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (j.i[1].g.e);
+ std::start_lifetime (j.i[1].g.e.d);
+ std::start_lifetime (j.i[1].g.e.d[1]);
+ if (!std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g.e.d[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ j.i[1].g.e.d[1].b = 42;
+ if (!std::is_within_lifetime (&j.i[1].g.e.d[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ std::start_lifetime (j.i[1].g);
+ if (!std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (j.i[1].g);
+ j.i[1].g.f = 42;
+ if (!std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::destroy_at (&j.i[1]);
+ if (std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ std::construct_at (&j.i[1]);
+ if (!std::is_within_lifetime (&j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&j.i[1].g.f))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&j.i[1].h.d[0].a))
+ return __LINE__;
+ return 0;
+}
+
+static_assert (foo () == 0);
Jakub