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