From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: 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 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ot1-x32f.google.com with SMTP id 46e09a7af769-6b5ef64bca6so2252026a34.3 for ; Fri, 30 Jun 2023 20:28:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1688182124; x=1690774124; h=in-reply-to:content-disposition:mime-version:message-id:subject:cc :to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=LciYhUEFrOCO7E4n2M1q0X32IdcoB5QF5oW34vNvQj4=; b=VHIldl5SLCu/9NUrHi4YtSenYZZY/pfbqQspvH43uI2XxRk15SnJDnUIKrKxUGnm/6 6uStipEUPeLB7XBrA9qyaD/YlV9W63dkf9yEYWymletIBz0ptrH+RZw09y3C4l/qZisM MHQiZLCQkzhmo95yEnSD0IPnilt2nvn4BXaclOX87hSwfEgB3Wa+ckoHZ6kUxWL85eWK P40PUb/H/kL/6g4uQEvh8fkQuHf+Oa0yoXX3mBt4+dW1B614WAG83UfGycXWbGdO3PIp fY3QUMYtShfeGiLYPttx9fnk7ve+mosiLcLodR695t5nC5XKA/YROvAR+xFPs/nAnZIZ otEQ== 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-Google-Smtp-Source: ACHHUZ4psS4E+drKTQVmkuwULr2AKk1rTqSoqEJjDDm/UslvOeqIrhzUKHbJl4BxwDoCe263GcAeLg== 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 From: Nathaniel Shead 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-Type: text/plain; charset=us-ascii 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 List-Id: 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 "" } -- 2.41.0