On 7/9/25 2:36 PM, Jakub Jelinek wrote:
On Wed, Jul 09, 2025 at 10:26:51AM -0400, Jason Merrill wrote:
I don't understand this comment, at least in connection with the above
snippet, that just handles the magic calls.

Sorry I wasn't clear, the comment was about the existing code that follows
the end of that hunk:

       if (!ctx->quiet)
         {
           if (!lambda_static_thunk_p (fun))
             error_at (loc, "call to non-%<constexpr%> function %qD", fun);
           explain_invalid_constexpr_fn (fun);
         }
       *non_constant_p = true;
       return t;

In C++26 we can't take this early exit before cxx_bind_parameters_in_call.

Ah, you're right.  Added a new testcase for this and had to change both
this spot and potential_constant_expression_1.

Here is what I'm going to test next:

2025-07-09  Jakub Jelinek  <ja...@redhat.com>

        PR c++/117785
gcc/c-family/
        * c-cppbuiltin.cc (c_cpp_builtins): Predefine
        __cpp_constexpr_exceptions=202411L for C++26.
gcc/cp/
        * constexpr.cc: Implement C++26 P3068R5 - constexpr exceptions.
        (class constexpr_global_ctx): Add caught_exceptions and
        uncaught_exceptions members.
        (constexpr_global_ctx::constexpr_global_ctx): Initialize
        uncaught_exceptions.
        (returns, breaks, continues, switches): Move earlier.
        (throws): New function.
        (exception_what_str, diagnose_std_terminate,
        diagnose_uncaught_exception): New functions.
        (enum cxa_builtin): New type.
        (cxx_cxa_builtin_fn_p, cxx_eval_cxa_builtin_fn): New functions.
        (cxx_eval_builtin_function_call): Add jump_target argument.  Call
        cxx_eval_cxa_builtin_fn for __builtin_eh_ptr_adjust_ref.  Adjust
        cxx_eval_constant_expression calls, if it results in jmp_target,
        set *jump_target to it and return.
        (cxx_bind_parameters_in_call): Add jump_target argument.  Pass
        it through to cxx_eval_constant_expression.  If it sets *jump_target,
        break.
        (fold_operand): Adjust cxx_eval_constant_expression caller.
        (cxx_eval_assert): Likewise.  If it set jmp_target, return true.
        (cxx_eval_internal_function): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression.  Return early if
        *jump_target after recursing on args.
        (cxx_eval_dynamic_cast_fn): Likewise.  Don't set reference_p for
        C++26 with -fexceptions.
        (cxx_eval_thunk_call): Add jump_target argument.  Pass it through
        to cxx_eval_constant_expression.
        (cxx_set_object_constness): Likewise.  Don't set TREE_READONLY if
        throws (jump_target).
        (cxx_eval_call_expression): Add jump_target argument.  Pass it
        through to cxx_eval_internal_function, cxx_eval_builtin_function_call,
        cxx_eval_thunk_call, cxx_eval_dynamic_cast_fn and
        cxx_set_object_constness.  Pass it through also
        cxx_eval_constant_expression on arguments, cxx_bind_parameters_in_call
        and cxx_fold_indirect_ref and for those cases return early if
        *jump_target.  Call cxx_eval_cxa_builtin_fn for cxx_cxa_builtin_fn_p
        functions.  For cxx_eval_constant_expression on body, pass address of
        cleared jmp_target automatic variable, if it throws propagate to
        *jump_target and make it non-cacheable.  For C++26 don't diagnose
        calls to non-constexpr functions before cxx_bind_parameters_in_call
        could report some argument throwing an exception.
        (cxx_eval_unary_expression): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression and return early if
        *jump_target after the call.
        (cxx_fold_pointer_plus_expression): Likewise.
        (cxx_eval_binary_expression): Likewise and similarly for
        cxx_fold_pointer_plus_expression call.
        (cxx_eval_conditional_expression): Pass jump_target to
        cxx_eval_constant_expression on first operand and return early if
        *jump_target after the call.
        (cxx_eval_vector_conditional_expression): Add jump_target argument.
        Pass it through to cxx_eval_constant_expression for all 3 arguments
        and return early if *jump_target after any of those calls.
        (get_array_or_vector_nelts): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression.
        (eval_and_check_array_index): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression calls and return early after
        each of them if *jump_target.
        (cxx_eval_array_reference): Likewise.
        (cxx_eval_component_reference): Likewise.
        (cxx_eval_bit_field_ref): Likewise.
        (cxx_eval_bit_cast): Likewise.  Assert CHECKING_P call doesn't
        throw or return.
        (cxx_eval_logical_expression): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression calls and return early after
        each of them if *jump_target.
        (cxx_eval_bare_aggregate): Likewise.
        (cxx_eval_vec_init_1): Add jump_target argument.  Pass it through
        to cxx_eval_bare_aggregate and recursive call.  Pass it through
        to get_array_or_vector_nelts and cxx_eval_constant_expression
        and return early after it if *jump_target.
        (cxx_eval_vec_init): Add jump_target argument.  Pass it through
        to cxx_eval_constant_expression and cxx_eval_vec_init_1.
        (cxx_union_active_member): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression and return early after it
        if *jump_target.
        (cxx_fold_indirect_ref_1): Add jump_target argument.  Pass it
        through to cxx_union_active_member and recursive calls.
        (cxx_eval_indirect_ref): Add jump_target argument.  Pass it through
        to cxx_fold_indirect_ref_1 calls and to recursive call, in which
        case return early after it if *jump_target.
        (cxx_fold_indirect_ref): Add jump_target argument.  Pass it through
        to cxx_fold_indirect_ref and cxx_eval_constant_expression calls and
        return early after those if *jump_target.
        (cxx_eval_trinary_expression): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression calls and return early after
        those if *jump_target.
        (cxx_eval_store_expression): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression and eval_and_check_array_index
        calls and return early after those if *jump_target.
        (cxx_eval_increment_expression): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression calls and return early after
        those if *jump_target.
        (label_matches): Handle VAR_DECL case.
        (cxx_eval_statement_list): Remove local_target variable and
        !jump_target handling.  Handle throws (jump_target) like returns or
        breaks.
        (cxx_eval_loop_expr): Remove local_target variable and !jump_target
        handling.  Pass it through to cxx_eval_constant_expression.  Handle
        throws (jump_target) like returns.
        (cxx_eval_switch_expr): Pass jump_target through to
        cxx_eval_constant_expression on cond, return early after it if
        *jump_target.
        (build_new_constexpr_heap_type): Add jump_target argument.  Pass it
        through to cxx_eval_constant_expression calls, return early after
        those if *jump_target.
        (cxx_eval_constant_expression): Make jump_target argument no longer
        defaulted, don't test jump_target for NULL.  Pass jump_target
        through to recursive calls, cxx_eval_call_expression,
        cxx_eval_store_expression, cxx_eval_indirect_ref,
        cxx_eval_unary_expression, cxx_eval_binary_expression,
        cxx_eval_logical_expression, cxx_eval_array_reference,
        cxx_eval_component_reference, cxx_eval_bit_field_ref,
        cxx_eval_vector_conditional_expression, cxx_eval_bare_aggregate,
        cxx_eval_vec_init, cxx_eval_trinary_expression, cxx_fold_indirect_ref,
        build_new_constexpr_heap_type, cxx_eval_increment_expression,
        cxx_eval_bit_cast and return earlyu after some of those if
        *jump_target as needed.
        (cxx_eval_constant_expression) <case TARGET_EXPR>: For C++26 push
        also CLEANUP_EH_ONLY cleanups, with NULL_TREE marker after them.
        (cxx_eval_constant_expression) <case RETURN_EXPR>: Don't override
        *jump_target if throws (jump_target).
        (cxx_eval_constant_expression) <case TRY_CATCH_EXPR, case TRY_BLOCK,
        case MUST_NOT_THROW_EXPR, case TRY_FINALLY_EXPR, case CLEANUP_STMT>:
        Handle C++26 constant expressions.
        (cxx_eval_constant_expression) <case CLEANUP_POINT_EXPR>: For C++26
        with throws (jump_target) evaluate the CLEANUP_EH_ONLY cleanups as
        well, and if not throws (jump_target) skip those.  Set *jump_target
        if some of the cleanups threw.
        (cxx_eval_constant_expression) <case THROW_EXPR>: Recurse on operand
        for C++26.
        (cxx_eval_outermost_constant_expr): Diagnose uncaught exceptions both
        from main expression and cleanups, diagnose also
        break/continue/returns from the main expression.  Handle
        CLEANUP_EH_ONLY cleanup markers.  Don't diagnose mutable poison stuff
        if non_constant_p.  Use different diagnostics for non-deleted heap
        allocations if they were allocated by __cxa_allocate_exception.
        (struct check_for_return_continue_data): Add could_throw field.
        (check_for_return_continue): Handle AGGR_INIT_EXPR and CALL_EXPR and
        set d->could_throw if they could throw.
        (potential_constant_expression_1): For CALL_EXPR allow
        cxx_dynamic_cast_fn_p calls.  For C++26 set *jump_target to void_node
        for calls that could throw.  For C++26 if call to non-constexpr call
        is seen, try to evaluate arguments first and if they could throw,
        don't diagnose call to non-constexpr function nor return false.
        Adjust check_for_return_continue_data initializers and set
        *jump_target to void_node if data.could_throw_p.  For C++26 recurse on
        THROW_EXPR argument.  Add comment explaining TRY_BLOCK handling with
        C++26 exceptions.  Handle throws like returns in some cases.
        * cp-tree.h (MUST_NOT_THROW_NOEXCEPT_P, MUST_NOT_THROW_THROW_P,
        MUST_NOT_THROW_CATCH_P, DECL_EXCEPTION_REFCOUNT): Define.
        (DECL_LOCAL_DECL_P): Fix comment typo, VARIABLE_DECL -> VAR_DECL.
        (enum cp_built_in_function): Add CP_BUILT_IN_EH_PTR_ADJUST_REF,
        (handler_match_for_exception_type): Declare.
        * call.cc (handler_match_for_exception_type): New function.
        * except.cc (initialize_handler_parm): Set MUST_NOT_THROW_CATCH_P
        on newly created MUST_NOT_THROW_EXPR.
        (begin_eh_spec_block): Set MUST_NOT_THROW_NOEXCEPT_P.
        (wrap_cleanups_r): Set MUST_NOT_THROW_THROW_P.
        (build_throw): Add another TARGET_EXPR whose scope spans
        until after the __cxa_throw call and copy pointer value from ptr
        to it and use it in __cxa_throw argument.
        * tree.cc (builtin_valid_in_constant_expr_p): Handle
        CP_BUILT_IN_EH_PTR_ADJUST_REF.
        * decl.cc (cxx_init_decl_processing): Initialize
        __builtin_eh_ptr_adjust_ref FE builtin.
        * pt.cc (tsubst_stmt) <case MUST_NOT_THROW_EXPR>: Copy the
        MUST_NOT_THROW_NOEXCEPT_P, MUST_NOT_THROW_THROW_P and
        MUST_NOT_THROW_CATCH_P flags.
        * cp-gimplify.cc (cp_gimplify_expr) <case CALL_EXPR>: Error on
        non-folded CP_BUILT_IN_EH_PTR_ADJUST_REF calls.
