https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117785
--- Comment #23 from GCC Commits <cvs-commit at gcc dot gnu.org> --- The master branch has been updated by Jakub Jelinek <ja...@gcc.gnu.org>: https://gcc.gnu.org/g:baaee10123db6cf896283175b345d535b225defb commit r16-2183-gbaaee10123db6cf896283175b345d535b225defb Author: Jakub Jelinek <ja...@redhat.com> Date: Thu Jul 10 23:26:15 2025 +0200 c++, libstdc++: Implement C++26 P3068R5 - constexpr exceptions [PR117785] The following patch implements the C++26 P3068R5 - constexpr exceptions paper. As the IL cxx_eval_constant* functions process already contains the low level calls like __cxa_{allocate,free}_exception, __cxa_{,re}throw etc., the patch just makes 10 extern "C" __cxa_* functions magic builtins which during constant evaluation pretend to be constexpr even when not declared so and handle them directly, plus does the same for 3 std namespace functions - std::uncaught_exceptions, std::current_exception and std::rethrow_exception and adds one new FE builtin - __builtin_eh_ptr_adjust_ref which the library can use instead of the _M_addref and _M_release out of line methods (this one instead of recognizing _M_* as magic too because those are clearly specific to libstdc++ and e.g. libc++ could use something else). The patch uses magic VAR_DECLs with heap_{uninit_,,deleted_}identifier DECL_NAME like for operator new/delete for objects allocated with __cxa_allocate_exception, just sets their DECL_LANG_SPECIFIC so that we can track their reference count as well (with std::exception_ptr the same exception object can be referenced multiple times and we want to destruct and free only when it reaches zero refcount). For uncaught exceptions being propagated, the patch uses new kind of *jump_target, which is that magic VAR_DECL described above. The largest change in the patch is making jump_target argument non-optional in cxa_eval_constant_exception and all functions it calls that need it. This is because exceptions can be thrown from pretty much everywhere, e.g. binary expression can throw in either operand. And the patch also adds if (*jump_target) return NULL_TREE; or similar in many spots, so that we don't crash because cxx_eval_constant_expression returned NULL_TREE somewhere before actually trying to use it and so that we don't uselessly dive into other operands etc. Note, with statement expressions actually this was something we just didn't handle correctly before, one can validly have: a = ({ if (x) return 42; 12; }) + b; or in the other operand, or break/continue instead of return if it is somewhere in a loop/switch; and it isn't ok to branch from one operand to another one through some kind of goto. On the potential_constant_expression_1 side, important change was to set *jump_target conservatively on calls that could throw for C++26 (the patch uses magic void_node for potential_constant_expression* instead of VAR_DECL, so that we don't have to create new VAR_DECLs there uselessly). Without that change, several methods in libstdc++ wouldn't work correctly. I'm not sure what exactly potential_constant_expression_1 maps to in the C++26 standard wording now and whether doing that is ok, because basically after the first call to non-noexcept function it stops checking stuff. And, in some spots where I know potential_constant_expression_1 didn't check some subexpressions (e.g. the EH only cleanups or TRY_BLOCK handlers) I've added *potential_constant_expression* calls during cxx_eval_constant*, not sure if I need to do that because potential_constant_expression_1 is very conservative and just doesn't recurse on subexpressions in many cases. 2025-07-10 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. (merge_jump_target): New function. (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. (callee_might_throw): New function. (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-ellipsis2.C: Expect different diagnostics for C++26. * g++.dg/cpp0x/constexpr-throw.C: Likewise. * 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.