[PATCH] c++: End lifetime of objects in constexpr after destructor call [PR71093]
Nathaniel Shead
nathanieloshead@gmail.com
Fri Nov 3 01:34:06 GMT 2023
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
>
More information about the Gcc-patches
mailing list