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?