gcc/testsuite/
        * g++.dg/cpp0x/constexpr-throw.C: Expect different diagnostics for
        C++26.
        * g++.dg/cpp1y/constexpr-84192.C: Expect different diagnostics.
        * g++.dg/cpp1y/constexpr-throw.C: Expect different diagnostics for
        C++26.
        * g++.dg/cpp1z/constexpr-asm-5.C: Likewise.
        * g++.dg/cpp26/constexpr-eh1.C: New test.
        * g++.dg/cpp26/constexpr-eh2.C: New test.
        * g++.dg/cpp26/constexpr-eh3.C: New test.
        * g++.dg/cpp26/constexpr-eh4.C: New test.
        * g++.dg/cpp26/constexpr-eh5.C: New test.
        * g++.dg/cpp26/constexpr-eh6.C: New test.
        * g++.dg/cpp26/constexpr-eh7.C: New test.
        * g++.dg/cpp26/constexpr-eh8.C: New test.
        * g++.dg/cpp26/constexpr-eh9.C: New test.
        * g++.dg/cpp26/constexpr-eh10.C: New test.
        * g++.dg/cpp26/constexpr-eh11.C: New test.
        * g++.dg/cpp26/constexpr-eh12.C: New test.
        * g++.dg/cpp26/constexpr-eh13.C: New test.
        * g++.dg/cpp26/constexpr-eh14.C: New test.
        * g++.dg/cpp26/constexpr-eh15.C: New test.
        * g++.dg/cpp26/feat-cxx26.C: Change formatting in __cpp_pack_indexing
        and __cpp_pp_embed test.  Add __cpp_constexpr_exceptions test.
        * g++.dg/cpp26/static_assert1.C: Expect different diagnostics for
        C++26.
        * g++.dg/cpp2a/consteval34.C: Likewise.
        * g++.dg/cpp2a/consteval-memfn1.C: Likewise.
        * g++.dg/cpp2a/constexpr-dynamic4.C: For C++26 add std::exception and
        std::bad_cast definitions and expect different diagnostics.
        * g++.dg/cpp2a/constexpr-dynamic6.C: Likewise.
        * g++.dg/cpp2a/constexpr-dynamic7.C: Likewise.
        * g++.dg/cpp2a/constexpr-dynamic8.C: Likewise.
        * g++.dg/cpp2a/constexpr-dynamic9.C: Likewise.
        * g++.dg/cpp2a/constexpr-dynamic11.C: Likewise.
        * g++.dg/cpp2a/constexpr-dynamic14.C: Likewise.
        * g++.dg/cpp2a/constexpr-dynamic18.C: Likewise.
        * g++.dg/cpp2a/constexpr-new27.C: New test.
        * g++.dg/cpp2a/constexpr-typeid5.C: New test.
