From patchwork Sat Jul 1 03:28:34 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nathaniel Shead X-Patchwork-Id: 114941 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:994d:0:b0:3d9:f83d:47d9 with SMTP id k13csp10805679vqr; Fri, 30 Jun 2023 20:30:05 -0700 (PDT) X-Google-Smtp-Source: APBJJlFRzPPGfA29WsOS415AaDxTrl/q5Hb/cLrs6pRQDPw4QwOI6rcdMwQmYFq6wUnti35oA4e8 X-Received: by 2002:a17:906:f20c:b0:982:c8d0:683f with SMTP id gt12-20020a170906f20c00b00982c8d0683fmr2822772ejb.18.1688182204966; Fri, 30 Jun 2023 20:30:04 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1688182204; cv=none; d=google.com; s=arc-20160816; b=XO7ytlD0Z0Cfoxre5yq3yebQdsbDoQAWuWaQs+y+3EVabuM5mR9ZVe3P4pvMVa9ypC GORX31Zpr/Ilj9oAynIXL5mlfumwjxG7TvMWDmZ5NNTPrLyVh8RuqrhbH2fSkPKOcIaq gF1bB4UN9g+6N9RBWW3U4cjOmw3XYGVLhwd7r8rj8UuFKRoUQAAK1k4Qp20E0t1QE756 evdXhDfZ7RDEIf1xFaf3zrIrkc+lEZYeG6B7Esdd0lXrueLy3JZqT7V6MnLqJvPnoBlE H1jcUY/SR6nz5Bd6Q73jZI03m6Gv5lytcgvwU+8aF07OQvk9AR0w4Z+IM6JTZ6QYUgft htaQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:reply-to:from:list-subscribe:list-help:list-post :list-archive:list-unsubscribe:list-id:precedence:in-reply-to :content-disposition:mime-version:message-id:subject:cc:to:date :dmarc-filter:delivered-to:dkim-signature:dkim-filter; bh=LciYhUEFrOCO7E4n2M1q0X32IdcoB5QF5oW34vNvQj4=; fh=rniJ05hh5TtJEgvFF322qtTpFQcCvp9aWk5OC4T+KQA=; b=aN5uhsGrIeunG+ooTjyncl2AnE5PD9X9+1p26ypAPiQSVEhSen/8UGCzDAD+wRGNoG CRiHR2EbAeplUraqU1JRD3ULU0TzvUUKtJMXpBtWMku9ijugviwODGpHtDpq1NCjxaSU K0PCvLMenqjYddwqe9dpBop1pwHhCYf6bEgcGWL88b42rT5ZCS7Cov7tMwY9J/aNYncB YLZsNcPl/z7uhOhhb4KFbTIrZgjBZgIdQfYV5OvMh3iwk542m6nPiWf0XelmqDwRKfIw TVznNFtRsU7+EnFMsdtkSrsslSvGxb3OApnKgHYiPqQQdbjvgPXU0X6j066nAYq6+jDs dX7w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gcc.gnu.org header.s=default header.b=Hz3E4Y2s; spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gnu.org Received: from sourceware.org (server2.sourceware.org. [2620:52:3:1:0:246e:9693:128c]) by mx.google.com with ESMTPS id q16-20020a170906361000b009889b296070si8478303ejb.261.2023.06.30.20.30.04 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Jun 2023 20:30:04 -0700 (PDT) Received-SPF: pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) client-ip=2620:52:3:1:0:246e:9693:128c; Authentication-Results: mx.google.com; dkim=pass header.i=@gcc.gnu.org header.s=default header.b=Hz3E4Y2s; spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gnu.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 8C9E23858D38 for ; Sat, 1 Jul 2023 03:30:03 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8C9E23858D38 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1688182203; bh=LciYhUEFrOCO7E4n2M1q0X32IdcoB5QF5oW34vNvQj4=; h=Date:To:Cc:Subject:In-Reply-To:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=Hz3E4Y2sHTdqaBt9y8dFuCSDDhYWN5+vrWicEw7S2ysXq3oD1zu92a+sv2Z5oVRPi K4o5q0TCwau+u/o4+zZ0OH4EfefaLkLS0GJAE8fpZnv/NIfpdah2lK2wlDg4rn4qqH 1gHAx5shEql7ZXSpRJNkSHH4oukGTq5hPT5s2EbA= X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mail-ot1-x32f.google.com (mail-ot1-x32f.google.com [IPv6:2607:f8b0:4864:20::32f]) by sourceware.org (Postfix) with ESMTPS id 852FA3858D33 for ; Sat, 1 Jul 2023 03:28:45 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 852FA3858D33 Received: by mail-ot1-x32f.google.com with SMTP id 46e09a7af769-6b5ef64bca6so2252026a34.3 for ; Fri, 30 Jun 2023 20:28:45 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1688182124; x=1690774124; h=in-reply-to:content-disposition:mime-version:message-id:subject:cc :to:from:date:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=LciYhUEFrOCO7E4n2M1q0X32IdcoB5QF5oW34vNvQj4=; b=XEs2keiYGY8KPxG4llTwLA7dvnLDMOEvgcklV13u6ifGKb+zOZQU58fj4tA7Sj8WJV qeRit7QgLAQOdm5Zw3A3R3xEnqioJ+XFd0pNBI+wmcH8FoECCn4C4CeTwgiGRGO1tOfP QKLgXyOs9er44R68K/aAEbXK5gHErMF5xxZk0XARrjiQlpIyRueZQkCQdHd4enakIkZH feNfmAdbeEVvEot93/mXJk0e45edlXu0cW+IDB8FboOtTZLoyJHroiBq+ws2JYjDQy+2 L4uC8PdxT0HBTlTK5kkGTNg1yBD1VI3+hQjn9pcOwidQcOFXaW1zOJBmJ86CEg45OByr oFsw== X-Gm-Message-State: AC+VfDxZhNTYNkG2qRn3Z1LgkdYSg8CcpDpLBAIoJJAgoVIiB12zdPH4 xEwaxjtmC86+zoqZDiNm52TwhV2h0zk= X-Received: by 2002:a9d:6844:0:b0:6b7:43eb:c1a with SMTP id c4-20020a9d6844000000b006b743eb0c1amr4743173oto.36.1688182124468; Fri, 30 Jun 2023 20:28:44 -0700 (PDT) Received: from Thaum.localdomain (59-102-120-25.tpgi.com.au. [59.102.120.25]) by smtp.gmail.com with ESMTPSA id me14-20020a17090b17ce00b002630bfd35b0sm7246618pjb.7.2023.06.30.20.28.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Jun 2023 20:28:44 -0700 (PDT) Date: Sat, 1 Jul 2023 13:28:34 +1000 To: gcc-patches@gcc.gnu.org Cc: Patrick Palka Subject: [PATCH v3 1/3] c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675] Message-ID: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: X-Spam-Status: No, score=-11.6 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Nathaniel Shead via Gcc-patches From: Nathaniel Shead Reply-To: Nathaniel Shead Errors-To: gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org Sender: "Gcc-patches" X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1770187343716328171?= X-GMAIL-MSGID: =?utf-8?q?1770187343716328171?= This adds rudimentary lifetime tracking in C++ constexpr contexts, allowing the compiler to report errors with using values after their backing has gone out of scope. We don't yet handle other ways of accessing values outside their lifetime (e.g. following explicit destructor calls). PR c++/96630 PR c++/98675 PR c++/70331 gcc/cp/ChangeLog: * constexpr.cc (constexpr_global_ctx::remove_value): Mark value as outside lifetime. (find_expired_values): New function. (outside_lifetime_error): New function. (cxx_eval_call_expression): Don't cache calls that return references to values outside their lifetime. (cxx_eval_constant_expression): Add checks for out-of-lifetime values. Forget local variables at end of bind expressions, and temporaries after cleanup points. gcc/testsuite/ChangeLog: * g++.dg/cpp1y/constexpr-lifetime1.C: New test. * g++.dg/cpp1y/constexpr-lifetime2.C: New test. * g++.dg/cpp1y/constexpr-lifetime3.C: New test. * g++.dg/cpp1y/constexpr-lifetime4.C: New test. * g++.dg/cpp1y/constexpr-lifetime5.C: New test. Signed-off-by: Nathaniel Shead --- gcc/cp/constexpr.cc | 112 ++++++++++++++---- .../g++.dg/cpp1y/constexpr-lifetime1.C | 13 ++ .../g++.dg/cpp1y/constexpr-lifetime2.C | 20 ++++ .../g++.dg/cpp1y/constexpr-lifetime3.C | 13 ++ .../g++.dg/cpp1y/constexpr-lifetime4.C | 11 ++ .../g++.dg/cpp1y/constexpr-lifetime5.C | 11 ++ 6 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index cca0435bafc..bc59b4aab67 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -1165,6 +1165,8 @@ public: hash_set *modifiable; /* Number of heap VAR_DECL deallocations. */ unsigned heap_dealloc_count; + /* Values that are not within their lifetime. */ + hash_set outside_lifetime; /* Constructor. */ constexpr_global_ctx () : constexpr_ops_count (0), cleanups (NULL), modifiable (nullptr), @@ -1188,7 +1190,12 @@ public: if (!already_in_map && modifiable) modifiable->add (t); } - void remove_value (tree t) { values.remove (t); } + void remove_value (tree t) + { + if (DECL_P (t)) + outside_lifetime.add (t); + values.remove (t); + } }; /* Helper class for constexpr_global_ctx. In some cases we want to avoid @@ -2509,6 +2516,22 @@ cxx_eval_dynamic_cast_fn (const constexpr_ctx *ctx, tree call, return cp_build_addr_expr (obj, complain); } +/* Look for expired values in the expression *TP, called through + cp_walk_tree. DATA is ctx->global->outside_lifetime. */ + +static tree +find_expired_values (tree *tp, int *walk_subtrees, void *data) +{ + hash_set *outside_lifetime = (hash_set *) data; + + if (TYPE_P (*tp)) + *walk_subtrees = 0; + else if (outside_lifetime->contains (*tp)) + return *tp; + + return NULL_TREE; +} + /* Data structure used by replace_decl and replace_decl_r. */ struct replace_decl_data @@ -3160,10 +3183,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, for (tree save_expr : save_exprs) ctx->global->remove_value (save_expr); - /* Remove the parms/result from the values map. Is it worth - bothering to do this when the map itself is only live for - one constexpr evaluation? If so, maybe also clear out - other vars from call, maybe in BIND_EXPR handling? */ + /* Remove the parms/result from the values map. */ ctx->global->remove_value (res); for (tree parm = parms; parm; parm = TREE_CHAIN (parm)) ctx->global->remove_value (parm); @@ -3210,13 +3230,20 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, cacheable = false; } - /* Rewrite all occurrences of the function's RESULT_DECL with the - current object under construction. */ - if (!*non_constant_p && ctx->object - && CLASS_TYPE_P (TREE_TYPE (res)) - && !is_empty_class (TREE_TYPE (res))) - if (replace_decl (&result, res, ctx->object)) - cacheable = false; + /* Also don't cache a call if we return a pointer to an expired + value. */ + if (cacheable && (cp_walk_tree_without_duplicates + (&result, find_expired_values, + &ctx->global->outside_lifetime))) + cacheable = false; + + /* Rewrite all occurrences of the function's RESULT_DECL with the + current object under construction. */ + if (!*non_constant_p && ctx->object + && CLASS_TYPE_P (TREE_TYPE (res)) + && !is_empty_class (TREE_TYPE (res))) + if (replace_decl (&result, res, ctx->object)) + cacheable = false; } else /* Couldn't get a function copy to evaluate. */ @@ -5687,6 +5714,25 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t, return r; } +/* Complain about R, a DECL that is accessed outside its lifetime. */ + +static void +outside_lifetime_error (location_t loc, tree r) +{ + if (DECL_NAME (r) == heap_deleted_identifier) + { + /* Provide a more accurate message for deleted variables. */ + error_at (loc, "use of allocated storage after deallocation " + "in a constant expression"); + inform (DECL_SOURCE_LOCATION (r), "allocated here"); + } + else + { + error_at (loc, "accessing object outside its lifetime"); + inform (DECL_SOURCE_LOCATION (r), "declared here"); + } +} + /* Complain about R, a VAR_DECL, not being usable in a constant expression. FUNDEF_P is true if we're checking a constexpr function body. Shared between potential_constant_expression and @@ -7054,6 +7100,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, r = build_constructor (TREE_TYPE (t), NULL); TREE_CONSTANT (r) = true; } + else if (ctx->global->outside_lifetime.contains (t)) + { + if (!ctx->quiet) + outside_lifetime_error (loc, t); + *non_constant_p = true; + break; + } else if (ctx->strict) r = decl_really_constant_value (t, /*unshare_p=*/false); else @@ -7085,7 +7138,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, case PARM_DECL: if (lval && !TYPE_REF_P (TREE_TYPE (t))) /* glvalue use. */; - else if (tree v = ctx->global->get_value (r)) + else if (tree v = ctx->global->get_value (t)) r = v; else if (lval) /* Defer in case this is only used for its type. */; @@ -7099,7 +7152,12 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, else { if (!ctx->quiet) - error ("%qE is not a constant expression", t); + { + if (ctx->global->outside_lifetime.contains (t)) + outside_lifetime_error (loc, t); + else + error_at (loc, "%qE is not a constant expression", t); + } *non_constant_p = true; } break; @@ -7328,17 +7386,28 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, auto_vec cleanups; vec *prev_cleanups = ctx->global->cleanups; ctx->global->cleanups = &cleanups; - r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0), + + auto_vec save_exprs; + constexpr_ctx new_ctx = *ctx; + new_ctx.save_exprs = &save_exprs; + + r = cxx_eval_constant_expression (&new_ctx, TREE_OPERAND (t, 0), lval, non_constant_p, overflow_p, jump_target); + ctx->global->cleanups = prev_cleanups; unsigned int i; tree cleanup; /* Evaluate the cleanups. */ FOR_EACH_VEC_ELT_REVERSE (cleanups, i, cleanup) - cxx_eval_constant_expression (ctx, cleanup, vc_discard, + cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard, non_constant_p, overflow_p); + + /* Forget SAVE_EXPRs and TARGET_EXPRs created by this + full-expression. */ + for (tree save_expr : save_exprs) + ctx->global->remove_value (save_expr); } break; @@ -7855,10 +7924,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, non_constant_p, overflow_p, jump_target); case BIND_EXPR: - return cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t), - lval, - non_constant_p, overflow_p, - jump_target); + 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; case PREINCREMENT_EXPR: case POSTINCREMENT_EXPR: diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C new file mode 100644 index 00000000000..43aa7c974c1 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C @@ -0,0 +1,13 @@ +// PR c++/96630 +// { dg-do compile { target c++14 } } + +struct S { + int x = 0; + constexpr const int& get() const { return x; } +}; + +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" } diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C new file mode 100644 index 00000000000..22cd919fcda --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C @@ -0,0 +1,20 @@ +// PR c++/98675 +// { dg-do compile { target c++14 } } + +struct S { + int x = 0; + constexpr const int& get() const { return x; } +}; + +constexpr int error() { + const auto& local = S{}.get(); // { dg-message "note: declared here" } + return local; +} +constexpr int x = error(); // { dg-error "accessing object outside its lifetime" } + +constexpr int ok() { + // temporary should only be destroyed after end of full-expression + auto local = S{}.get(); + return local; +} +constexpr int y = ok(); diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C new file mode 100644 index 00000000000..6329f8cf6c6 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C @@ -0,0 +1,13 @@ +// PR c++/70331 +// { dg-do compile { target c++14 } } + +constexpr int f(int i) { + int *p = &i; + if (i == 0) { + int j = 123; // { dg-message "note: declared here" } + p = &j; + } + return *p; +} + +constexpr int i = f(0); // { dg-error "accessing object outside its lifetime" } diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C new file mode 100644 index 00000000000..181a1201663 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C @@ -0,0 +1,11 @@ +// { dg-do compile { target c++14 } } + +constexpr const double& test() { + const double& local = 3.0; // { dg-message "note: declared here" } + return local; +} + +static_assert(test() == 3.0, ""); // { dg-error "constant|accessing object outside its lifetime" } + +// no deference, shouldn't error +static_assert((test(), true), ""); diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C new file mode 100644 index 00000000000..a4bc71d890a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C @@ -0,0 +1,11 @@ +// { dg-do compile { target c++14 } } +// { dg-options "-Wno-return-local-addr" } + +constexpr const int& id(int x) { return x; } + +constexpr bool test() { + const int& y = id(3); + return y == 3; +} + +constexpr bool x = test(); // { dg-error "" }