From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-pg1-x52f.google.com (mail-pg1-x52f.google.com [IPv6:2607:f8b0:4864:20::52f]) by sourceware.org (Postfix) with ESMTPS id 9AB8D3858D28 for ; Fri, 3 Nov 2023 01:18:36 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9AB8D3858D28 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 9AB8D3858D28 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::52f ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1698974327; cv=none; b=PcRqeN88XaTofM3JRKsdGGVBqL6TO6zNY6BIFo5qIW+A5gDR4zbuskrzLsFjy82HR0YOdJ44cajEPX1vbNdV8svF4CgXb1qhqgIMTMKEwJIinW1aS3eSA6goOQ8iA9+7sBtD68KaQc2pY61cmuEnLmZWAGGYdUWZx3o4OzsNI54= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1698974327; c=relaxed/simple; bh=KPKTNCQ9tPX+ieQRbxHkfdxiHbuyHF6+rd++n1jz0Ms=; h=DKIM-Signature:Message-ID:Date:From:To:Subject:MIME-Version; b=qdkhtzqfV/kWIRwvEYaPFUg1AUIaebAh97Dqwr38Gyd+qwQC0sem1gpwmD3rJ+P6myPigheEdEcsm88ijSCYnWqYJ+1KGE3sQ1hpZKjFgf/x4GlriWTmq36Aae4Clvh2+ijS/SQ80qdI/uTSYhghHZH2bYwOtQxrsNiy1zfuVak= ARC-Authentication-Results: i=1; server2.sourceware.org Received: by mail-pg1-x52f.google.com with SMTP id 41be03b00d2f7-53fa455cd94so1213094a12.2 for ; Thu, 02 Nov 2023 18:18:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1698974315; x=1699579115; darn=gcc.gnu.org; h=content-disposition:mime-version:subject:cc:to:from:date:message-id :from:to:cc:subject:date:message-id:reply-to; bh=ipPBjysSOi+qaqGy1uC4ytJUInsf89HBFuD4Q6QxgH0=; b=R4B6HqZYSg3vmq8a1a/v9YZo8PJb4tIhaM3lLYqseEHLpRqNnxDIt2TysqCLOe3Nnl Ab/GlJDSc6/5hXY2O1p1hbA25R1pMl3XnzLRR6SMwjzMFiypjBm/W+QyGyfxmT3yMC63 Rue8T8Mk7Lk78gTBzvo8cO1N/tbLy1zcIO+1SXEYqDLNGsajb8Yk7soKeY4hdLXmRCWg vCHY1xmygvR9pxgOJMjNlktmZEiLdbvUnTLphpOTeq4HpXs4F3MGEilxR3aooXHEQdL1 YmW1BpfZOOyrlYgWsdQANRjwFQY0CfEtkiStKSLcSQA7IXPYGqTGNfe5QKAzJIjjfDAH GWdQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1698974315; x=1699579115; h=content-disposition:mime-version:subject:cc:to:from:date:message-id :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=ipPBjysSOi+qaqGy1uC4ytJUInsf89HBFuD4Q6QxgH0=; b=fRiO34xi7ll4B1NzlqQKwjX9ig/H8ohnWzdPEgBoCeZ9JvzXcOo6tkkmRpwOVu/FOb epw4VJSRtj1I9xjU63RQ9q1F0q3UJGcL0HEslYIyYCPlko7QgKPkvH6hHegrD5YJgz7s OVujSHzpTKIc8sELjP8OLtCxSfJpqcbvi6ZeAuNiU8ujgc78naacPGs1dR3KcMKB9Z57 I8y6CHqInSSENHZx3+VidreAHWTUSomLegTuW5QfjKfnKNvY2S5MSvuzY/5t1CeqCJ7i HXJ8e/JSDleJcYXaw+JEs4UqZ56i6rD71d5qabmC+SYM0zX/TKitxxXik5/hZs6a7xDM RPfQ== X-Gm-Message-State: AOJu0YwYV9Ctu5iob/zkZiL86WefjHLI19bnoJ/5XqV7pl9ogVorj06+ hK2a5/DVnx82cpg9PhgYnl2rFc30T+s= X-Google-Smtp-Source: AGHT+IEiiodxZifcV5zNNghJyXVrn8lrxat9HeNKgdYfIIJxe5dxJdRs+EgaksET2lmfU5p/Ss0W3w== X-Received: by 2002:a05:6a21:328c:b0:159:b4ba:a5bf with SMTP id yt12-20020a056a21328c00b00159b4baa5bfmr21081726pzb.13.1698974314789; Thu, 02 Nov 2023 18:18:34 -0700 (PDT) Received: from Thaum. (124-150-88-161.tpgi.com.au. [124.150.88.161]) by smtp.gmail.com with ESMTPSA id y20-20020a170902ed5400b001cc256ce1besm284642plb.138.2023.11.02.18.18.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Nov 2023 18:18:34 -0700 (PDT) Message-ID: <65444a6a.170a0220.5f247.11c7@mx.google.com> X-Google-Original-Message-ID: Date: Fri, 3 Nov 2023 12:18:29 +1100 From: Nathaniel Shead To: gcc-patches@gcc.gnu.org Cc: Jason Merrill Subject: [PATCH] c++: End lifetime of objects in constexpr after destructor call [PR71093] MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline 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: 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