From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-pf1-x42b.google.com (mail-pf1-x42b.google.com [IPv6:2607:f8b0:4864:20::42b]) by sourceware.org (Postfix) with ESMTPS id 9040438768BB for ; Fri, 30 Jun 2023 15:45:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9040438768BB 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-pf1-x42b.google.com with SMTP id d2e1a72fcca58-66f3fc56ef4so1266992b3a.0 for ; Fri, 30 Jun 2023 08:45:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1688139928; x=1690731928; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=461NW6IapWUGIkhtHo4ysT71obDjCifr3g87uH5pNWs=; b=a0o5JeXf1xFDgZp2lr8VCdSo9/iyvKtTGBLWPUixSdRRwExka8CVXVbxo9PxWLlszN 9TT9cTpJS2YHYp7BA/bzjEJZGtLp0CirJIFnZ/eW4xMs6kVMABlX9vMY6Lom5P/3SsUR t8PZtzTSJDoMsyUqaC8SBAIylk/Z8exztCxDzLLf+pKCYZEviKNoQyZ0zQUZRfFnhmIZ K5c2Un6pITl16Z4SpNzVbrLiufuPpgf4cHuIlC9kUo7Qu3EXHDL4sau2VjhTrzEAXczo Y8USgf8Mw+m3OgCQ3dHD7cwnVo+9kJ4Q4hUThZKVwm9IONVbcrjlrD2igK2Jnodcsu/5 XJHg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1688139928; x=1690731928; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=461NW6IapWUGIkhtHo4ysT71obDjCifr3g87uH5pNWs=; b=Sv+Xe2FI2MZ0DNtTWHhdYk+jB11qOLyV4+WYFTZ3sR1GiBAM9NQQXoSqQSx8mIMoNI oG9LaBJjfAC0803l98NRnjvBSs4GnotIFsYn7QhpjuyRIfOtnyXWrkBVRwp3IxcLwZYV B1jXaE4s6+zJ/X4SCH/puRMO8MPq29N+URklSLroH1QtBPChNZ/V8aGfvjao9BmVjpf0 RDob8hItd0e5zw7FFQUZ0iUrwoMh1BIfo48lb/gYa6gp++RUGfpgqtbI6XYT8MraxHDs CGek/ESVI3L4/o0AtpHakhuN8yurMnEc1SH8NY4WNLVzikhB+EB1vwjAMV5HEAQ2U+d4 EKxg== X-Gm-Message-State: ABy/qLZZrPdMwXZ2QZJGmCdjYG86y+Vqdnd62EVHtl5UpU4OroAEX7/U jCAHGC1CqGWYQ8cR/Qiqhrw= X-Google-Smtp-Source: APBJJlHu2a/vT1W8pj55u+zHdRCP44agM418lTJSw0cdWZ1Z3FE2cnFBWWWXRdY71lh4r/ect4Qdrw== X-Received: by 2002:a05:6a00:158a:b0:67f:d5e7:4604 with SMTP id u10-20020a056a00158a00b0067fd5e74604mr4779097pfk.13.1688139927553; Fri, 30 Jun 2023 08:45:27 -0700 (PDT) Received: from Thaum.localdomain (59-102-120-25.tpgi.com.au. [59.102.120.25]) by smtp.gmail.com with ESMTPSA id c26-20020aa781da000000b0062e0515f020sm10154233pfn.162.2023.06.30.08.45.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Jun 2023 08:45:27 -0700 (PDT) Date: Sat, 1 Jul 2023 01:45:16 +1000 From: Nathaniel Shead To: Patrick Palka Cc: gcc-patches@gcc.gnu.org Subject: Re: [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675] Message-ID: References: <20230329023258.13487-1-nathanieloshead@gmail.com> <20230329023258.13487-2-nathanieloshead@gmail.com> <75995995-bf20-6644-433e-1b43bc27de09@idea> <02695d94-d636-fca3-2a57-d7f8c350327d@idea> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <02695d94-d636-fca3-2a57-d7f8c350327d@idea> X-Spam-Status: No, score=-11.7 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: On Mon, Jun 26, 2023 at 03:37:32PM -0400, Patrick Palka wrote: > On Sun, 25 Jun 2023, Nathaniel Shead wrote: > > > On Fri, Jun 23, 2023 at 12:43:21PM -0400, Patrick Palka wrote: > > > On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote: > > > > > > > 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 ending > > > > lifetimes (e.g. explicit destructor calls). > > > > > > Awesome! > > > > > > > > > > > PR c++/96630 > > > > PR c++/98675 > > > > PR c++/70331 > > > > > > > > gcc/cp/ChangeLog: > > > > > > > > * constexpr.cc (constexpr_global_ctx::put_value): Mark value as > > > > in lifetime. > > > > (constexpr_global_ctx::remove_value): Mark value as expired. > > > > (cxx_eval_call_expression): Remove comment that is no longer > > > > applicable. > > > > (non_const_var_error): Add check for expired values. > > > > (cxx_eval_constant_expression): Add checks for expired values. Forget > > > > local variables at end of bind expressions. Forget temporaries at end > > > > of cleanup points. > > > > * cp-tree.h (struct lang_decl_base): New flag to track expired values > > > > in constant evaluation. > > > > (DECL_EXPIRED_P): Access the new flag. > > > > (SET_DECL_EXPIRED_P): Modify the new flag. > > > > * module.cc (trees_out::lang_decl_bools): Write out the new flag. > > > > (trees_in::lang_decl_bools): Read in the new flag. > > > > > > > > gcc/testsuite/ChangeLog: > > > > > > > > * g++.dg/cpp0x/constexpr-ice20.C: Update error raised by test. > > > > * 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 | 69 +++++++++++++++---- > > > > gcc/cp/cp-tree.h | 10 ++- > > > > gcc/cp/module.cc | 2 + > > > > gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C | 2 +- > > > > .../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 +++ > > > > 9 files changed, 137 insertions(+), 14 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 3de60cfd0f8..bdbc12144a7 100644 > > > > --- a/gcc/cp/constexpr.cc > > > > +++ b/gcc/cp/constexpr.cc > > > > @@ -1185,10 +1185,22 @@ public: > > > > void put_value (tree t, tree v) > > > > { > > > > bool already_in_map = values.put (t, v); > > > > + if (!already_in_map && DECL_P (t)) > > > > + { > > > > + if (!DECL_LANG_SPECIFIC (t)) > > > > + retrofit_lang_decl (t); > > > > + if (DECL_LANG_SPECIFIC (t)) > > > > + SET_DECL_EXPIRED_P (t, false); > > > > + } > > > > > > Since this new flag would only be used only during constexpr evaluation, > > > could we instead use an on-the-side hash_set in constexpr_global_ctx for > > > tracking expired-ness? That way we won't have to allocate a > > > DECL_LANG_SPECIFIC structure for decls that lack it, and won't have to > > > worry about the flag in other parts of the compiler. > > > > I've tried this but I haven't been able to get it to work well. The main > > issue I'm running into is the caching of function calls in constant > > evaluation. For example, consider the following: > > > > constexpr const double& test() { > > const double& local = 3.0; > > return local; > > } > > > > constexpr int foo(const double&) { return 5; } > > > > constexpr int a = foo(test()); > > static_assert(test() == 3.0); > > > > When constant-evaluating 'a', we evaluate 'test()'. It returns a value > > that ends its lifetime immediately, so we mark this in 'ctx->global' as > > expired. However, 'foo()' never actually evaluates this expired value, > > so the initialisation of 'a' succeeds. > > > > However, then when the static assertion attempts to constant evaluate a > > second time, the result of 'test' has already been cached, and we just > > get directly handed a value. This is a new constant evaluation, so > > 'ctx->global' has been reset, and because we just got the result of the > > cached function we don't actually know whether this is expired or not > > anymore, and so this compiles without any error in case it was valid. > > Ouch, good catch.. > > > > > I haven't yet been able to come up with a good way of avoiding this > > issue without complicating the caching of call expressions overly much. > > I suppose I could add an extra field to 'constexpr_call' to track if the > > value has already been expired (which would solve this particular case), > > but I'm worried that I'll overlook other cases that will sidestep this. > > > > Do you have any other thoughts on the best approach here? Thanks. > > This situation seems similar to that of a constexpr call returning a delete'd > pointer, which we handle by preventing caching of the call: > > From constexpr.cc (cxx_eval_call_expression): > 3207 /* Also don't cache a call that returns a deallocated pointer. */ > 3208 if (cacheable && (cp_walk_tree_without_duplicates > 3209 (&result, find_heap_var_refs, NULL))) > 3210 cacheable = false; > > Maybe we could also disable caching in this situation as well, i.e. whenever a > constexpr call returns a reference to an expired variable? Thanks for the pointer, I tried this and it works well. I was initially a little worried about something similar happening with parameters but it looks like that's already handled, since non-constant arguments also already disable caching. I'm still bootstrapping/regtesting but I'll send out a new version of this patch series tomorrow when it's done. > > > > > > 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) && DECL_LANG_SPECIFIC (t)) > > > > + SET_DECL_EXPIRED_P (t, true); > > > > + values.remove (t); > > > > + } > > > > }; > > > > > > > > /* Helper class for constexpr_global_ctx. In some cases we want to avoid > > > > @@ -3157,10 +3169,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); > > > > @@ -5708,6 +5717,13 @@ non_const_var_error (location_t loc, tree r, bool fundef_p) > > > > inform (DECL_SOURCE_LOCATION (r), "allocated here"); > > > > return; > > > > } > > > > + if (DECL_EXPIRED_P (r)) > > > > + { > > > > + if (constexpr_error (loc, fundef_p, "accessing object outside its " > > > > + "lifetime")) > > > > + inform (DECL_SOURCE_LOCATION (r), "declared here"); > > > > + return; > > > > + } > > > > if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in " > > > > "a constant expression", r)) > > > > return; > > > > @@ -7048,6 +7064,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, > > > > r = build_constructor (TREE_TYPE (t), NULL); > > > > TREE_CONSTANT (r) = true; > > > > } > > > > + else if (DECL_EXPIRED_P (t)) > > > > + { > > > > + if (!ctx->quiet) > > > > + non_const_var_error (loc, r, /*fundef_p*/false); > > > > + *non_constant_p = true; > > > > + break; > > > > + } > > > > else if (ctx->strict) > > > > r = decl_really_constant_value (t, /*unshare_p=*/false); > > > > else > > > > @@ -7093,7 +7116,15 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, > > > > else > > > > { > > > > if (!ctx->quiet) > > > > - error ("%qE is not a constant expression", t); > > > > + { > > > > + if (DECL_EXPIRED_P (r)) > > > > + { > > > > + error_at (loc, "accessing object outside its lifetime"); > > > > + inform (DECL_SOURCE_LOCATION (r), "declared here"); > > > > + } > > > > + else > > > > + error_at (loc, "%qE is not a constant expression", t); > > > > + } > > > > *non_constant_p = true; > > > > } > > > > break; > > > > @@ -7315,17 +7346,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; > > > > > > > > @@ -7831,10 +7873,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/cp/cp-tree.h b/gcc/cp/cp-tree.h > > > > index b74c18b03ad..3cc08da816f 100644 > > > > --- a/gcc/cp/cp-tree.h > > > > +++ b/gcc/cp/cp-tree.h > > > > @@ -2860,6 +2860,7 @@ struct GTY(()) lang_decl_base { > > > > unsigned concept_p : 1; /* applies to vars and functions */ > > > > unsigned var_declared_inline_p : 1; /* var */ > > > > unsigned dependent_init_p : 1; /* var */ > > > > + unsigned expired_p : 1; /* var or parm */ > > > > > > > > /* The following apply to VAR, FUNCTION, TYPE, CONCEPT, & NAMESPACE > > > > decls. */ > > > > @@ -2871,7 +2872,7 @@ struct GTY(()) lang_decl_base { > > > > /* VAR_DECL or FUNCTION_DECL has keyed decls. */ > > > > unsigned module_keyed_decls_p : 1; > > > > > > > > - /* 12 spare bits. */ > > > > + /* 11 spare bits. */ > > > > }; > > > > > > > > /* True for DECL codes which have template info and access. */ > > > > @@ -4366,6 +4367,13 @@ get_vec_init_expr (tree t) > > > > #define SET_DECL_DEPENDENT_INIT_P(NODE, X) \ > > > > (DECL_LANG_SPECIFIC (VAR_DECL_CHECK (NODE))->u.base.dependent_init_p = (X)) > > > > > > > > +/* Nonzero if NODE is a VAR_DECL, PARM_DECL, or FIELD_DECL that is within > > > > + its lifetime for constant evaluation purposes. */ > > > > +#define DECL_EXPIRED_P(NODE) \ > > > > + (DECL_LANG_SPECIFIC (NODE) && DECL_LANG_SPECIFIC (NODE)->u.base.expired_p) > > > > +#define SET_DECL_EXPIRED_P(NODE, X) \ > > > > + (DECL_LANG_SPECIFIC (NODE)->u.base.expired_p = (X)) > > > > + > > > > /* Nonzero if NODE is an artificial VAR_DECL for a C++17 structured binding > > > > declaration or one of VAR_DECLs for the user identifiers in it. */ > > > > #define DECL_DECOMPOSITION_P(NODE) \ > > > > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc > > > > index ac2fe66b080..7af43b5736d 100644 > > > > --- a/gcc/cp/module.cc > > > > +++ b/gcc/cp/module.cc > > > > @@ -5654,6 +5654,7 @@ trees_out::lang_decl_bools (tree t) > > > > WB (lang->u.base.concept_p); > > > > WB (lang->u.base.var_declared_inline_p); > > > > WB (lang->u.base.dependent_init_p); > > > > + WB (lang->u.base.expired_p); > > > > /* When building a header unit, everthing is marked as purview, (so > > > > we know which decls to write). But when we import them we do not > > > > want to mark them as in module purview. */ > > > > @@ -5728,6 +5729,7 @@ trees_in::lang_decl_bools (tree t) > > > > RB (lang->u.base.concept_p); > > > > RB (lang->u.base.var_declared_inline_p); > > > > RB (lang->u.base.dependent_init_p); > > > > + RB (lang->u.base.expired_p); > > > > RB (lang->u.base.module_purview_p); > > > > RB (lang->u.base.module_attach_p); > > > > if (VAR_OR_FUNCTION_DECL_P (t)) > > > > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C > > > > index e2d4853a284..ebaa95e5324 100644 > > > > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C > > > > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C > > > > @@ -4,4 +4,4 @@ > > > > typedef bool (*Function)(int); > > > > constexpr bool check(int x, Function p) { return p(x); } // { dg-message "in .constexpr. expansion of" } > > > > > > > > -static_assert(check(2, check), ""); // { dg-error "conversion|constant|in .constexpr. expansion of" } > > > > +static_assert(check(2, check), ""); // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" } > > > > 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.34.1 > > > > > > > > > > > > > > > >