From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-oi1-x22f.google.com (mail-oi1-x22f.google.com [IPv6:2607:f8b0:4864:20::22f]) by sourceware.org (Postfix) with ESMTPS id 1FC3B3858D37 for ; Fri, 3 Nov 2023 01:34:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1FC3B3858D37 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 1FC3B3858D37 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::22f ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1698975262; cv=none; b=wS1pwK4uqC0LD+usK/KdzuVLCJHzYx21kH7HSOtpVZVBtw5DgWU58jkhI8giDWAv54G6xBy48LJMWDw5xwuEkGHvYXp1Qs0xo5pmwBDDoSmqseCpF76ByZdw9hjA9N8TGiRZBwkgsVz32/DZ6LZAl4hYXp2s2HNjogny0bqOJLs= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1698975262; c=relaxed/simple; bh=bn2LUYde+y1piXumgfiVWzdYnr6WcH0JYYp2xZrgwH0=; h=DKIM-Signature:Message-ID:Date:From:To:Subject:MIME-Version; b=R2JM3DuI27/DIWkWeV613zLpigkNuFN6dklWfH5AE/OQcoxUHV7rLr4qgNMZOAuJ9MdUtxk0WePY03jP3+8vZ9PMOMEHrkQsZW3vUyZxi5+FPw2mjs2CN1xAFgnHr1cRp9hEU7lmy55O/4q4JM0evx5lWAkUdQfCdQh0rFXsK88= ARC-Authentication-Results: i=1; server2.sourceware.org Received: by mail-oi1-x22f.google.com with SMTP id 5614622812f47-3b2ec5ee2e4so880865b6e.3 for ; Thu, 02 Nov 2023 18:34:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1698975251; x=1699580051; darn=gcc.gnu.org; h=in-reply-to:content-disposition:mime-version:references:subject:cc :to:from:date:message-id:from:to:cc:subject:date:message-id:reply-to; bh=/+M+vTqYT0JW8llAccWgVXiZErGFIyifxZEwlKC1wYc=; b=TuexQKYJVFZlIqcEj9PTk0HJnZ3x+fobQG0AgbJPc5fb/wYaD/kY6wpoTrGg4huoCy 0JqSzUZjnWWJ7/MmHN+VVHbRNpY5ZN4mdSkk8NNxe6tnVj5xL1aNy/k2cq1yI9dmzrbm ts838iCI7biuc9DQX4NiBzXg4bN2ybE774ao/of2P7dnSM+03z8WMobFXsipTBb2S7Jc UrMtA/5C0xfMC1V6V4fB3jOx4Wd9b1/3m8owEffC/dXuBNm2Q7YmheVMzyDrRntcbf+K PnT5ZXL2jMnMX7I3z87L7jg9VCDUHWuWlEayuMr9c53AH2KBqBJyfzC7PJT0DgUX9xmH pq9Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1698975251; x=1699580051; h=in-reply-to:content-disposition:mime-version:references:subject:cc :to:from:date:message-id:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=/+M+vTqYT0JW8llAccWgVXiZErGFIyifxZEwlKC1wYc=; b=kqias7L4/IO+lfqWkZ+8XgmIVYRpQewaxijlm7bIGt6P58980YkRevoodP7ovI+r5P lliIpScEIqAJRQ4PJHLB+bXbiroPsb4j9+vhLPzF7XaL/T8ruYKgPrEgMwk++O+fszAe N2B/+PYvGnWsxN+krq7wbzHilGIYKLfOsO4iUYHHdujmSxa2xVW8xHA8whohcv+RvjmV Sf0isw5zTNFuC10dHYxocQybaUI8rAODlTwOg4qFFMkKY+eFB1C7voOiwUJVAZEtEZgv Z9yEMX0Hv3VP8TbRiV8kndSHpWVuKdHqGWwO4lYezosifE497IwjvD2amkF9K+cYA8lh Yx9Q== X-Gm-Message-State: AOJu0YxSuoeqmIBOafrBqNxbW2Fos4AKkpznq5ubjP9C0aXWAZ1pjKlc DKO/vHWnuWXR/LGcEcn3mQTzDYL2+sY= X-Google-Smtp-Source: AGHT+IGEZq1TPHStIG3xx24AmqQd9jv8CJI1XGQANlQ+pcU+lEu2KnVo5X3a5vA6fFEgyMQWsoaISA== X-Received: by 2002:a05:6808:15a9:b0:3a4:4b42:612b with SMTP id t41-20020a05680815a900b003a44b42612bmr24491762oiw.42.1698975250639; Thu, 02 Nov 2023 18:34:10 -0700 (PDT) Received: from Thaum. (124-150-88-161.tpgi.com.au. [124.150.88.161]) by smtp.gmail.com with ESMTPSA id a23-20020aa78657000000b0069ea08a2a99sm327342pfo.211.2023.11.02.18.34.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Nov 2023 18:34:10 -0700 (PDT) Message-ID: <65444e12.a70a0220.cf10e.14fd@mx.google.com> X-Google-Original-Message-ID: Date: Fri, 3 Nov 2023 12:34:06 +1100 From: Nathaniel Shead To: gcc-patches@gcc.gnu.org Cc: Jason Merrill Subject: Re: [PATCH] c++: End lifetime of objects in constexpr after destructor call [PR71093] References: <65444a6a.170a0220.5f247.11c7@mx.google.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <65444a6a.170a0220.5f247.11c7@mx.google.com> X-Spam-Status: No, score=-11.8 required=5.0 tests=BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,GIT_PATCH_0,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS,TXREP,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: 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 > --- > 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 > +constexpr bool test_access() { > + T x {}; > + x.~T(); > + T y = x; // { dg-error "lifetime" } > + return true; > +} > + > +template > +constexpr bool test_modification() { > + T x {}; > + x.~T(); > + x = T(); // { dg-error "lifetime" } > + return true; > +} > + > +template > +constexpr bool test_scope() { > + { > + T x {}; > + x.~T(); > + } // { dg-error "destroying" } > + return true; > +} > + > +template > +constexpr bool test_destroy_temp() { > + T{}.~T(); // { dg-error "destroying" } > + return true; > +} > + > +template > +constexpr bool test_parameter(T t) { > + // note: error message occurs at point of call > + t.~T(); > + return true; > +} > + > +template > +constexpr void test_bindings_impl(int n) { > + if (n == 0) return; > + T a {}; > + if (n == 1) return; > + T b {}; > +} > + > +template > +constexpr bool test_bindings() { > + test_bindings_impl(1); > + test_bindings_impl(0); > + test_bindings_impl(2); > + return true; > +} > + > +constexpr bool i1 = test_access(); // { dg-message "in .constexpr." } > +constexpr bool i2 = test_modification(); // { dg-message "in .constexpr." } > +constexpr bool i3 = test_scope(); // { dg-message "in .constexpr." } > +constexpr bool i4 = test_destroy_temp(); // { dg-message "in .constexpr." "" { xfail *-*-* } } > +constexpr bool i5 = test_parameter(int{}); // { dg-error "destroying" } > +constexpr bool i6 = test_bindings(); > + > +struct Trivial { int x; }; > +constexpr bool t1 = test_access(); // { dg-message "in .constexpr." } > +constexpr bool t2 = test_modification(); // { dg-message "in .constexpr." } > +constexpr bool t3 = test_scope(); // { dg-message "in .constexpr." } > +constexpr bool t4 = test_destroy_temp(); // { dg-message "in .constexpr." } > +constexpr bool t5 = test_parameter(Trivial{}); // { dg-error "destroying" } > +constexpr bool t6 = test_bindings(); > + > +#if __cplusplus >= 202002L > +struct NonTrivial { int x; constexpr ~NonTrivial() {} }; // { dg-error "destroying" "" { target c++20 } } > +constexpr bool n1 = test_access(); // { dg-message "in .constexpr." "" { target c++20 } } > +constexpr bool n2 = test_modification(); // { dg-message "in .constexpr." "" { target c++20 } } > +constexpr bool n3 = test_scope(); // { dg-message "in .constexpr." "" { target c++20 } } > +constexpr bool n4 = test_destroy_temp(); // { dg-message "in .constexpr." "" { target c++20 } } > +constexpr bool n5 = test_parameter(NonTrivial{}); // { dg-error "destroying" "" { target c++20 } } > +constexpr bool n6 = test_bindings(); > +#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 > 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 >