libstdc++-v3/
        * include/bits/version.def (constexpr_exceptions): New.
        * include/bits/version.h: Regenerate.
        * libsupc++/exception (std::bad_exception::bad_exception): Add
        _GLIBCXX26_CONSTEXPR.
        (std::bad_exception::~bad_exception, std::bad_exception::what): For
        C++26 add constexpr and define inline.
        * libsupc++/exception.h (std::exception::exception,
        std::exception::operator=): Add _GLIBCXX26_CONSTEXPR.
        (std::exception::~exception, std::exception::what): For C++26 add
        constexpr and define inline.
        * libsupc++/exception_ptr.h (std::make_exception_ptr): Add
        _GLIBCXX26_CONSTEXPR.  For if consteval use just throw with
        current_exception() in catch.
        (std::exception_ptr::exception_ptr(void*)): For C++26 add constexpr
        and define inline.
        (std::exception_ptr::exception_ptr()): Add _GLIBCXX26_CONSTEXPR.
        (std::exception_ptr::exception_ptr(const exception_ptr&)): Likewise.
        Use __builtin_eh_ptr_adjust_ref if consteval and compiler has it
        instead of _M_addref.
        (std::exception_ptr::exception_ptr(nullptr_t)): Add
        _GLIBCXX26_CONSTEXPR.
        (std::exception_ptr::exception_ptr(exception_ptr&&)): Likewise.
        (std::exception_ptr::operator=): Likewise.
        (std::exception_ptr::~exception_ptr): Likewise.  Use
        __builtin_eh_ptr_adjust_ref if consteval and compiler has it
        instead of _M_release.
        (std::exception_ptr::swap): Add _GLIBCXX26_CONSTEXPR.
        (std::exception_ptr::operator bool): Likewise.
        (std::exception_ptr::operator==): Likewise.
        * libsupc++/nested_exception.h
        (std::nested_exception::nested_exception): Add _GLIBCXX26_CONSTEXPR.
        (std::nested_exception::operator=): Likewise.
        (std::nested_exception::~nested_exception): For C++26 add constexpr
        and define inline.
        (std::nested_exception::rethrow_if_nested): Add _GLIBCXX26_CONSTEXPR.
        (std::nested_exception::nested_ptr): Likewise.
        (std::_Nested_exception::_Nested_exception): Likewise.
        (std::throw_with_nested, std::rethrow_if_nested): Likewise.
        * libsupc++/new (std::bad_alloc::bad_alloc): Likewise.
        (std::bad_alloc::operator=): Likewise.
        (std::bad_alloc::~bad_alloc): For C++26 add constexpr and define
        inline.
        (std::bad_alloc::what): Likewise.
        (std::bad_array_new_length::bad_array_new_length): Add
        _GLIBCXX26_CONSTEXPR.
        (std::bad_array_new_length::~bad_array_new_length): For C++26 add
        constexpr and define inline.
        (std::bad_array_new_length::what): Likewise.
        * libsupc++/typeinfo (std::bad_cast::bad_cast): Add
        _GLIBCXX26_CONSTEXPR.
        (std::bad_cast::~bad_cast): For C++26 add constexpr and define inline.
        (std::bad_cast::what): Likewise.
        (std::bad_typeid::bad_typeid): Add _GLIBCXX26_CONSTEXPR.
        (std::bad_typeid::~bad_typeid): For C++26 add constexpr and define
        inline.
        (std::bad_typeid::what): Likewise.

--- gcc/c-family/c-cppbuiltin.cc.jj     2025-07-08 19:06:41.569662820 +0200
+++ gcc/c-family/c-cppbuiltin.cc        2025-07-09 08:23:15.048898304 +0200
@@ -1087,6 +1087,7 @@ c_cpp_builtins (cpp_reader *pfile)
        {
          /* Set feature test macros for C++26.  */
          cpp_define (pfile, "__cpp_constexpr=202406L");
+         cpp_define (pfile, "__cpp_constexpr_exceptions=202411L");
          cpp_define (pfile, "__cpp_static_assert=202306L");
          cpp_define (pfile, "__cpp_placeholder_variables=202306L");
          cpp_define (pfile, "__cpp_structured_bindings=202403L");
--- gcc/cp/constexpr.cc.jj      2025-07-09 13:30:46.086784510 +0200
+++ gcc/cp/constexpr.cc 2025-07-09 20:25:36.024614779 +0200
@@ -1184,6 +1184,10 @@ public:
    /* Heap VAR_DECLs created during the evaluation of the outermost constant
       expression.  */
    auto_vec<tree, 16> heap_vars;
+  /* Vector of caught exceptions, including exceptions still not active at
+     the start of a handler (those are immediately followed up by HANDLER_TYPE
+     until __cxa_begin_catch finishes).  */
+  auto_vec<tree, 2> caught_exceptions;
    /* Cleanups that need to be evaluated at the end of CLEANUP_POINT_EXPR.  */
    vec<tree> *cleanups;
    /* If non-null, only allow modification of existing values of the variables
@@ -1191,10 +1195,13 @@ public:
    hash_set<tree> *modifiable;
    /* Number of heap VAR_DECL deallocations.  */
    unsigned heap_dealloc_count;
+  /* Number of uncaught exceptions.  */
+  unsigned uncaught_exceptions;
+
    /* Constructor.  */
    constexpr_global_ctx ()
      : constexpr_ops_count (0), cleanups (NULL), modifiable (nullptr),
-      heap_dealloc_count (0) {}
+      heap_dealloc_count (0), uncaught_exceptions (0) {}
bool is_outside_lifetime (tree t)
    {
@@ -1308,6 +1315,48 @@ struct constexpr_ctx {
    mce_value manifestly_const_eval;
  };
+/* Predicates for the meaning of *jump_target. */
+
+static bool
+returns (tree *jump_target)
+{
+  return *jump_target && TREE_CODE (*jump_target) == RETURN_EXPR;
+}
+
+static bool
+breaks (tree *jump_target)
+{
+  return (*jump_target
+         && ((TREE_CODE (*jump_target) == LABEL_DECL
+              && LABEL_DECL_BREAK (*jump_target))
+             || TREE_CODE (*jump_target) == BREAK_STMT
+             || TREE_CODE (*jump_target) == EXIT_EXPR));
+}
+
+static bool
+continues (tree *jump_target)
+{
+  return (*jump_target
+         && ((TREE_CODE (*jump_target) == LABEL_DECL
+              && LABEL_DECL_CONTINUE (*jump_target))
+             || TREE_CODE (*jump_target) == CONTINUE_STMT));
+}
+
+static bool
+switches (tree *jump_target)
+{
+  return *jump_target && TREE_CODE (*jump_target) == INTEGER_CST;
+}
+
+static bool
+throws (tree *jump_target)
+{
+  /* void_node is for use in potential_constant_expression_1, otherwise
+     it should an artificial VAR_DECL created by constant evaluation
+     of __cxa_allocate_exception ().  */
+  return (*jump_target && (VAR_P (*jump_target) || *jump_target == void_node));
+}
+
  /* True if the constexpr relaxations afforded by P2280R4 for unknown
     references and objects are in effect.  */
@@ -1543,13 +1592,675 @@ enum value_cat {
  };
static tree cxx_eval_constant_expression (const constexpr_ctx *, tree,
-                                         value_cat, bool *, bool *, tree * = 
NULL);
+                                         value_cat, bool *, bool *, tree *);
  static tree cxx_eval_bare_aggregate (const constexpr_ctx *, tree,
-                                    value_cat, bool *, bool *);
+                                    value_cat, bool *, bool *, tree *);
  static tree cxx_fold_indirect_ref (const constexpr_ctx *, location_t, tree, 
tree,
-                                  bool * = NULL);
+                                  bool *, tree *);
  static tree find_heap_var_refs (tree *, int *, void *);
