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 <nathanielosh...@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 via email to