public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Nathaniel Shead <nathanieloshead@gmail.com>
To: gcc-patches@gcc.gnu.org
Cc: Jason Merrill <jason@redhat.com>
Subject: Re: [PATCH] c++: End lifetime of objects in constexpr after destructor call [PR71093]
Date: Mon, 27 Nov 2023 22:08:53 +1100	[thread overview]
Message-ID: <656478c8.170a0220.6b0b.1db1@mx.google.com> (raw)
In-Reply-To: <65444e12.a70a0220.cf10e.14fd@mx.google.com>

Ping for https://gcc.gnu.org/pipermail/gcc-patches/2023-November/635071.html.

On Fri, Nov 03, 2023 at 12:34:06PM +1100, Nathaniel Shead wrote:
> 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 <nathanieloshead@gmail.com>
> > ---
> >  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 <typename T>
> > +constexpr bool test_access() {
> > +  T x {};
> > +  x.~T();
> > +  T y = x;  // { dg-error "lifetime" }
> > +  return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_modification() {
> > +  T x {};
> > +  x.~T();
> > +  x = T();  // { dg-error "lifetime" }
> > +  return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_scope() {
> > +  {
> > +    T x {};
> > +    x.~T();
> > +  }  // { dg-error "destroying" }
> > +  return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_destroy_temp() {
> > +  T{}.~T();  // { dg-error "destroying" }
> > +  return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_parameter(T t) {
> > +  // note: error message occurs at point of call
> > +  t.~T();
> > +  return true;
> > +}
> > +
> > +template <typename T>
> > +constexpr void test_bindings_impl(int n) {
> > +  if (n == 0) return;
> > +  T a {};
> > +  if (n == 1) return;
> > +  T b {};
> > +}
> > +
> > +template <typename T>
> > +constexpr bool test_bindings() {
> > +  test_bindings_impl<T>(1);
> > +  test_bindings_impl<T>(0);
> > +  test_bindings_impl<T>(2);
> > +  return true;
> > +}
> > +
> > +constexpr bool i1 = test_access<int>();        // { dg-message "in .constexpr." }
> > +constexpr bool i2 = test_modification<int>();  // { dg-message "in .constexpr." }
> > +constexpr bool i3 = test_scope<int>();         // { dg-message "in .constexpr." }
> > +constexpr bool i4 = test_destroy_temp<int>();  // { dg-message "in .constexpr." "" { xfail *-*-* } }
> > +constexpr bool i5 = test_parameter(int{});     // { dg-error "destroying" }
> > +constexpr bool i6 = test_bindings<int>();
> > +
> > +struct Trivial { int x; };
> > +constexpr bool t1 = test_access<Trivial>();        // { dg-message "in .constexpr." }
> > +constexpr bool t2 = test_modification<Trivial>();  // { dg-message "in .constexpr." }
> > +constexpr bool t3 = test_scope<Trivial>();         // { dg-message "in .constexpr." }
> > +constexpr bool t4 = test_destroy_temp<Trivial>();  // { dg-message "in .constexpr." }
> > +constexpr bool t5 = test_parameter(Trivial{});     // { dg-error "destroying" }
> > +constexpr bool t6 = test_bindings<Trivial>();
> > +
> > +#if __cplusplus >= 202002L
> > +struct NonTrivial { int x; constexpr ~NonTrivial() {} };  // { dg-error "destroying" "" { target c++20 } }
> > +constexpr bool n1 = test_access<NonTrivial>();        // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n2 = test_modification<NonTrivial>();  // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n3 = test_scope<NonTrivial>();         // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n4 = test_destroy_temp<NonTrivial>();  // { dg-message "in .constexpr." "" { target c++20 } }
> > +constexpr bool n5 = test_parameter(NonTrivial{});     // { dg-error "destroying" "" { target c++20 } }
> > +constexpr bool n6 = test_bindings<NonTrivial>();
> > +#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 <bool V, int W>
> >  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
> > 

  reply	other threads:[~2023-11-27 11:08 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-03  1:18 Nathaniel Shead
2023-11-03  1:34 ` Nathaniel Shead
2023-11-27 11:08   ` Nathaniel Shead [this message]
2023-12-09 20:12 ` Jason Merrill
2023-12-10 10:22   ` Richard Biener
2023-12-10 11:21     ` Alexander Monakov
2023-12-10 15:58       ` Richard Biener
2023-12-10 18:34     ` Jason Merrill
2023-12-11  8:02       ` Richard Biener
2023-12-11 19:12         ` Jason Merrill
2023-12-11 19:17           ` Richard Biener
2023-12-11 19:21             ` Marek Polacek
2023-12-11 22:00               ` Jason Merrill
2023-12-11 22:22                 ` Marek Polacek
2023-12-11 23:03                 ` Jakub Jelinek
2023-12-12 11:13                   ` Alexander Monakov
2023-12-12 11:15                     ` Jakub Jelinek
2023-12-12 15:24                       ` Jason Merrill
2023-12-12 17:50                         ` Jason Merrill
2023-12-13  4:40                           ` Jason Merrill
2023-12-13 16:47                             ` [pushed 1/4] c++: copy location to AGGR_INIT_EXPR Jason Merrill
2023-12-13 16:47                               ` [pushed 2/4] c++: constant direct-initialization [PR108243] Jason Merrill
2023-12-13 16:47                               ` [pushed 3/4] c++: fix in-charge parm in constexpr Jason Merrill
2023-12-13 16:47                               ` [pushed 4/4] c++: End lifetime of objects in constexpr after destructor call [PR71093] Jason Merrill
2023-12-13 18:05                               ` [pushed 1/4] c++: copy location to AGGR_INIT_EXPR Patrick Palka
2023-12-13 20:06                                 ` [pushed] c++: TARGET_EXPR location in default arg [PR96997] Jason Merrill
2023-12-14  0:00                               ` [pushed 1/4] c++: copy location to AGGR_INIT_EXPR Marek Polacek
2023-12-14  1:38                                 ` Jason Merrill
2023-12-14 14:25                                   ` Marek Polacek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=656478c8.170a0220.6b0b.1db1@mx.google.com \
    --to=nathanieloshead@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jason@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).