+/* For exception object EXC if it has class type and usable what () method
+   which returns cv char * return the xmalloced string literal which it returns
+   if possible, otherwise return NULL.  */
+
+static char *
+exception_what_str (const constexpr_ctx *ctx, tree exc)
+{
+  tree type = strip_array_types (TREE_TYPE (exc));
+  if (!CLASS_TYPE_P (type))
+    return NULL;
+  tree std_exception = lookup_qualified_name (std_node, "exception",
+                                             LOOK_want::NORMAL, false);
+  if (TREE_CODE (std_exception) != TYPE_DECL)
+    return NULL;
+  if (!CLASS_TYPE_P (TREE_TYPE (std_exception)))
+    return NULL;
+  base_kind b_kind;
+  tree binfo = lookup_base (type, TREE_TYPE (std_exception), ba_check, &b_kind,
+                           tf_none);
+  if (binfo == NULL_TREE || binfo == error_mark_node)
+    return NULL;
+  if (type != TREE_TYPE (exc))
+    exc = build4 (ARRAY_REF, type, exc, size_zero_node, NULL, NULL);
+  tree call
+    = finish_class_member_access_expr (exc, get_identifier ("what"), false,
+                                      tf_none);
+  if (call == error_mark_node)
+    return NULL;
+  releasing_vec what_args;
+  call = finish_call_expr (call, &what_args, false, false, tf_none);
+  if (call == error_mark_node)
+    return NULL;
+  if (TREE_CODE (TREE_TYPE (call)) != POINTER_TYPE
+      || !INTEGRAL_TYPE_P (TREE_TYPE (TREE_TYPE (call)))
+      || !COMPLETE_TYPE_P (TREE_TYPE (TREE_TYPE (call)))
+      || !tree_int_cst_equal (TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (call))),
+                             TYPE_SIZE_UNIT (char_type_node))
+      || TYPE_PRECISION (TREE_TYPE (TREE_TYPE (call))) != BITS_PER_UNIT)
+    return NULL;
+  if (!potential_constant_expression (call))
+    return NULL;
+  bool non_constant_p = false, overflow_p = false;
+  tree jmp_target = NULL;
+  tree ptr = cxx_eval_constant_expression (ctx, call, vc_prvalue,
+                                          &non_constant_p, &overflow_p,
+                                          &jmp_target);
+  if (!throws (&jmp_target)
+      && !non_constant_p

I think you want to return at this point if throws or non_constant_p.

+      && reduced_constant_expression_p (ptr))
+    if (const char *msg = c_getstr (ptr))
+      return xstrdup (msg);
+  auto_vec <char, 32> v;
+  for (unsigned i = 0; i < INT_MAX; ++i)
+    {
+      tree t = call;
+      if (i)
+       t = build2 (POINTER_PLUS_EXPR, TREE_TYPE (ptr), ptr, size_int (i));
+      t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t);
+      non_constant_p = false;
+      overflow_p = false;
+      jmp_target = NULL;
+      tree t2 = cxx_eval_constant_expression (ctx, t, vc_prvalue,
+                                             &non_constant_p, &overflow_p,
+                                             &jmp_target);
+      if (throws (&jmp_target)
+         || non_constant_p
+         || !tree_fits_shwi_p (t2))
+       return NULL;
+      char c = tree_to_shwi (t2);
+      v.safe_push (c);
+      if (c == '\0')
+       break;
+    }
+  return xstrdup (v.address ());
+}
+
+/* Diagnose constant expression evaluation encountering call to
+   std::terminate due to exception EXC.  */
+
+static void
+diagnose_std_terminate (location_t loc, const constexpr_ctx *ctx, tree exc)
+{
+  tree type = strip_array_types (TREE_TYPE (exc));
+  if (char *str = exception_what_str (ctx, exc))
+    {
+      error_at (loc, "%qs called after throwing an exception of type %qT; "
+                    "%<what()%>: %qs", "std::terminate", type, str);
+      free (str);
+    }
+  else
+    {
+      if (type != TREE_TYPE (exc))
+       exc = build4 (ARRAY_REF, type, exc, size_zero_node, NULL, NULL);
+      bool non_constant_p = false, overflow_p = false;
+      tree jmp_target = NULL;
+      tree val = cxx_eval_constant_expression (ctx, exc, vc_prvalue,
+                                              &non_constant_p, &overflow_p,
+                                              &jmp_target);
+      if (!throws (&jmp_target)
+         && !non_constant_p

Hmm, neither of these should be possible; any throwing or non-constant operations would have happened at the point of throw, they shouldn't have made it into the exception. Maybe change these two tests to an assert?

+         && reduced_constant_expression_p (val))

And a value doesn't need to be constant to be printable, we should be able to print it unconditionally.

+       error_at (loc, "%qs called after throwing an exception %qE",
+                 "std::terminate", val);
+      else
+       error_at (loc, "%qs called after throwing an exception of type %qT",
+                 "std::terminate", type);
+    }
+}
+
+/* Diagnose constant expression evaluation encountering call to
+   uncaught exception EXC.  */
+
+static void
+diagnose_uncaught_exception (location_t loc, const constexpr_ctx *ctx, tree 
exc)
+{
+  tree type = strip_array_types (TREE_TYPE (exc));
+  if (char *str = exception_what_str (ctx, exc))
+    {
+      error_at (loc, "uncaught exception of type %qT; %<what()%>: %qs", type, 
str);
+      free (str);
+    }
+  else
+    {
+      if (type != TREE_TYPE (exc))
+       exc = build4 (ARRAY_REF, type, exc, size_zero_node, NULL, NULL);
+      bool non_constant_p = false, overflow_p = false;
+      tree jmp_target = NULL;
+      tree val = cxx_eval_constant_expression (ctx, exc, vc_prvalue,
+                                              &non_constant_p, &overflow_p,
+                                              &jmp_target);
+      if (!throws (&jmp_target)
+         && !non_constant_p
+         && reduced_constant_expression_p (val))

As above.

+       error_at (loc, "uncaught exception %qE", val);
+      else
+       error_at (loc, "uncaught exception of type %qT", type);
+    }
+}
+
+/* Kinds of __cxa_* functions (and a few other EH related ones) we handle as
+   magic constexpr functions for C++26.  */
+
+enum cxa_builtin {
+  CXA_NONE = 0,
+  CXA_ALLOCATE_EXCEPTION = 1,
+  CXA_FREE_EXCEPTION = 2,
+  CXA_THROW = 3,
+  CXA_BEGIN_CATCH = 4,
+  CXA_END_CATCH = 5,
+  CXA_RETHROW = 6,
+  CXA_GET_EXCEPTION_PTR = 7,
+  CXA_BAD_CAST = 8,
+  CXA_BAD_TYPEID = 9,
+  CXA_THROW_BAD_ARRAY_NEW_LENGTH = 10,
+  STD_UNCAUGHT_EXCEPTIONS = 11,
+  STD_CURRENT_EXCEPTION = 12,
+  STD_RETHROW_EXCEPTION = 13,
+  BUILTIN_EH_PTR_ADJUST_REF = 14
+};
+
+/* Return cxa_builtin if FNDECL is a __cxa_* function handled as
+   magic constexpr function for C++26.  Return CXA_NONE otherwise.  */
+
+static enum cxa_builtin
+cxx_cxa_builtin_fn_p (tree fndecl)
+{
+  if (cxx_dialect < cxx26)
+    return CXA_NONE;
+  if (DECL_LANGUAGE (fndecl) != lang_c)
+    {
+      if (!decl_in_std_namespace_p (fndecl))
+       return CXA_NONE;
+      if (id_equal (DECL_NAME (fndecl), "uncaught_exceptions"))
+       return STD_UNCAUGHT_EXCEPTIONS;
+      if (id_equal (DECL_NAME (fndecl), "current_exception"))
+       return STD_CURRENT_EXCEPTION;
+      if (id_equal (DECL_NAME (fndecl), "rethrow_exception"))
+       return STD_RETHROW_EXCEPTION;
+      return CXA_NONE;
+    }
+  if (!startswith (IDENTIFIER_POINTER (DECL_NAME (fndecl)), "__cxa_"))
+    return CXA_NONE;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_allocate_exception"))
+    return CXA_ALLOCATE_EXCEPTION;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_free_exception"))
+    return CXA_FREE_EXCEPTION;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_throw"))
+    return CXA_THROW;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_begin_catch"))
+    return CXA_BEGIN_CATCH;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_end_catch"))
+    return CXA_END_CATCH;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_rethrow"))
+    return CXA_RETHROW;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_get_exception_ptr"))
+    return CXA_GET_EXCEPTION_PTR;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_bad_cast"))
+    return CXA_BAD_CAST;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_bad_typeid"))
+    return CXA_BAD_TYPEID;
+  if (id_equal (DECL_NAME (fndecl), "__cxa_throw_bad_array_new_length"))
+    return CXA_THROW_BAD_ARRAY_NEW_LENGTH;
+  return CXA_NONE;
+}
+
+/* Helper function for cxx_eval_cxa_builtin_fn.
+   Check if ARG is a valid first argument of __cxa_throw or
+   __cxa_free_exception or __builtin_eh_ptr_adjust_ref.  Return NULL_TREE if
+   not, otherwise return the artificial __cxa_allocate_exception allocated
+   VAR_DECL.  FREE_EXC is true for __cxa_free_exception, false otherwise.  */
+
+static tree
+cxa_check_throw_arg (tree arg, bool free_exc)
+{
+  STRIP_NOPS (arg);
+  if (TREE_CODE (arg) != ADDR_EXPR)
+    return NULL_TREE;
+  arg = TREE_OPERAND (arg, 0);
+  if (!VAR_P (arg)
+      || !DECL_ARTIFICIAL (arg)
+      || ((!free_exc || DECL_NAME (arg) != heap_uninit_identifier)
+         && DECL_NAME (arg) != heap_identifier)
+      || !DECL_LANG_SPECIFIC (arg))
+    return NULL_TREE;
+  return arg;
+}
+
+/* Helper function for cxx_eval_cxa_builtin_fn.
+   "Allocate" on the constexpr heap an exception object of TYPE
+   with REFCOUNT.  */
+
+static tree
+cxa_allocate_exception (location_t loc, tree type, tree refcount)
+{
+  tree var = build_decl (loc, VAR_DECL, heap_uninit_identifier, type);
+  DECL_ARTIFICIAL (var) = 1;
+  retrofit_lang_decl (var);
+  DECL_EXCEPTION_REFCOUNT (var) = refcount;

Is there a reason not to add it to heap_vars here?

+  return var;
+}
+
+/* Evaluate various __cxa_* calls as magic constexpr builtins for
+   C++26 constexpr exception support (P3068R5).  */
+
+static tree
+cxx_eval_cxa_builtin_fn (const constexpr_ctx *ctx, tree call,
+                        enum cxa_builtin kind, tree fndecl,
+                        bool *non_constant_p, bool *overflow_p,
+                        tree *jump_target)
+{
+  int nargs = call_expr_nargs (call);
+  location_t loc = cp_expr_loc_or_input_loc (call);
+  tree args[4], arg;
+  if (nargs > 4)
+    {
+    invalid_nargs:
+      if (!ctx->quiet)
+       error_at (loc, "call to %qD function with incorrect"
+                 "number of arguments", fndecl);
+      *non_constant_p = true;
+      return call;
+    }
+  if ((kind == CXA_BEGIN_CATCH || kind == CXA_GET_EXCEPTION_PTR)
+      && nargs == 1
+      && (arg = CALL_EXPR_ARG (call, 0))
+      && TREE_CODE (arg) == CALL_EXPR
+      && call_expr_nargs (arg) == 1
+      && integer_zerop (CALL_EXPR_ARG (arg, 0)))
+    if (tree fun = get_function_named_in_call (arg))
+      if (fndecl_built_in_p (fun, BUILT_IN_EH_POINTER))
+       {
+         if (ctx->global->caught_exceptions.length () < 2)
+           {
+           no_caught_exceptions:
+             if (!ctx->quiet)
+               error_at (loc, "%qD called with no caught exceptions pending",
+                         fndecl);
+             *non_constant_p = true;
+             return call;
+           }
+         /* Both __cxa_get_exception_ptr (__builtin_eh_pointer (0))
+            and __cxa_begin_catch (__builtin_eh_pointer (0)) calls expect
+            ctx->global->caught_exceptions vector to end with
+            __cxa_allocate_exception created artificial VAR_DECL (the
+            exception object) followed by handler type, pushed by TRY_BLOCK
+            evaluation.  The only difference between the functions is that
+            __cxa_begin_catch pops the handler type from the vector and keeps
+            the VAR_DECL last and decreases uncaught_exceptions.  The
+            VAR_DECL after __cxa_begin_catch serves as the current exception
+            and is then popped in __cxa_end_catch evaluation.  */
+         tree handler_type = ctx->global->caught_exceptions.last ();
+         if (handler_type && VAR_P (handler_type))
+           goto no_caught_exceptions;
+         unsigned idx = ctx->global->caught_exceptions.length () - 2;
+         arg = ctx->global->caught_exceptions[idx];
+         gcc_assert (VAR_P (arg));
+         if (kind == CXA_BEGIN_CATCH)
+           {
+             ctx->global->caught_exceptions.pop ();
+             --ctx->global->uncaught_exceptions;
+           }
+         if (handler_type == NULL_TREE)
+           /* __cxa_begin_catch used for catch (...).  Just return

Let's drop "__cxa_begin_catch" from this and the two comments below since this also applies to __cxa_get_exception_ptr.

+              void.  */
+           return void_node;
+         else if (POINTER_TYPE_P (handler_type))
+           {
+             /* __cxa_begin_catch used for catch of a pointer.  */
+             if (TREE_CODE (TREE_TYPE (arg)) == ARRAY_TYPE)
+               arg = build4 (ARRAY_REF, TREE_TYPE (TREE_TYPE (arg)), arg,
+                             size_zero_node, NULL_TREE, NULL_TREE);
+             arg = cp_convert (handler_type, arg,
+                               ctx->quiet ? tf_none : tf_warning_or_error);
+             if (arg == error_mark_node)
+               {
+                 *non_constant_p = true;
+                 return call;
+               }
+           }
+         else
+           {
+             /* __cxa_begin_catch used for catch of a non-pointer type.  */
+             tree exc_type = strip_array_types (TREE_TYPE (arg));
+             tree exc_ptr_type = build_pointer_type (exc_type);
+             arg = build_fold_addr_expr_with_type (arg, exc_ptr_type);
+             if (CLASS_TYPE_P (handler_type))
+               {
+                 tree ptr_type = build_pointer_type (handler_type);
+                 arg = cp_convert (ptr_type, arg,
+                                   ctx->quiet ? tf_none
+                                   : tf_warning_or_error);
+                 if (arg == error_mark_node)
+                   {
+                     *non_constant_p = true;
+                     return call;
+                   }
+               }
+           }
+         return cxx_eval_constant_expression (ctx, arg, vc_prvalue,
+                                              non_constant_p, overflow_p,
+                                              jump_target);
+       }
+  for (int i = 0; i < nargs; ++i)
+    {
+      args[i] = cxx_eval_constant_expression (ctx, CALL_EXPR_ARG (call, i),
+                                             vc_prvalue, non_constant_p,
+                                             overflow_p, jump_target);
+      if (*non_constant_p)
+       return call;
+      if (*jump_target)
+       return NULL_TREE;
+    }
+  switch (kind)
+    {
+    case CXA_ALLOCATE_EXCEPTION:
+      if (nargs != 1)
+       goto invalid_nargs;
+      if (!tree_fits_uhwi_p (args[0]))
+       {
+         if (!ctx->quiet)
+           error_at (loc, "cannot allocate exception: size not constant");
+         *non_constant_p = true;
+         return call;
+       }
+      else
+       {
+         tree type = build_array_type_nelts (char_type_node,
+                                             tree_to_uhwi (args[0]));
+         tree var = cxa_allocate_exception (loc, type, size_zero_node);
+         ctx->global->heap_vars.safe_push (var);
+         ctx->global->put_value (var, NULL_TREE);
+         return fold_convert (ptr_type_node, build_address (var));
+       }
+    case CXA_FREE_EXCEPTION:
+      if (nargs != 1)
+       goto invalid_nargs;
+      arg = cxa_check_throw_arg (args[0], true);
+      if (arg == NULL_TREE)
+       {
+       invalid_ptr:
+         if (!ctx->quiet)
+           error_at (loc, "first argument to %qD function not result of "
+                     "%<__cxa_allocate_exception%>", fndecl);
+         *non_constant_p = true;
+         return call;
+       }
+      DECL_NAME (arg) = heap_deleted_identifier;
+      ctx->global->destroy_value (arg);
+      ctx->global->heap_dealloc_count++;
+      return void_node;
+    case CXA_THROW:
+      if (nargs != 3)
+       goto invalid_nargs;
+      arg = cxa_check_throw_arg (args[0], false);
+      if (arg == NULL_TREE)
+       goto invalid_ptr;
+      DECL_EXCEPTION_REFCOUNT (arg)
+       = size_binop (PLUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg),
+                     size_one_node);
+      ++ctx->global->uncaught_exceptions;
+      *jump_target = arg;
+      return void_node;
+    case CXA_BEGIN_CATCH:
+    case CXA_GET_EXCEPTION_PTR:
+      goto invalid_nargs;
+    case CXA_END_CATCH:
+      if (nargs != 0)
+       goto invalid_nargs;
+      if (ctx->global->caught_exceptions.is_empty ())
+       {
+       no_active_exc:
+         if (!ctx->quiet)
+           error_at (loc, "%qD called with no caught exceptions active",
+                     fndecl);
+         *non_constant_p = true;
+         return call;
+       }
+      else
+       {
+         arg = ctx->global->caught_exceptions.pop ();
+         if (arg == NULL_TREE || !VAR_P (arg))
+           goto no_active_exc;
+       free_except:
+         DECL_EXCEPTION_REFCOUNT (arg)
+           = size_binop (MINUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg),
+                         size_one_node);
+         if (integer_zerop (DECL_EXCEPTION_REFCOUNT (arg)))
+           {
+             if (type_build_dtor_call (TREE_TYPE (arg)))
+               {
+                 tree cleanup
+                   = cxx_maybe_build_cleanup (arg, (ctx->quiet ? tf_none
+                                                    : tf_warning_or_error));
+                 if (cleanup == error_mark_node)
+                   *non_constant_p = true;
+                 tree jmp_target = NULL_TREE;
+                 cxx_eval_constant_expression (ctx, cleanup, vc_discard,
+                                               non_constant_p, overflow_p,
+                                               &jmp_target);
+                 if (throws (&jmp_target))
+                   *jump_target = jmp_target;
+               }
+             DECL_NAME (arg) = heap_deleted_identifier;
+             ctx->global->destroy_value (arg);
+             ctx->global->heap_dealloc_count++;
+           }
+       }
+      return void_node;
+    case CXA_RETHROW:
+      if (nargs != 0)
+       goto invalid_nargs;
+      unsigned idx;
+      FOR_EACH_VEC_ELT_REVERSE (ctx->global->caught_exceptions, idx, arg)
+       if (arg == NULL_TREE || !VAR_P (arg))
+         --idx;
+       else
+         break;
+      if (arg == NULL_TREE)
+       {
+         if (!ctx->quiet)
+           error_at (loc, "%qD called with no caught exceptions active",
+                     fndecl);
+         *non_constant_p = true;
+         return call;
+       }
+      DECL_EXCEPTION_REFCOUNT (arg)
+       = size_binop (PLUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg), size_one_node);
+      ++ctx->global->uncaught_exceptions;
+      *jump_target = arg;
+      return void_node;
+    case CXA_BAD_CAST:
+    case CXA_BAD_TYPEID:
+    case CXA_THROW_BAD_ARRAY_NEW_LENGTH:
+      if (nargs != 0)
+       goto invalid_nargs;
+      else
+       {
+         tree name;
+         switch (kind)
+           {
+           case CXA_BAD_CAST:
+             name = get_identifier ("bad_cast");
+             break;
+           case CXA_BAD_TYPEID:
+             name = get_identifier ("bad_typeid");
+             break;
+           case CXA_THROW_BAD_ARRAY_NEW_LENGTH:
+             name = get_identifier ("bad_array_new_length");
+             break;
+           default:
+             gcc_unreachable ();
+           }
+         tree decl = lookup_qualified_name (std_node, name);
+         if (TREE_CODE (decl) != TYPE_DECL
+             || !CLASS_TYPE_P (TREE_TYPE (decl))
+             || !type_build_ctor_call (TREE_TYPE (decl)))
+           {
+             if (!ctx->quiet)
+               error_at (loc, "%qD called without %<std::%D%> being defined",
+                         fndecl, name);
+             *non_constant_p = true;
+             return call;
+           }
+         tree type = TREE_TYPE (decl);
+         tree var = cxa_allocate_exception (loc, type, size_one_node);
+         tree ctor
+           = build_special_member_call (var, complete_ctor_identifier,
+                                        NULL, type, LOOKUP_NORMAL,
+                                        ctx->quiet ? tf_none
+                                        : tf_warning_or_error);
+         if (ctor == error_mark_node)
+           {
+             *non_constant_p = true;
+             return call;
+           }
+         ctx->global->heap_vars.safe_push (var);
+         if (TREE_CONSTANT (ctor))
+           ctx->global->put_value (var, ctor);
+         else
+           {
+             ctx->global->put_value (var, NULL_TREE);
+             cxx_eval_constant_expression (ctx, ctor, vc_discard,
+                                           non_constant_p, overflow_p,
+                                           jump_target);
+             if (*non_constant_p)
+               return call;
+             if (throws (jump_target))
+               return NULL_TREE;
+           }
+         ++ctx->global->uncaught_exceptions;
+         *jump_target = var;
+       }
+      return void_node;
+    case STD_UNCAUGHT_EXCEPTIONS:
+      if (nargs != 0)
+       goto invalid_nargs;
+      /* Similarly to __builtin_is_constant_evaluated (), we don't
+        want to give a definite answer during mce_unknown evaluation,
+        because that might prevent evaluation later on when some
+        exceptions might be uncaught.  But unlike that, we don't
+        want to constant fold it even during cp_fold, because at runtime
+        std::uncaught_exceptions () might still be non-zero.  */
+      if (ctx->manifestly_const_eval != mce_true)
+       {
+         *non_constant_p = true;
+         return call;
+       }
+      return build_int_cst (integer_type_node,
+                           ctx->global->uncaught_exceptions);
+    case STD_CURRENT_EXCEPTION:
+      if (nargs != 0)
+       goto invalid_nargs;
+      else
+       {
+         tree name = get_identifier ("exception_ptr");
+         tree decl = lookup_qualified_name (std_node, name);
+         tree fld;
+         if (TREE_CODE (decl) != TYPE_DECL
+             || !CLASS_TYPE_P (TREE_TYPE (decl))
+             || !COMPLETE_TYPE_P (TREE_TYPE (decl))
+             || !(fld = next_aggregate_field (TYPE_FIELDS (TREE_TYPE (decl))))
+             || DECL_ARTIFICIAL (fld)
+             || TREE_CODE (TREE_TYPE (fld)) != POINTER_TYPE
+             || next_aggregate_field (DECL_CHAIN (fld))
+             || !tree_int_cst_equal (TYPE_SIZE (TREE_TYPE (decl)),
+                                     TYPE_SIZE (TREE_TYPE (fld))))
+           {
+             if (!ctx->quiet)
+               error_at (loc, "%qD called without supportable %qs",
+                         fndecl, "std::exception_ptr");
+             *non_constant_p = true;
+             return call;
+           }
+         FOR_EACH_VEC_ELT_REVERSE (ctx->global->caught_exceptions, idx, arg)
+           if (arg == NULL_TREE || !VAR_P (arg))
+             --idx;
+           else
+             break;
+         /* Similarly to __builtin_is_constant_evaluated (), we don't
+            want to give a definite answer during mce_unknown evaluation,
+            because that might prevent evaluation later on when some
+            exceptions might be current.  But unlike that, we don't
+            want to constant fold it even during cp_fold, because at runtime
+            std::current_exception () might still be non-null.  */

We don't want to fold it *to null*.

+         if (ctx->manifestly_const_eval != mce_true && arg == NULL_TREE)
+           {
+             *non_constant_p = true;
+             return call;
+           }
+         if (arg == NULL_TREE)
+           arg = build_zero_cst (TREE_TYPE (fld));
+         else
+           {
+             DECL_EXCEPTION_REFCOUNT (arg)
+               = size_binop (PLUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg),
+                             size_one_node);
+             arg = fold_convert (ptr_type_node, build_address (arg));
+           }
+         return build_constructor_single (TREE_TYPE (decl), fld, arg);
+       }
+    case STD_RETHROW_EXCEPTION:
+      if (nargs != 1)
+       goto invalid_nargs;
+      if (TYPE_REF_P (TREE_TYPE (args[0])))
+       {
+         arg = args[0];
+         STRIP_NOPS (arg);
+         if (TREE_CODE (arg) == ADDR_EXPR)
+           {
+             args[0]
+               = cxx_eval_constant_expression (ctx, TREE_OPERAND (arg, 0),
+                                               vc_prvalue, non_constant_p,
+                                               overflow_p, jump_target);
+             if (*non_constant_p)
+               return call;
+             if (*jump_target)
+               return NULL_TREE;
+           }
+       }
+      if (TREE_CODE (args[0]) != CONSTRUCTOR
+         || CONSTRUCTOR_NELTS (args[0]) != 1)
+       {
+       invalid_std_rethrow:
+         if (!ctx->quiet)
+           error_at (loc, "%qD called with unexpected %qs argument",
+                     fndecl, "std::exception_ptr");
+         *non_constant_p = true;
+         return void_node;
+       }
+      arg = cxa_check_throw_arg (CONSTRUCTOR_ELT (args[0], 0)->value, false);
+      if (arg == NULL_TREE)
+       goto invalid_std_rethrow;
+      DECL_EXCEPTION_REFCOUNT (arg)
+       = size_binop (PLUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg), size_one_node);
+      ++ctx->global->uncaught_exceptions;
+      *jump_target = arg;
+      return void_node;
+    case BUILTIN_EH_PTR_ADJUST_REF:
+      if (nargs != 2)
+       goto invalid_nargs;
+      arg = cxa_check_throw_arg (args[0], false);
+      if (arg == NULL_TREE)
+       goto invalid_ptr;
+      if (integer_onep (args[1]))
+       DECL_EXCEPTION_REFCOUNT (arg)
+         = size_binop (PLUS_EXPR, DECL_EXCEPTION_REFCOUNT (arg),
+                       size_one_node);
+      else if (integer_minus_onep (args[1]))
+       goto free_except;
+      else
+       {
+         if (!ctx->quiet)
+           error_at (loc, "%qD called with second argument "
+                     "other than 1 or -1", fndecl);
+         *non_constant_p = true;
+       }
+      return void_node;
+    default:
+      gcc_unreachable ();
+    }
+}
+
  /* Attempt to evaluate T which represents a call to a builtin function.
     We assume here that all builtin functions evaluate to scalar types
     represented by _CST nodes.  */
