From: Nathaniel Shead <nathanieloshead@gmail.com>
To: gcc-patches@gcc.gnu.org
Cc: Jason Merrill <jason@redhat.com>
Subject: Re: [PATCH] c++: End lifetime of objects in constexpr after destructor call [PR71093]
Date: Mon, 27 Nov 2023 22:08:53 +1100 [thread overview]
Message-ID: <656478c8.170a0220.6b0b.1db1@mx.google.com> (raw)
In-Reply-To: <65444e12.a70a0220.cf10e.14fd@mx.google.com>
Ping for https://gcc.gnu.org/pipermail/gcc-patches/2023-November/635071.html.
On Fri, Nov 03, 2023 at 12:34:06PM +1100, Nathaniel Shead wrote:
> Oh, this also fixes PR102284 and its other linked PRs (apart from
> fields); I forgot to note that in the commit.
>
> On Fri, Nov 03, 2023 at 12:18:29PM +1100, Nathaniel Shead wrote:
> > Bootstrapped and regtested on x86-64_pc_linux_gnu.
> >
> > I'm not entirely sure if the change I made to have destructors clobber with
> > CLOBBER_EOL instead of CLOBBER_UNDEF is appropriate, but nothing seemed to have
> > broken by doing this and I wasn't able to find anything else that really
> > depended on this distinction other than a warning pass. Otherwise I could
> > experiment with a new clobber kind for destructor calls.
> >
> > -- >8 --
> >
> > This patch adds checks for using objects after they've been manually
> > destroyed via explicit destructor call. Currently this is only
> > implemented for 'top-level' objects; FIELD_DECLs and individual elements
> > of arrays will need a lot more work to track correctly and are left for
> > a future patch.
> >
> > The other limitation is that destruction of parameter objects is checked
> > too 'early', happening at the end of the function call rather than the
> > end of the owning full-expression as they should be for consistency;
> > see cpp2a/constexpr-lifetime2.C. This is because I wasn't able to find a
> > good way to link the constructed parameter declarations with the
> > variable declarations that are actually destroyed later on to propagate
> > their lifetime status, so I'm leaving this for a later patch.
> >
> > PR c++/71093
> >
> > gcc/cp/ChangeLog:
> >
> > * call.cc (build_trivial_dtor_call): Mark pseudo-destructors as
> > ending lifetime.
> > * constexpr.cc (constexpr_global_ctx::get_value_ptr): Don't
> > return NULL_TREE for objects we're initializing.
> > (constexpr_global_ctx::destroy_value): Rename from remove_value.
> > Only mark real variables as outside lifetime.
> > (constexpr_global_ctx::clear_value): New function.
> > (destroy_value_checked): New function.
> > (cxx_eval_call_expression): Defer complaining about non-constant
> > arg0 for operator delete. Use remove_value_safe.
> > (cxx_fold_indirect_ref_1): Handle conversion to 'as base' type.
> > (outside_lifetime_error): Include name of object we're
> > accessing.
> > (cxx_eval_store_expression): Handle clobbers. Improve error
> > messages.
> > (cxx_eval_constant_expression): Use remove_value_safe. Clear
> > bind variables before entering body.
> > * decl.cc (build_clobber_this): Mark destructors as ending
> > lifetime.
> > (start_preparsed_function): Pass false to build_clobber_this.
> > (begin_destructor_body): Pass true to build_clobber_this.
> >
> > gcc/testsuite/ChangeLog:
> >
> > * g++.dg/cpp1y/constexpr-lifetime1.C: Improve error message.
> > * g++.dg/cpp1y/constexpr-lifetime2.C: Likewise.
> > * g++.dg/cpp1y/constexpr-lifetime3.C: Likewise.
> > * g++.dg/cpp1y/constexpr-lifetime4.C: Likewise.
> > * g++.dg/cpp2a/bitfield2.C: Likewise.
> > * g++.dg/cpp2a/constexpr-new3.C: Likewise. New check.
> > * g++.dg/cpp1y/constexpr-lifetime7.C: New test.
> > * g++.dg/cpp2a/constexpr-lifetime1.C: New test.
> > * g++.dg/cpp2a/constexpr-lifetime2.C: New test.
> >
> > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> > ---
> > gcc/cp/call.cc | 2 +-
> > gcc/cp/constexpr.cc | 149 +++++++++++++++---
> > gcc/cp/decl.cc | 10 +-
> > .../g++.dg/cpp1y/constexpr-lifetime1.C | 2 +-
> > .../g++.dg/cpp1y/constexpr-lifetime2.C | 2 +-
> > .../g++.dg/cpp1y/constexpr-lifetime3.C | 2 +-
> > .../g++.dg/cpp1y/constexpr-lifetime4.C | 2 +-
> > .../g++.dg/cpp1y/constexpr-lifetime7.C | 93 +++++++++++
> > gcc/testsuite/g++.dg/cpp2a/bitfield2.C | 2 +-
> > .../g++.dg/cpp2a/constexpr-lifetime1.C | 21 +++
> > .../g++.dg/cpp2a/constexpr-lifetime2.C | 23 +++
> > gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C | 17 +-
> > 12 files changed, 292 insertions(+), 33 deletions(-)
> > create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime7.C
> > create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime1.C
> > create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime2.C
> >
> > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> > index 2eb54b5b6ed..e5e9c6c44f8 100644
> > --- a/gcc/cp/call.cc
> > +++ b/gcc/cp/call.cc
> > @@ -9682,7 +9682,7 @@ build_trivial_dtor_call (tree instance, bool no_ptr_deref)
> > }
> >
> > /* A trivial destructor should still clobber the object. */
> > - tree clobber = build_clobber (TREE_TYPE (instance));
> > + tree clobber = build_clobber (TREE_TYPE (instance), CLOBBER_EOL);
> > return build2 (MODIFY_EXPR, void_type_node,
> > instance, clobber);
> > }
> > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > index c05760e6789..4f0f590c38a 100644
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -1193,13 +1193,20 @@ public:
> > return *p;
> > return NULL_TREE;
> > }
> > - tree *get_value_ptr (tree t)
> > + tree *get_value_ptr (tree t, bool initializing)
> > {
> > if (modifiable && !modifiable->contains (t))
> > return nullptr;
> > if (tree *p = values.get (t))
> > - if (*p != void_node)
> > - return p;
> > + {
> > + if (*p != void_node)
> > + return p;
> > + else if (initializing)
> > + {
> > + *p = NULL_TREE;
> > + return p;
> > + }
> > + }
> > return nullptr;
> > }
> > void put_value (tree t, tree v)
> > @@ -1208,13 +1215,20 @@ public:
> > if (!already_in_map && modifiable)
> > modifiable->add (t);
> > }
> > - void remove_value (tree t)
> > + void destroy_value (tree t)
> > {
> > - if (DECL_P (t))
> > + if ((TREE_CODE (t) == VAR_DECL
> > + || TREE_CODE (t) == PARM_DECL
> > + || TREE_CODE (t) == RESULT_DECL)
> > + && DECL_NAME (t) != in_charge_identifier)
> > values.put (t, void_node);
> > else
> > values.remove (t);
> > }
> > + void clear_value (tree t)
> > + {
> > + values.remove (t);
> > + }
> > };
> >
> > /* Helper class for constexpr_global_ctx. In some cases we want to avoid
> > @@ -1238,7 +1252,7 @@ public:
> > ~modifiable_tracker ()
> > {
> > for (tree t: set)
> > - global->remove_value (t);
> > + global->clear_value (t);
> > global->modifiable = nullptr;
> > }
> > };
> > @@ -1278,6 +1292,40 @@ struct constexpr_ctx {
> > mce_value manifestly_const_eval;
> > };
> >
> > +/* Remove T from the global values map, checking for attempts to destroy
> > + a value that has already finished its lifetime. */
> > +
> > +static void
> > +destroy_value_checked (const constexpr_ctx* ctx, tree t, bool *non_constant_p)
> > +{
> > + if (t == error_mark_node || TREE_TYPE (t) == error_mark_node)
> > + return;
> > +
> > + /* Don't error again here if we've already reported a problem. */
> > + if (!*non_constant_p
> > + && DECL_P (t)
> > + /* Non-trivial destructors have their lifetimes ended explicitly
> > + with a clobber, so don't worry about it here. */
> > + && (!TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (t))
> > + /* ...except parameters are remapped in cxx_eval_call_expression,
> > + and the destructor call during cleanup won't be able to tell that
> > + this value has already been destroyed, so complain now. This is
> > + not quite unobservable, but is extremely unlikely to crop up in
> > + practice; see g++.dg/cpp2a/constexpr-lifetime2.C. */
> > + || TREE_CODE (t) == PARM_DECL)
> > + && ctx->global->is_outside_lifetime (t))
> > + {
> > + if (!ctx->quiet)
> > + {
> > + auto_diagnostic_group d;
> > + error ("destroying %qE outside its lifetime", t);
> > + inform (DECL_SOURCE_LOCATION (t), "declared here");
> > + }
> > + *non_constant_p = true;
> > + }
> > + ctx->global->destroy_value (t);
> > +}
> > +
> > /* This internal flag controls whether we should avoid doing anything during
> > constexpr evaluation that would cause extra DECL_UID generation, such as
> > template instantiation and function body copying. */
> > @@ -2806,6 +2854,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> > && (CALL_FROM_NEW_OR_DELETE_P (t)
> > || is_std_allocator_allocate (ctx->call)))
> > {
> > + const bool new_op_p = IDENTIFIER_NEW_OP_P (DECL_NAME (fun));
> > const int nargs = call_expr_nargs (t);
> > tree arg0 = NULL_TREE;
> > for (int i = 0; i < nargs; ++i)
> > @@ -2813,12 +2862,15 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> > tree arg = CALL_EXPR_ARG (t, i);
> > arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
> > non_constant_p, overflow_p);
> > - VERIFY_CONSTANT (arg);
> > + /* Deleting a non-constant pointer has a better error message
> > + below. */
> > + if (new_op_p || i != 0)
> > + VERIFY_CONSTANT (arg);
> > if (i == 0)
> > arg0 = arg;
> > }
> > gcc_assert (arg0);
> > - if (IDENTIFIER_NEW_OP_P (DECL_NAME (fun)))
> > + if (new_op_p)
> > {
> > tree type = build_array_type_nelts (char_type_node,
> > tree_to_uhwi (arg0));
> > @@ -2867,7 +2919,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> > return t;
> > }
> > DECL_NAME (var) = heap_deleted_identifier;
> > - ctx->global->remove_value (var);
> > + ctx->global->destroy_value (var);
> > ctx->global->heap_dealloc_count++;
> > return void_node;
> > }
> > @@ -2890,7 +2942,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> > return t;
> > }
> > DECL_NAME (var) = heap_deleted_identifier;
> > - ctx->global->remove_value (var);
> > + ctx->global->destroy_value (var);
> > ctx->global->heap_dealloc_count++;
> > return void_node;
> > }
> > @@ -3242,9 +3294,9 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> > non_constant_p, overflow_p);
> >
> > /* Remove the parms/result from the values map. */
> > - ctx->global->remove_value (res);
> > + destroy_value_checked (ctx, res, non_constant_p);
> > for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
> > - ctx->global->remove_value (parm);
> > + destroy_value_checked (ctx, parm, non_constant_p);
> >
> > /* Free any parameter CONSTRUCTORs we aren't returning directly. */
> > while (!ctors->is_empty ())
> > @@ -5644,6 +5696,10 @@ cxx_fold_indirect_ref_1 (const constexpr_ctx *ctx, location_t loc, tree type,
> > }
> > }
> >
> > + /* Handle conversion to "as base" type. */
> > + if (CLASSTYPE_AS_BASE (optype) == type)
> > + return op;
> > +
> > /* Handle conversion to an empty base class, which is represented with a
> > NOP_EXPR. Do this before spelunking into the non-empty subobjects,
> > which is likely to be a waste of time (109678). */
> > @@ -5895,7 +5951,7 @@ outside_lifetime_error (location_t loc, tree r)
> > }
> > else
> > {
> > - error_at (loc, "accessing object outside its lifetime");
> > + error_at (loc, "accessing %qE outside its lifetime", r);
> > inform (DECL_SOURCE_LOCATION (r), "declared here");
> > }
> > }
> > @@ -6112,8 +6168,10 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> > constexpr_ctx new_ctx = *ctx;
> >
> > tree init = TREE_OPERAND (t, 1);
> > - if (TREE_CLOBBER_P (init))
> > - /* Just ignore clobbers. */
> > +
> > + if (TREE_CLOBBER_P (init)
> > + && CLOBBER_KIND (init) != CLOBBER_EOL)
> > + /* Only handle clobbers ending the lifetime of storage. */
> > return void_node;
> >
> > /* First we figure out where we're storing to. */
> > @@ -6123,7 +6181,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> >
> > tree type = TREE_TYPE (target);
> > bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR;
> > - if (preeval)
> > + if (preeval && !TREE_CLOBBER_P (init))
> > {
> > /* Evaluate the value to be stored without knowing what object it will be
> > stored in, so that any side-effects happen first. */
> > @@ -6231,11 +6289,18 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> > && const_object_being_modified == NULL_TREE)
> > const_object_being_modified = object;
> >
> > + if (DECL_P (object)
> > + && TREE_CLOBBER_P (init)
> > + && DECL_NAME (object) == heap_deleted_identifier)
> > + /* Ignore clobbers of deleted allocations for now; we'll get a better error
> > + message later when operator delete is called. */
> > + return void_node;
> > +
> > /* And then find/build up our initializer for the path to the subobject
> > we're initializing. */
> > tree *valp;
> > if (DECL_P (object))
> > - valp = ctx->global->get_value_ptr (object);
> > + valp = ctx->global->get_value_ptr (object, TREE_CODE (t) == INIT_EXPR);
> > else
> > valp = NULL;
> > if (!valp)
> > @@ -6243,10 +6308,45 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> > /* A constant-expression cannot modify objects from outside the
> > constant-expression. */
> > if (!ctx->quiet)
> > - error ("modification of %qE is not a constant expression", object);
> > + {
> > + auto_diagnostic_group d;
> > + if (DECL_P (object) && DECL_NAME (object) == heap_deleted_identifier)
> > + {
> > + error ("modification of allocated storage after deallocation "
> > + "is not a constant expression");
> > + inform (DECL_SOURCE_LOCATION (object), "allocated here");
> > + }
> > + else if (DECL_P (object) && ctx->global->is_outside_lifetime (object))
> > + {
> > + if (TREE_CLOBBER_P (init))
> > + error ("destroying %qE outside its lifetime", object);
> > + else
> > + error ("modification of %qE outside its lifetime "
> > + "is not a constant expression", object);
> > + inform (DECL_SOURCE_LOCATION (object), "declared here");
> > + }
> > + else
> > + {
> > + if (TREE_CLOBBER_P (init))
> > + error ("destroying %qE from outside current evaluation "
> > + "is not a constant expression", object);
> > + else
> > + error ("modification of %qE from outside current evaluation "
> > + "is not a constant expression", object);
> > + }
> > + }
> > *non_constant_p = true;
> > return t;
> > }
> > +
> > + /* Handle explicit end-of-lifetime. */
> > + if (TREE_CLOBBER_P (init))
> > + {
> > + if (refs->is_empty ())
> > + ctx->global->destroy_value (object);
> > + return void_node;
> > + }
> > +
> > type = TREE_TYPE (object);
> > bool no_zero_init = true;
> >
> > @@ -6520,7 +6620,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> > /* The hash table might have moved since the get earlier, and the
> > initializer might have mutated the underlying CONSTRUCTORs, so we must
> > recompute VALP. */
> > - valp = ctx->global->get_value_ptr (object);
> > + valp = ctx->global->get_value_ptr (object, TREE_CODE (t) == INIT_EXPR);
> > for (unsigned i = 0; i < vec_safe_length (indexes); i++)
> > {
> > ctors[i] = valp;
> > @@ -7631,7 +7731,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > /* Forget SAVE_EXPRs and TARGET_EXPRs created by this
> > full-expression. */
> > for (tree save_expr : save_exprs)
> > - ctx->global->remove_value (save_expr);
> > + destroy_value_checked (ctx, save_expr, non_constant_p);
> > }
> > break;
> >
> > @@ -8184,13 +8284,18 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > non_constant_p, overflow_p, jump_target);
> >
> > case BIND_EXPR:
> > + /* Pre-emptively clear the vars declared by this BIND_EXPR from the value
> > + map, so that when checking whether they're already destroyed later we
> > + don't get confused by remnants of previous calls. */
> > + for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
> > + ctx->global->clear_value (decl);
> > r = cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
> > lval,
> > non_constant_p, overflow_p,
> > jump_target);
> > for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
> > - ctx->global->remove_value (decl);
> > - return r;
> > + destroy_value_checked (ctx, decl, non_constant_p);
> > + break;
> >
> > case PREINCREMENT_EXPR:
> > case POSTINCREMENT_EXPR:
> > diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> > index 16af59de696..43f6379befb 100644
> > --- a/gcc/cp/decl.cc
> > +++ b/gcc/cp/decl.cc
> > @@ -17313,7 +17313,7 @@ implicit_default_ctor_p (tree fn)
> > storage is dead when we enter the constructor or leave the destructor. */
> >
> > static tree
> > -build_clobber_this ()
> > +build_clobber_this (bool is_destructor)
> > {
> > /* Clobbering an empty base is pointless, and harmful if its one byte
> > TYPE_SIZE overlays real data. */
> > @@ -17329,7 +17329,8 @@ build_clobber_this ()
> > if (!vbases)
> > ctype = CLASSTYPE_AS_BASE (ctype);
> >
> > - tree clobber = build_clobber (ctype);
> > + enum clobber_kind kind = is_destructor ? CLOBBER_EOL : CLOBBER_UNDEF;
> > + tree clobber = build_clobber (ctype, kind);
> >
> > tree thisref = current_class_ref;
> > if (ctype != current_class_type)
> > @@ -17750,7 +17751,7 @@ start_preparsed_function (tree decl1, tree attrs, int flags)
> > because part of the initialization might happen before we enter the
> > constructor, via AGGR_INIT_ZERO_FIRST (c++/68006). */
> > && !implicit_default_ctor_p (decl1))
> > - finish_expr_stmt (build_clobber_this ());
> > + finish_expr_stmt (build_clobber_this (/*is_destructor=*/false));
> >
> > if (!processing_template_decl
> > && DECL_CONSTRUCTOR_P (decl1)
> > @@ -17973,7 +17974,8 @@ begin_destructor_body (void)
> > finish_decl_cleanup (NULL_TREE, stmt);
> > }
> > else
> > - finish_decl_cleanup (NULL_TREE, build_clobber_this ());
> > + finish_decl_cleanup (NULL_TREE,
> > + build_clobber_this (/*is_destructor=*/true));
> > }
> >
> > /* And insert cleanups for our bases and members so that they
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > index 43aa7c974c1..3fda29e0cc2 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > @@ -10,4 +10,4 @@ constexpr const int& test() {
> > auto local = S{}; // { dg-message "note: declared here" }
> > return local.get();
> > }
> > -constexpr int x = test(); // { dg-error "accessing object outside its lifetime" }
> > +constexpr int x = test(); // { dg-error "accessing .local. outside its lifetime" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > index 2f5ae8db6d5..d82ba5c8b73 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > @@ -8,7 +8,7 @@ struct S {
> >
> > constexpr int error() {
> > const auto& local = S{}.get(); // { dg-message "note: declared here" }
> > - return local; // { dg-error "accessing object outside its lifetime" }
> > + return local; // { dg-error "accessing '\[^'\]+' outside its lifetime" }
> > }
> > constexpr int x = error(); // { dg-message "in .constexpr. expansion" }
> >
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > index 53785521d05..67e9b91c723 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > @@ -7,7 +7,7 @@ constexpr int f(int i) {
> > int j = 123; // { dg-message "note: declared here" }
> > p = &j;
> > }
> > - return *p; // { dg-error "accessing object outside its lifetime" }
> > + return *p; // { dg-error "accessing 'j' outside its lifetime" }
> > }
> >
> > constexpr int i = f(0); // { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > index 181a1201663..6f0d749dcf2 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > @@ -5,7 +5,7 @@ constexpr const double& test() {
> > return local;
> > }
> >
> > -static_assert(test() == 3.0, ""); // { dg-error "constant|accessing object outside its lifetime" }
> > +static_assert(test() == 3.0, ""); // { dg-error "constant|accessing '\[^'\]+' outside its lifetime" }
> >
> > // no deference, shouldn't error
> > static_assert((test(), true), "");
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime7.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime7.C
> > new file mode 100644
> > index 00000000000..4148f42f7be
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime7.C
> > @@ -0,0 +1,93 @@
> > +// PR c++/71093
> > +// { dg-do compile { target c++14 } }
> > +
> > +constexpr int f (const int *p)
> > +{
> > + typedef int T;
> > + p->~T (); // { dg-error "destroying" }
> > + return *p;
> > +}
> > +
> > +constexpr int i = 0;
> > +constexpr int j = f (&i);
> > +
> > +
> > +template <typename T>
> > +constexpr bool test_access() {
> > + T x {};
> > + x.~T();
> > + T y = x; // { dg-error "lifetime" }
> > + return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_modification() {
> > + T x {};
> > + x.~T();
> > + x = T(); // { dg-error "lifetime" }
> > + return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_scope() {
> > + {
> > + T x {};
> > + x.~T();
> > + } // { dg-error "destroying" }
> > + return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_destroy_temp() {
> > + T{}.~T(); // { dg-error "destroying" }
> > + return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_parameter(T t) {
> > + // note: error message occurs at point of call
> > + t.~T();
> > + return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr void test_bindings_impl(int n) {
> > + if (n == 0) return;
> > + T a {};
> > + if (n == 1) return;
> > + T b {};
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_bindings() {
> > + test_bindings_impl<T>(1);
> > + test_bindings_impl<T>(0);
> > + test_bindings_impl<T>(2);
> > + return true;
> > +}
> > +
> > +constexpr bool i1 = test_access<int>(); // { dg-message "in .constexpr." }
> > +constexpr bool i2 = test_modification<int>(); // { dg-message "in .constexpr." }
> > +constexpr bool i3 = test_scope<int>(); // { dg-message "in .constexpr." }
> > +constexpr bool i4 = test_destroy_temp<int>(); // { dg-message "in .constexpr." "" { xfail *-*-* } }
> > +constexpr bool i5 = test_parameter(int{}); // { dg-error "destroying" }
> > +constexpr bool i6 = test_bindings<int>();
> > +
> > +struct Trivial { int x; };
> > +constexpr bool t1 = test_access<Trivial>(); // { dg-message "in .constexpr." }
> > +constexpr bool t2 = test_modification<Trivial>(); // { dg-message "in .constexpr." }
> > +constexpr bool t3 = test_scope<Trivial>(); // { dg-message "in .constexpr." }
> > +constexpr bool t4 = test_destroy_temp<Trivial>(); // { dg-message "in .constexpr." }
> > +constexpr bool t5 = test_parameter(Trivial{}); // { dg-error "destroying" }
> > +constexpr bool t6 = test_bindings<Trivial>();
> > +
> > +#if __cplusplus >= 202002L
> > +struct NonTrivial { int x; constexpr ~NonTrivial() {} }; // { dg-error "destroying" "" { target c++20 } }
> > +constexpr bool n1 = test_access<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n2 = test_modification<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n3 = test_scope<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n4 = test_destroy_temp<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n5 = test_parameter(NonTrivial{}); // { dg-error "destroying" "" { target c++20 } }
> > +constexpr bool n6 = test_bindings<NonTrivial>();
> > +#endif
> > +
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/bitfield2.C b/gcc/testsuite/g++.dg/cpp2a/bitfield2.C
> > index dcb424fc8f6..885d4f0e26d 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/bitfield2.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/bitfield2.C
> > @@ -13,7 +13,7 @@ template <bool V, int W>
> > struct U {
> > int j : W = 7; // { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
> > int k : W { 8 }; // { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
> > - int l : V ? 7 : a = 3; // { dg-error "modification of .a. is not a constant expression" }
> > + int l : V ? 7 : a = 3; // { dg-error "modification of .a. from outside current evaluation is not a constant expression" }
> > // { dg-error "width not an integer constant" "" { target *-*-* } .-1 }
> > int m : (V ? W : b) = 9; // { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
> > // { dg-error "zero width for bit-field" "" { target *-*-* } .-1 }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime1.C
> > new file mode 100644
> > index 00000000000..36163844eca
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime1.C
> > @@ -0,0 +1,21 @@
> > +// { dg-do compile { target c++20 } }
> > +
> > +#include "construct_at.h"
> > +
> > +struct S { int x; };
> > +constexpr int f() {
> > + S s;
> > + s.~S();
> > + std::construct_at(&s, 5);
> > + return s.x;
> > +}
> > +static_assert(f() == 5);
> > +
> > +struct T { int x; constexpr ~T() {} };
> > +constexpr int g() {
> > + T t;
> > + t.~T();
> > + std::construct_at(&t, 12);
> > + return t.x;
> > +}
> > +static_assert(g() == 12);
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime2.C
> > new file mode 100644
> > index 00000000000..56cc9e3c1c8
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-lifetime2.C
> > @@ -0,0 +1,23 @@
> > +// { dg-do compile { target c++20 } }
> > +
> > +#include "construct_at.h"
> > +
> > +struct S { int x; };
> > +
> > +constexpr bool foo(S s, S*& p) {
> > + p = &s;
> > + s.~S();
> > + return true;
> > +}
> > +
> > +constexpr bool bar() {
> > + // This is, strictly speaking, implementation-defined behaviour;
> > + // see [expr.call] p6. However, in all other cases we destroy
> > + // at the end of the full-expression, so the below should be fixed.
> > + S* p;
> > + foo(S{}, p), std::construct_at(p); // { dg-bogus "destroying" "" { xfail *-*-* } }
> > +
> > + return true;
> > +}
> > +
> > +constexpr bool x = bar();
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> > index 3ba440fec53..5d9f192507b 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> > @@ -34,7 +34,7 @@ constexpr auto v3 = f3 (); // { dg-message "in 'constexpr' expansion of" }
> > constexpr bool
> > f4 (int *p)
> > {
> > - delete p; // { dg-error "deallocation of storage that was not previously allocated" }
> > + delete p; // { dg-error "destroying 'q' from outside current evaluation" }
> > return false;
> > }
> >
> > @@ -70,3 +70,18 @@ f7 ()
> > }
> >
> > constexpr auto v7 = f7 ();
> > +
> > +constexpr bool
> > +f8_impl (int *p)
> > +{
> > + delete p; // { dg-error "deallocation of storage that was not previously allocated" }
> > + return false;
> > +}
> > +
> > +constexpr bool
> > +f8 ()
> > +{
> > + int q = 0;
> > + return f8_impl (&q);
> > +}
> > +constexpr auto v8 = f8 (); // { dg-message "in 'constexpr' expansion of" }
> > --
> > 2.42.0
> >
next prev parent reply other threads:[~2023-11-27 11:08 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-11-03 1:18 Nathaniel Shead
2023-11-03 1:34 ` Nathaniel Shead
2023-11-27 11:08 ` Nathaniel Shead [this message]
2023-12-09 20:12 ` Jason Merrill
2023-12-10 10:22 ` Richard Biener
2023-12-10 11:21 ` Alexander Monakov
2023-12-10 15:58 ` Richard Biener
2023-12-10 18:34 ` Jason Merrill
2023-12-11 8:02 ` Richard Biener
2023-12-11 19:12 ` Jason Merrill
2023-12-11 19:17 ` Richard Biener
2023-12-11 19:21 ` Marek Polacek
2023-12-11 22:00 ` Jason Merrill
2023-12-11 22:22 ` Marek Polacek
2023-12-11 23:03 ` Jakub Jelinek
2023-12-12 11:13 ` Alexander Monakov
2023-12-12 11:15 ` Jakub Jelinek
2023-12-12 15:24 ` Jason Merrill
2023-12-12 17:50 ` Jason Merrill
2023-12-13 4:40 ` Jason Merrill
2023-12-13 16:47 ` [pushed 1/4] c++: copy location to AGGR_INIT_EXPR Jason Merrill
2023-12-13 16:47 ` [pushed 2/4] c++: constant direct-initialization [PR108243] Jason Merrill
2023-12-13 16:47 ` [pushed 3/4] c++: fix in-charge parm in constexpr Jason Merrill
2023-12-13 16:47 ` [pushed 4/4] c++: End lifetime of objects in constexpr after destructor call [PR71093] Jason Merrill
2023-12-13 18:05 ` [pushed 1/4] c++: copy location to AGGR_INIT_EXPR Patrick Palka
2023-12-13 20:06 ` [pushed] c++: TARGET_EXPR location in default arg [PR96997] Jason Merrill
2023-12-14 0:00 ` [pushed 1/4] c++: copy location to AGGR_INIT_EXPR Marek Polacek
2023-12-14 1:38 ` Jason Merrill
2023-12-14 14:25 ` Marek Polacek
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=656478c8.170a0220.6b0b.1db1@mx.google.com \
--to=nathanieloshead@gmail.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=jason@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).