@@ -5329,8 +6203,11 @@ cxx_eval_bit_cast (const constexpr_ctx *
          if (CHECKING_P)
            {
              tree e = cxx_eval_bare_aggregate (ctx, r, vc_prvalue,
-                                               non_constant_p, overflow_p);
-             gcc_checking_assert (e == r);
+                                               non_constant_p, overflow_p,
+                                               jump_target);
+             gcc_checking_assert (e == r
+                                  && !throws (jump_target)
+                                  && !returns (jump_target));

Maybe just !*jump_target?

              r = e;
            }
        }
@@ -8230,47 +9172,189 @@ cxx_eval_constant_expression (const cons
ctx->global->cleanups = prev_cleanups;
        unsigned int i;
-       tree cleanup;
+       tree cleanup, jmp_target = NULL_TREE;
+       bool eh = throws (jump_target);
        /* Evaluate the cleanups.  */
        FOR_EACH_VEC_ELT_REVERSE (cleanups, i, cleanup)
-         cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard,
-                                       non_constant_p, overflow_p);
+         if (cleanup == NULL_TREE)
+           {
+             /* NULL_TREE cleanup is a marker that before it is
+                CLEANUP_EH_ONLY cleanup.  Skip the cleanup before it
+                if the body didn't throw.  */
+             if (!eh)
+               --i;
+           }
+         else
+           cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard,
+                                         non_constant_p, overflow_p,
+                                         &jmp_target);
/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
           full-expression.  */
        for (tree save_expr : save_exprs)
          destroy_value_checked (ctx, save_expr, non_constant_p);
+       if (throws (&jmp_target))
+         *jump_target = jmp_target;
        }
        break;
+ case MUST_NOT_THROW_EXPR:
+      r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0),
+                                       lval,
+                                       non_constant_p, overflow_p,
+                                       jump_target);
+      if (throws (jump_target))
+       {
+         /* [except.handle]/7 - If the search for a handler exits the
+            function body of a function with a non-throwing exception
+            specification, the function std::terminate is invoked.  */
+         if (!ctx->quiet)
+           {
+             auto_diagnostic_group d;
+             diagnose_std_terminate (loc, ctx, *jump_target);
+             if (MUST_NOT_THROW_NOEXCEPT_P (t)
+                 && ctx->call
+                 && ctx->call->fundef)
+               inform (loc, "uncaught exception exited from %<noexcept%> "
+                            "function %qD",
+                       ctx->call->fundef->decl);
+             else if (MUST_NOT_THROW_THROW_P (t))
+               inform (loc, "destructor exited with an exception after "
+                            "initializing the exception object");
+             else if (MUST_NOT_THROW_CATCH_P (t))
+               inform (loc, "constructor exited with another exception while "
+                            "entering handler");
+           }
+         *non_constant_p = true;
+         *jump_target = NULL_TREE;
+         r = NULL_TREE;
+       }
+      break;
+
+    case TRY_CATCH_EXPR:
+      if (TREE_OPERAND (t, 0) == NULL_TREE)
+       {
+         r = void_node;
+         break;
+       }
+      r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0), lval,
+                                       non_constant_p, overflow_p,
+                                       jump_target);
+      if (!*non_constant_p && throws (jump_target))
+       {
+         tree jmp_target = NULL_TREE;
+         cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 1), vc_discard,
+                                       non_constant_p, overflow_p,
+                                       &jmp_target);
+         if (throws (&jmp_target))
+           {
+             if (throws (jump_target))

Why do you need to test this again after testing it 8 lines earlier?

+               {
+                 /* [except.throw]/9 - If the exception handling mechanism
+                    handling an uncaught exception directly invokes a function
+                    that exits via an exception, the function std::terminate is
+                    invoked.  */
+                 if (!ctx->quiet)
+                   {
+                     auto_diagnostic_group d;
+                     diagnose_std_terminate (loc, ctx, *jump_target);
+                     inform (loc, "destructor exited with an exception");
+                   }
+                 *non_constant_p = true;
+                 *jump_target = NULL_TREE;
+                 r = NULL_TREE;
+               }
+             else
+               *jump_target = jmp_target;
+           }
+       }
+      break;
+
      case TRY_FINALLY_EXPR:
        r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0), lval,
                                        non_constant_p, overflow_p,
                                        jump_target);
        if (!*non_constant_p)
-       /* Also evaluate the cleanup.  */
-       cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 1), vc_discard,
-                                     non_constant_p, overflow_p);
+       {
+         tree jmp_target = NULL_TREE;
+         /* Also evaluate the cleanup.  */
+         if (TREE_CODE (TREE_OPERAND (t, 1)) == EH_ELSE_EXPR
+             && throws (jump_target))
+           cxx_eval_constant_expression (ctx,
+                                         TREE_OPERAND (TREE_OPERAND (t, 1),
+                                                       1), vc_discard,
+                                         non_constant_p, overflow_p,
+                                         &jmp_target);
+         else
+           cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 1), vc_discard,
+                                         non_constant_p, overflow_p,
+                                         &jmp_target);
+         if (throws (&jmp_target))
+           {
+             if (throws (jump_target))
+               {
+                 /* [except.throw]/9 - If the exception handling mechanism
+                    handling an uncaught exception directly invokes a function
+                    that exits via an exception, the function std::terminate is
+                    invoked.  */
+                 if (!ctx->quiet)
+                   {
+                     auto_diagnostic_group d;
+                     diagnose_std_terminate (loc, ctx, *jump_target);
+                     inform (loc, "destructor exited with an exception");
+                   }
+                 *non_constant_p = true;
+                 *jump_target = NULL_TREE;
+                 r = NULL_TREE;
+               }
+             else
+               *jump_target = jmp_target;
+           }
+       }
        break;
case EH_ELSE_EXPR:
        /* Evaluate any cleanup that applies to non-EH exits.  */
        cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0), vc_discard,
-                                   non_constant_p, overflow_p);
+                                   non_constant_p, overflow_p,
+                                   jump_target);
- /* We do not have constexpr exceptions yet, so skip the EH path. */
+      /* The EH path is handled in TRY_FINALLY_EXPR handling above.  */
        break;
case CLEANUP_STMT:
        r = cxx_eval_constant_expression (ctx, CLEANUP_BODY (t), lval,
                                        non_constant_p, overflow_p,
                                        jump_target);
-      if (!CLEANUP_EH_ONLY (t) && !*non_constant_p)
+      if ((!CLEANUP_EH_ONLY (t) || throws (jump_target)) && !*non_constant_p)
        {
          iloc_sentinel ils (loc);
+         tree jmp_target = NULL_TREE;
          /* Also evaluate the cleanup.  */
          cxx_eval_constant_expression (ctx, CLEANUP_EXPR (t), vc_discard,
-                                       non_constant_p, overflow_p);
+                                       non_constant_p, overflow_p,
+                                       &jmp_target);
+         if (throws (&jmp_target))
+           {
+             if (throws (jump_target))
+               {
+                 /* [except.throw]/9 - If the exception handling mechanism
+                    handling an uncaught exception directly invokes a function
+                    that exits via an exception, the function std::terminate is
+                    invoked.  */
+                 if (!ctx->quiet)
+                   {
+                     auto_diagnostic_group d;
+                     diagnose_std_terminate (loc, ctx, *jump_target);
+                     inform (loc, "destructor exited with an exception");
+                   }
+                 *non_constant_p = true;
+                 *jump_target = NULL_TREE;
+                 r = NULL_TREE;
+               }
+             else
+               *jump_target = jmp_target;
+           }

...ah, it's a repeated pattern. Can we factor it out to e.g. merge_jump_target?

        }
        break;
@@ -10038,6 +11213,23 @@ check_for_return_continue (tree *tp, int
      case CONSTRUCTOR:
        break;
+ case AGGR_INIT_EXPR:
+    case CALL_EXPR:
+      /* In C++26 a function could throw.  */
+      if (cxx_dialect >= cxx26 && flag_exceptions && cp_get_callee (t))
+       {
+         tree callee = cp_get_callee (t);
+         tree callee_fn = cp_get_fndecl_from_callee (callee, false);
+         if (!flag_enforce_eh_specs
+             || type_dependent_expression_p (callee)
+             || !POINTER_TYPE_P (TREE_TYPE (callee))
+             || (!type_noexcept_p (TREE_TYPE (TREE_TYPE (callee)))
+                 && (callee_fn == NULL_TREE
+                     || !TREE_NOTHROW (callee_fn))))
+           d->could_throw = true;
+       }
+      break;
+
      default:
        if (!EXPR_P (t))
        *walk_subtrees = 0;
@@ -10289,7 +11500,25 @@ potential_constant_expression_1 (tree t,
                                                  sub_now, fundef_p, flags,
                                                  jump_target))
              return false;
+           if (throws (jump_target))
+             return true;
            }
+       /* In C++26 a function could throw.  */
+       if (cxx_dialect >= cxx26
+           && flag_exceptions
+           && *jump_target == NULL_TREE
+           && cp_get_callee (t))
+         {
+           tree callee = cp_get_callee (t);
+           tree callee_fn = cp_get_fndecl_from_callee (callee, false);
+           if (!flag_enforce_eh_specs
+               || type_dependent_expression_p (callee)
+               || !POINTER_TYPE_P (TREE_TYPE (callee))
+               || (!type_noexcept_p (TREE_TYPE (TREE_TYPE (callee)))
+                   && (callee_fn == NULL_TREE
+                       || !TREE_NOTHROW (callee_fn))))

This could factor out to callee_might_throw?


Reply via email to