diff --git a/src/cp_gen_be.c b/src/cp_gen_be.c index 35e8eb655b..fdc3911326 100644 --- a/src/cp_gen_be.c +++ b/src/cp_gen_be.c @@ -7260,14 +7260,12 @@ successfully emitted. #endif /* TEMPLATE_INSTANTIATIONS_IN_SOURCE_SEQUENCE_LISTS */ #if BUILTIN_FUNCTIONS_ENABLED if (use_name_reference && - (nrp->special_kind == - (a_special_function_kind)sfk_builtin_operator_new || - nrp->special_kind == - (a_special_function_kind)sfk_builtin_operator_delete || - nrp->special_kind == - (a_special_function_kind)sfk_gnu_atomic_nongeneric_function || - nrp->special_kind == - (a_special_function_kind)sfk_gnu_sync_concrete_function)) { + (special_kind_is(nrp, sfk_builtin_operator_new) || + special_kind_is(nrp, sfk_builtin_operator_delete) || + special_kind_is(nrp, sfk_builtin_current_exception) || + special_kind_is(nrp, sfk_builtin_uncaught_exceptions) || + special_kind_is(nrp, sfk_gnu_atomic_nongeneric_function) || + special_kind_is(nrp, sfk_gnu_sync_concrete_function))) { /* These are special cases handled by gen_name_from_routine_node. */ use_name_reference = FALSE; } /* if */ @@ -7438,7 +7436,9 @@ qualified is TRUE, force the generation of a qualified name. (special_kind_is(nrp, sfk_gnu_sync_concrete_function) || special_kind_is(nrp, sfk_gnu_atomic_nongeneric_function) || special_kind_is(nrp, sfk_builtin_operator_new) || - special_kind_is(nrp, sfk_builtin_operator_delete))) { + special_kind_is(nrp, sfk_builtin_operator_delete) || + special_kind_is(nrp, sfk_builtin_current_exception) || + special_kind_is(nrp, sfk_builtin_uncaught_exceptions))) { switch (nrp->special_kind) { case sfk_gnu_sync_concrete_function: case sfk_gnu_atomic_nongeneric_function: @@ -7484,6 +7484,12 @@ qualified is TRUE, force the generation of a qualified name. case sfk_builtin_operator_delete: write_tok_str("__builtin_operator_delete"); break; + case sfk_builtin_current_exception: + write_tok_str("__builtin_current_exception"); + break; + case sfk_builtin_uncaught_exceptions: + write_tok_str("__builtin_uncaught_exceptions"); + break; default: unexpected_condition(); } /* switch */ diff --git a/src/decls.c b/src/decls.c index 6916c90263..bf1804849b 100644 --- a/src/decls.c +++ b/src/decls.c @@ -10585,6 +10585,15 @@ skip_overloading:; locator->symbol_header->has_intrinsic_name) { check_for_constexpr_intrinsic(routine_ptr, locator->symbol_header); } /* if */ + } else if (!redeclaration && innermost_function_scope == NULL && + constexpr_exceptions_enabled && + locator->symbol_header != NULL && + locator->symbol_header->has_intrinsic_name) { + /* Some library functions that can be evaluated during constant evaluation + are not necessarily declared constexpr (e.g., the GCC C++ standard + library, as of GCC 16, does not declare std::current_exception + constexpr). See check_for_constexpr_intrinsic. */ + check_for_constexpr_intrinsic(routine_ptr, locator->symbol_header); } /* if */ #if GNU_EXTENSIONS_ALLOWED if (gcc_pragma_options_stack != NULL && !func_info->is_main_function) { diff --git a/src/error_msg.txt b/src/error_msg.txt index 6f51f94558..2053a42f0b 100644 --- a/src/error_msg.txt +++ b/src/error_msg.txt @@ -3689,5 +3689,8 @@ ec_constexpr_exception_escapes_nonthrowing;;"std::terminate is called because an ec_constexpr_exception_during_unwinding;;"std::terminate is called because an exception escapes from a destructor during stack unwinding" ec_constexpr_exception_in_handler_init;;"std::terminate is called because an exception escapes from the initialization of a handler parameter" ec_constexpr_exception_from_exception_dtor;;"std::terminate is called because the destructor of an exception object exits by throwing an exception" +ec_constexpr_exception_thrown_here;;"the exception was thrown here" +ec_constexpr_exception_object_not_destroyed;;"an exception object of type %t is not destroyed at the end of constant evaluation" +ec_constexpr_rethrow_null_exception_ptr;;"std::rethrow_exception is called with a null std::exception_ptr" ############################################################################### diff --git a/src/expr.c b/src/expr.c index 1e820f2c5e..9234c19748 100644 --- a/src/expr.c +++ b/src/expr.c @@ -5956,14 +5956,15 @@ typedef struct a_builtin_call_adjustment { arguments, this field will be NULL. */ a_symbol_ptr overloaded_function_symbol; - /* If non-NULL, the symbol for operator new/delete - to be used in place of __builtin_operator_new/ - builtin_operator_delete. */ + /* If non-NULL, the symbol for the function to be + called in place of the builtin (e.g., operator + new/delete in place of __builtin_operator_new/ + __builtin_operator_delete). */ a_name_reference_ptr name_reference; /* If non-NULL, the special name reference to use - to represent __builtin_operator_new/ - __builtin_operator_delete to a back end. */ + to represent the builtin (e.g., + __builtin_operator_new) to a back end. */ int n_args; /* The number of arguments the builtin expects. */ int dispatch_arg; /* The argument number of the "dispatch type", if any. @@ -6257,6 +6258,38 @@ be called to check and adjust the argument and routine types as needed. } /* if */ } break; + case bufk_current_exception: + case bufk_uncaught_exceptions: + { /* The GCC C++ standard library uses __builtin_current_exception in + constexpr functions, since std::current_exception is not declared + constexpr there. The builtin is equivalent to + std::current_exception (which can be evaluated during constant + evaluation; see check_for_constexpr_intrinsic), and so the call is + replaced by a call to that function (with a name reference that + lets a back end recreate the original source). Likewise for + __builtin_uncaught_exceptions and std::uncaught_exceptions. If the + std function is not declared, the builtin is kept (and + __builtin_uncaught_exceptions is then evaluated directly; see + do_constexpr_builtin_function). */ + a_symbol_ptr sym = look_up_name_string_in_std( + bfk == bufk_current_exception + ? "current_exception" + : "uncaught_exceptions"); + if (sym != NULL) sym = fundamental_symbol_of(sym); + if (sym != NULL && (symbol_is(sym, sk_routine) || + symbol_is(sym, sk_overloaded_function))) { + bcap->overloaded_function_symbol = sym; + bcap->name_reference = alloc_name_reference(); + bcap->name_reference->special_kind = + bfk == bufk_current_exception + ? sfk_builtin_current_exception + : sfk_builtin_uncaught_exceptions; + bcap->callback = nullptr; + } else { + requires_processing = FALSE; + } /* if */ + } + break; case bfk_elementwise_abs: case bfk_elementwise_acos: case bfk_elementwise_asin: @@ -8180,6 +8213,7 @@ and bound_function_selector are expected to be NULL in that case. an_expr_node_ptr castexp; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ a_boolean call_folded_to_constant = FALSE; + an_expr_node_ptr function_call_node = NULL; #if BUILTIN_FUNCTIONS_ENABLED a_routine_ptr operand_routine; a_builtin_call_adjustment @@ -8762,8 +8796,9 @@ and bound_function_selector are expected to be NULL in that case. routine = NULL; unknown_dependent_function = FALSE; if (bcap->overloaded_function_symbol != NULL) { - /* The builtin is being replaced by a call to a global overloaded - operator function; use that symbol to do the overload resolution. */ + /* The builtin is being replaced by a call to another function (e.g., + a global overloaded operator function); use that symbol to do the + overload resolution. */ overloaded_function_symbol = bcap->overloaded_function_symbol; overloaded_function_case = TRUE; operand->is_qualified_name = TRUE; @@ -9060,7 +9095,6 @@ and bound_function_selector are expected to be NULL in that case. #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } /* if */ } /* if */ - an_expr_node_ptr function_call_node = NULL; assemble_function_call(operand, bound_function_selector, argument_list, /*compiler_generated=*/FALSE, arg_dep_lookup_suppressed, @@ -9104,13 +9138,15 @@ and bound_function_selector are expected to be NULL in that case. #if BUILTIN_FUNCTIONS_ENABLED if (bcap != NULL) { if (bcap->name_reference != NULL && !is_error_operand(result)) { - /* The builtin call has been replaced by a call to the appropriate - operator new/delete; use a name reference to communicate this to the - back end so the original source can be recreated. */ + /* The builtin call has been replaced by a call to another function + (e.g., the appropriate operator new/delete); use a name reference to + communicate this to the back end so the original source can be + recreated. (The call node can be below the expression in result, + e.g., when the function returns a class.) */ an_expr_node_ptr operand_node; - check_assertion(result->kind == ok_expression && - node_is_operator(result->variant.expression, eok_call)); - operand_node = result->variant.expression->variant.operation.operands; + check_assertion(function_call_node != NULL && + node_is_operator(function_call_node, eok_call)); + operand_node = function_call_node->variant.operation.operands; switch (operand_node->kind) { case enk_routine: operand_node->variant.routine.name_reference = bcap->name_reference; diff --git a/src/folding.c b/src/folding.c index 2a40aa2923..4cdb53e597 100644 --- a/src/folding.c +++ b/src/folding.c @@ -11043,6 +11043,7 @@ pseudo_call can be NULL if that information is not needed. case bufk_FILE_NAME: case bfk_FUNCTION: case bufk_FUNCSIG: + case bufk_eh_ptr_adjust_ref: case bfk_add_overflow_p: case bfk_add_overflow: case bfk_uadd_overflow: diff --git a/src/ifc_modules_write.c b/src/ifc_modules_write.c index 3b6c4c674e..1c89351edd 100644 --- a/src/ifc_modules_write.c +++ b/src/ifc_modules_write.c @@ -2137,8 +2137,10 @@ Return the declaration index for the routine. /* FIXME: Implement these. */ break; #if BUILTIN_FUNCTIONS_ENABLED + case sfk_builtin_current_exception: case sfk_builtin_operator_delete: case sfk_builtin_operator_new: + case sfk_builtin_uncaught_exceptions: #endif /* BUILTIN_FUNCTIONS_ENABLED */ case sfk_deduction_guide: #if MICROSOFT_EXTENSIONS_ALLOWED @@ -3122,8 +3124,10 @@ for the function type. /* FIXME: Implement these. */ break; #if BUILTIN_FUNCTIONS_ENABLED + case sfk_builtin_current_exception: case sfk_builtin_operator_delete: case sfk_builtin_operator_new: + case sfk_builtin_uncaught_exceptions: #endif /* BUILTIN_FUNCTIONS_ENABLED */ case sfk_deduction_guide: #if MICROSOFT_EXTENSIONS_ALLOWED diff --git a/src/il_def.h b/src/il_def.h index 43470cfc6b..7f5cc12d80 100644 --- a/src/il_def.h +++ b/src/il_def.h @@ -2627,6 +2627,12 @@ enum a_special_function_kind : a_byte { sfk_builtin_operator_delete, /* Represents __builtin_operator_delete (which has been replaced by operator delete). */ + sfk_builtin_current_exception, + /* Represents __builtin_current_exception (which has + been replaced by std::current_exception). */ + sfk_builtin_uncaught_exceptions, + /* Represents __builtin_uncaught_exceptions (which has + been replaced by std::uncaught_exceptions). */ #endif /* BUILTIN_FUNCTIONS_ENABLED */ sfk_last /* Must be last. */ }; @@ -4594,6 +4600,8 @@ EXTERN_CONSTINIT_ARRAY(a_const_char*, db_special_function_kinds, sfk_last + 1) "gnu atomic generic function", "__builtin_operator_new", "__builtin_operator_delete", + "__builtin_current_exception", + "__builtin_uncaught_exceptions", #endif /* BUILTIN_FUNCTIONS_ENABLED */ "last" /* used to check that initialization is right. */ } diff --git a/src/il_display.c b/src/il_display.c index 9145ed239d..c5959c152e 100644 --- a/src/il_display.c +++ b/src/il_display.c @@ -3575,6 +3575,12 @@ Print the name of a special function kind. case sfk_builtin_operator_delete: s = "sfk_builtin_operator_delete"; break; + case sfk_builtin_current_exception: + s = "sfk_builtin_current_exception"; + break; + case sfk_builtin_uncaught_exceptions: + s = "sfk_builtin_uncaught_exceptions"; + break; #endif /* BUILTIN_FUNCTIONS_ENABLED */ default: s = "**BAD SPECIAL FUNCTION KIND**"; } /* switch */ diff --git a/src/interpret.c b/src/interpret.c index fae2b15354..75c3665e46 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -98,9 +98,10 @@ constexpr_exceptions_enabled is TRUE. Their implementation involves the the list of exceptions of the interpreter state (see create_exception), initializes the exception object (which is stored in a block of its own), and then starts the throw (see start_throw), which makes a ctk_throw - transfer pending. The exception exists until it is no longer being thrown - and no handler for it is active (see the counts in a_constexpr_exception); - its object is then destroyed (see release_exception_if_unused). + transfer pending. The exception exists until it is no longer being thrown, + no handler for it is active, and no std::exception_ptr refers to it (see + the counts in a_constexpr_exception); its object is then destroyed (see + release_exception_if_unused). - Stack unwinding. The destructions of the objects whose lifetime ends with a scope or a full-expression are recorded in a_constexpr_destruction @@ -131,7 +132,18 @@ constexpr_exceptions_enabled is TRUE. Their implementation involves the - Escaping exceptions. An exception that escapes from a non-throwing function calls std::terminate (see check_exception_escape), and one that escapes from the outermost evaluation is not caught (see run_pushed_work). - Both are failures. + Both are failures. An exception that still exists at the end of the + evaluation (because a std::exception_ptr refers to it) is reported like a + dynamic allocation that was not deallocated (see + report_leftover_allocations). + + - Library support. std::current_exception, std::rethrow_exception, and + std::uncaught_exceptions are constexpr intrinsics (see, e.g., + do_constexpr_std_current_exception), and std::exception_ptr is supported if + it has a single nonstatic data member that points to the exception object + (see exception_ptr_member_offset and adjust_exception_ref_count). Failing + dynamic_cast and typeid operations and invalid array new-expressions throw + the exceptions specified by the standard (see throw_library_exception). - Ignored evaluations. When the failure of an evaluation is ignored (that of an assumption expression or of the operand of __builtin_constant_p), @@ -1259,16 +1271,20 @@ typedef struct a_constexpr_exception { /* The number of handlers that are active for the exception (i.e., that caught it and have not yet exited). */ + unsigned int ref_count; + /* The number of std::exception_ptr objects that refer + to the exception. */ unsigned int throw_count; /* The number of throws (or rethrows) of the exception that are in progress, i.e., that a handler has not yet caught (by completing the initialization of its - parameter). (The pending control transfer for a - throw is set aside while destructors run during the - unwinding, so it cannot be used to determine this. - The count can exceed one because such a destructor - can rethrow the exception that is being - rethrown.) */ + parameter). The sum of these counts is therefore + the result of std::uncaught_exceptions. (The + pending control transfer for a throw is set aside + while destructors run during the unwinding, so it + cannot be used to determine this. The count can + exceed one because such a destructor can rethrow the + exception that is being rethrown.) */ a_boolean is_initialized; /* TRUE once the initialization of the exception object has completed (so that it must be destroyed @@ -2262,13 +2278,21 @@ typedef struct an_interpreter_state { handled_exceptions; /* The stack of exceptions for which a handler is active, innermost first. The top entry is the - exception rethrown by "throw;". */ + exception rethrown by "throw;" and returned by + std::current_exception(). */ a_handler_ptr entering_handler; /* While the block of a handler is pushed for an exception that it caught (the exception of the top entry of handled_exceptions), the handler; NULL otherwise. The block initializes the handler parameter (if any) when it starts. */ + a_constexpr_exception_ptr + ignored_evaluation_exceptions; + /* While an evaluation whose failure is ignored is in + progress (see save_exception_state), the list of + exceptions when the innermost such evaluation + started; NULL otherwise. Those exceptions are not + released during that evaluation. */ } an_interpreter_state; @@ -3318,6 +3342,7 @@ result of calls to std::is_constant_evaluated(). ips->exceptions = NULL; ips->handled_exceptions = NULL; ips->entering_handler = NULL; + ips->ignored_evaluation_exceptions = NULL; n_active_interpreter_states += 1; } /* init_interpreter_state */ @@ -9245,6 +9270,7 @@ diagnostic recorded) if that allocation fails. exc->destructor = dtor; exc->position = *pos; exc->handler_count = 0; + exc->ref_count = 0; exc->is_initialized = FALSE; exc->throw_count = 0; exc->prev = NULL; @@ -9269,23 +9295,67 @@ is being rethrown). } /* start_throw */ +static unsigned int uncaught_exception_count(an_interpreter_state *ips) +/* +Return the number of exceptions that have been thrown (or rethrown) and not yet +caught (see std::uncaught_exceptions). +*/ +{ + a_constexpr_exception_ptr exc; + unsigned int count = 0; + + for (exc = ips->exceptions; exc != NULL; exc = exc->next) { + count += exc->throw_count; + } /* for */ + return count; +} /* uncaught_exception_count */ + + +static a_boolean predates_ignored_evaluation(an_interpreter_state *ips, + a_constexpr_exception_ptr exc) +/* +Return TRUE if an evaluation whose failure is ignored is in progress and the +exception exc existed when the innermost such evaluation started. +*/ +{ + a_boolean result = FALSE; + a_constexpr_exception_ptr old_exc; + + for (old_exc = ips->ignored_evaluation_exceptions; + old_exc != NULL && !result; old_exc = old_exc->next) { + result = (old_exc == exc); + } /* for */ + return result; +} /* predates_ignored_evaluation */ + + static a_boolean release_exception_if_unused(an_interpreter_state *ips, a_constexpr_exception_ptr exc, a_boolean may_propagate) /* -If the exception exc is no longer used -- i.e., no handler is active for it and -it is not being thrown -- destroy the exception object and release the -exception. A control transfer that is pending is preserved. Return FALSE if -the destruction fails or exits by throwing an exception. In the latter case, -if may_propagate is TRUE and the pending control transfer (if any) is not a -throw, the new exception replaces that transfer and is left pending; otherwise, -std::terminate is called (an exception that escapes from a destructor during -stack unwinding), which is a failure. +If the exception exc is no longer used -- i.e., no handler is active for it, no +std::exception_ptr refers to it, and it is not being thrown -- destroy the +exception object and release the exception. A control transfer that is pending +is preserved. Return FALSE if the destruction fails or exits by throwing an +exception. In the latter case, if may_propagate is TRUE and the pending +control transfer (if any) is not a throw, the new exception replaces that +transfer and is left pending; otherwise, std::terminate is called (an exception +that escapes from a destructor during stack unwinding), which is a failure. +Also return FALSE, without releasing the exception, if it existed when the +innermost evaluation whose failure is ignored started: Code outside that +evaluation can still refer to the exception (e.g., the evaluation has called +the destructor of a std::exception_ptr that it did not create), and the +failure of the evaluation restores the exception's counts. */ { a_boolean result = TRUE; - if (exc->handler_count == 0 && exc->throw_count == 0) { + if (exc->handler_count != 0 || exc->ref_count != 0 || + exc->throw_count != 0) { + /* The exception is still used. */ + } else if (predates_ignored_evaluation(ips, exc)) { + result = FALSE; + } else { if (exc->is_initialized && exc->destructor != NULL) { a_control_transfer saved_transfer = ips->transfer; ips->transfer.kind = ctk_none; @@ -9315,6 +9385,7 @@ The counts of an exception, as saved by save_exception_state. */ typedef struct a_saved_exception_counts { unsigned int handler_count; + unsigned int ref_count; unsigned int throw_count; } a_saved_exception_counts; @@ -9334,6 +9405,10 @@ typedef struct a_saved_exception_state { a_handled_exception_ptr handled_exceptions; /* The stack of handled exceptions. */ + a_constexpr_exception_ptr + enclosing_ignored_evaluation_exceptions; + /* The saved value of + ips->ignored_evaluation_exceptions. */ } a_saved_exception_state; @@ -9346,7 +9421,8 @@ runs with side effects disabled, so the only exceptions that code outside of it can refer to are the ones that exist now. The counts of those exceptions are saved in the storage stack (and so they are released along with the storage of the enclosing full-expression or scope). Return FALSE if that storage cannot -be allocated. +be allocated. Otherwise, the evaluation must end with a call of either +restore_exception_state or end_ignored_evaluation. */ { a_boolean result = TRUE; @@ -9355,6 +9431,8 @@ be allocated. saved->exceptions = ips->exceptions; saved->handled_exceptions = ips->handled_exceptions; + saved->enclosing_ignored_evaluation_exceptions = + ips->ignored_evaluation_exceptions; saved->counts = NULL; for (exc = ips->exceptions; exc != NULL; exc = exc->next) { n_exceptions += 1; @@ -9372,14 +9450,29 @@ be allocated. for (exc = ips->exceptions, cp = saved->counts; exc != NULL; exc = exc->next, ++cp) { cp->handler_count = exc->handler_count; + cp->ref_count = exc->ref_count; cp->throw_count = exc->throw_count; } /* for */ } /* if */ } /* if */ + if (result) { + ips->ignored_evaluation_exceptions = ips->exceptions; + } /* if */ return result; } /* save_exception_state */ +static void end_ignored_evaluation(an_interpreter_state *ips, + a_saved_exception_state *saved) +/* +The evaluation for which save_exception_state saved *saved completed normally. +*/ +{ + ips->ignored_evaluation_exceptions = + saved->enclosing_ignored_evaluation_exceptions; +} /* end_ignored_evaluation */ + + static void restore_exception_state(an_interpreter_state *ips, a_saved_exception_state *saved) /* @@ -9402,13 +9495,64 @@ it failed, for example, has ended). for (exc = ips->exceptions, cp = saved->counts; exc != NULL; exc = exc->next, ++cp) { exc->handler_count = cp->handler_count; + exc->ref_count = cp->ref_count; exc->throw_count = cp->throw_count; } /* for */ ips->handled_exceptions = saved->handled_exceptions; ips->entering_handler = NULL; + end_ignored_evaluation(ips, saved); } /* restore_exception_state */ +static a_boolean exception_ptr_member_offset(an_interpreter_state *ips, + a_type_ptr eptr_type, + a_byte_count *p_offset) +/* +eptr_type is std::exception_ptr. The interpreter supports library +implementations in which that class has a single nonstatic data member, of +pointer type, that points to the exception object (as in the GCC C++ standard +library). If eptr_type has that form, store the offset of that member in +*p_offset and return TRUE; otherwise, return FALSE. +*/ +{ + a_boolean result = FALSE; + + eptr_type = skip_typerefs(eptr_type); + if (is_immediate_class_type(eptr_type)) { + a_boolean layout_ok = TRUE; + (void)value_bytes_for_type(ips, eptr_type, &layout_ok); + if (layout_ok) { + a_field_ptr fp = next_alloc_field(fields_of(eptr_type)); + if (fp != NULL && is_pointer_type(skip_typerefs(fp->type)) && + next_alloc_field(fp->next) == NULL) { + get_mapped_byte_count(&persistent_map, fp, *p_offset); + result = TRUE; + } /* if */ + } /* if */ + } /* if */ + return result; +} /* exception_ptr_member_offset */ + + +static a_constexpr_exception_ptr exception_at_address( + an_interpreter_state *ips, + a_constexpr_address *cap) +/* +Return the exception whose object is at the address *cap, or NULL if there is +none. +*/ +{ + a_constexpr_exception_ptr exc = NULL; + + if (cap->address != NULL && !is_runtime_data_address(cap)) { + for (exc = ips->exceptions; exc != NULL; exc = exc->next) { + if (exc->address.address == cap->address) break; + } /* for */ + } /* if */ + return exc; +} /* exception_at_address */ + + static a_boolean is_null_constexpr_address(a_constexpr_address *cap) /* Return TRUE if *cap is a null pointer value (which is represented either @@ -9422,6 +9566,159 @@ without any address or as a null integer constant). } /* is_null_constexpr_address */ +static a_boolean store_exception_ptr( + an_interpreter_state *ips, + a_type_ptr eptr_type, + a_constexpr_exception_ptr exc, + a_source_position *pos, + a_byte *result_storage, + a_byte *complete_obj) +/* +Construct at result_storage (part of the complete object at complete_obj) a +std::exception_ptr object (of type eptr_type) that refers to the exception exc, +or a null std::exception_ptr if exc is NULL. The new object counts as a +reference to exc. Return TRUE if successful; if eptr_type does not have the +form expected by exception_ptr_member_offset, record a diagnostic (using pos) +and return FALSE. +*/ +{ + a_boolean result = TRUE; + a_byte_count offset; + + if (!exception_ptr_member_offset(ips, eptr_type, &offset)) { + info_with_pos(ec_constexpr_expression_cannot_be_interpreted, pos, ips); + do_constexpr_fail(result); + } else { + a_constexpr_address *cap = (a_constexpr_address*)(result_storage+offset); + if (exc == NULL) { + clear_address(cap, (a_byte*)NULL); + } else { + *cap = exc->address; + exc->ref_count += 1; + } /* if */ + mark_whole_subobject_initialized(ips, result_storage, + skip_typerefs(eptr_type), complete_obj); + if (result_storage == complete_obj) { + mark_complete_object_initialized(complete_obj); + } /* if */ + } /* if */ + return result; +} /* store_exception_ptr */ + + +static a_boolean adjust_exception_ref_count(an_interpreter_state *ips, + a_constexpr_address *cap, + a_host_large_integer delta, + a_source_position *pos) +/* +Implement __builtin_eh_ptr_adjust_ref (used by the GCC C++ standard library's +implementation of std::exception_ptr during constant evaluation): add delta to +the number of std::exception_ptr objects that refer to the exception whose +object is at the address *cap, and release the exception if it is no longer +used. Return TRUE if successful; otherwise, record a diagnostic (using pos) +and return FALSE. +*/ +{ + a_boolean result = TRUE; + a_constexpr_exception_ptr exc = exception_at_address(ips, cap); + a_boolean valid = FALSE; + + if (exc != NULL) { + /* Compute the magnitude of delta in unsigned arithmetic, so that neither + its negation nor the adjustment can overflow. */ + a_host_large_unsigned magnitude = delta < 0 ? + -(a_host_large_unsigned)delta : + (a_host_large_unsigned)delta; + if (delta < 0) { + valid = magnitude <= exc->ref_count; + } else { + valid = magnitude <= UINT_MAX - exc->ref_count; + } /* if */ + if (valid) { + exc->ref_count = delta < 0 ? exc->ref_count - (unsigned int)magnitude : + exc->ref_count + (unsigned int)magnitude; + } /* if */ + } /* if */ + if (!valid) { + info_with_pos(ec_constexpr_expression_cannot_be_interpreted, pos, ips); + do_constexpr_fail(result); + } else if (exc->ref_count == 0 && + !release_exception_if_unused(ips, exc, /*may_propagate=*/FALSE)) { + do_constexpr_fail(result); + } /* if */ + return result; +} /* adjust_exception_ref_count */ + + +static a_boolean throw_library_exception(an_interpreter_state *ips, + a_const_char *class_name, + a_source_position *pos) +/* +An operation evaluated at pos throws an exception of type std::class_name +(e.g., std::bad_cast for a dynamic_cast to a reference type that fails). If +that class is complete and has a constexpr default constructor without +parameters, create the exception object, initialize it with that constructor, +start throwing it, and return TRUE. The exception is then pending, unless its +initialization failed or exited by throwing another exception (which then +propagates instead). Return FALSE (without doing anything) if exceptions +cannot be thrown during constant evaluation or the class is not available, in +which case the caller reports that the operation cannot be evaluated. +*/ +{ + a_boolean result = FALSE; + a_symbol_ptr sym; + a_type_ptr class_type = NULL; + a_routine_ptr ctor = NULL; + + if (!constexpr_exceptions_enabled) { + /* The operation cannot throw the exception, and so it cannot be + evaluated. */ + goto done; + } /* if */ + sym = look_up_name_string_in_std(class_name); + if (sym != NULL) { + sym = fundamental_symbol_of(sym); + if (is_class_symbol(sym)) { + class_type = skip_typerefs(type_symbol_type(sym)); + } /* if */ + } /* if */ + if (class_type != NULL && !is_incomplete_type(class_type)) { + a_boolean error_detected, err; + ctor = select_default_constructor_full(class_type, pos, class_type, + /*declarative_context=*/FALSE, + /*evaluated=*/TRUE, + /*check_access=*/FALSE, + /*no_explicit=*/FALSE, + &error_detected, &err); + if (error_detected || err || + (ctor != NULL && + (!ctor->is_constexpr || + function_type_params(skip_typerefs(ctor->type)) != NULL))) { + ctor = NULL; + } /* if */ + } /* if */ + if (ctor != NULL) { + a_constexpr_exception_ptr exc; + result = TRUE; + exc = create_exception(ips, class_type, + nontrivial_destructor_for_type(class_type), pos); + if (exc != NULL) { + a_dynamic_init_ptr dip = alloc_dynamic_init(dik_constructor); + dip->variant.constructor.ptr = ctor; + if (do_constexpr_ctor(ips, dip, pos, exc->address, + (a_constexpr_address*)NULL)) { + exc->is_initialized = TRUE; + start_throw(ips, exc); + } else { + free_exception(ips, exc); + } /* if */ + } /* if */ + } /* if */ +done: + return result; +} /* throw_library_exception */ + + static a_handler_ptr find_matching_handler(a_handler_ptr handlers, a_constexpr_exception_ptr exc) /* @@ -10418,6 +10715,7 @@ evaluates to false. !save_exception_state(ips, &saved_exception_state)) { do_constexpr_fail(result); } else if (eval_bool_assertion(ips, expr, &assertion_value)) { + end_ignored_evaluation(ips, &saved_exception_state); if (!assertion_value) { /* Add a new note indicating that the assumption failed. The call to eval_bool_assertion may have appended a note after dp (indicating @@ -13682,6 +13980,7 @@ to FALSE and the reason for the failure is recorded in *ips. arg1_bytes) && do_constexpr_expression(ips, args, arg1_bytes, arg1_bytes)) { + end_ignored_evaluation(ips, &saved_exception_state); *(an_integer_value*)result_storage = one_int; } else { *(an_integer_value*)result_storage = zero_int; @@ -14330,6 +14629,50 @@ to FALSE and the reason for the failure is recorded in *ips. } /* if */ } /* if */ break; + case bufk_eh_ptr_adjust_ref: + interpreted = TRUE; + if (args == NULL || args->next == NULL || args->next->next != NULL) { + unexpected_condition(); + } else { + a_type_ptr tp1 = skip_typerefs(args->type); + a_type_ptr tp2 = skip_typerefs(args->next->type); + a_byte_count n_bytes1 = value_bytes_for_type(ips, tp1, p_result); + a_byte_count n_bytes2 = value_bytes_for_type(ips, tp2, p_result); + if (!*p_result) break; + if (!alloc_complete_object(ips, n_bytes1, tp1, arg1_bytes) || + !do_constexpr_expression(ips, args, arg1_bytes, arg1_bytes) || + !alloc_complete_object(ips, n_bytes2, tp2, arg2_bytes) || + !do_constexpr_expression(ips, args->next, arg2_bytes, + arg2_bytes)) { + do_constexpr_fail(*p_result); + } else { + a_boolean ovflo; + a_host_large_integer delta = 0; + conv_integer_value_to_host_large_integer( + (an_integer_value*)arg2_bytes, + /*is_signed=*/TRUE, &delta, &ovflo); + if (!adjust_exception_ref_count(ips, + (a_constexpr_address*)arg1_bytes, + delta, &call_node->position)) { + do_constexpr_fail(*p_result); + } /* if */ + } /* if */ + } /* if */ + break; + case bufk_uncaught_exceptions: + /* A call that was not replaced by a call of std::uncaught_exceptions + (because that function is not declared; see + builtin_call_needs_adjustment). */ + interpreted = TRUE; + if (args != NULL) { + unexpected_condition(); + } else if (!ips->is_constant_evaluated) { + do_constexpr_fail(*p_result); + } else { + set_integer_value((an_integer_value*)result_storage, + (a_host_large_integer)uncaught_exception_count(ips)); + } /* if */ + break; case bfk_assume: /* The argument to __builtin_assume is not evaluated. */ if (args == NULL || args->next != NULL) { @@ -23316,6 +23659,14 @@ explains the missing deallocation. info_with_pos(ec_constexpr_allocation_pos, &ips->dyn_allocations->pos, ips); } /* if */ + if (ips->exceptions != NULL) { + /* An exception object is still referred to (by a std::exception_ptr + object). */ + info_with_pos_type(ec_constexpr_exception_object_not_destroyed, + &ips->position, ips->exceptions->type, ips); + info_with_pos(ec_constexpr_exception_thrown_here, + &ips->exceptions->position, ips); + } /* if */ } /* report_leftover_allocations */ @@ -23711,6 +24062,116 @@ do_constexpr_intrinsic_call for the meaning of the parameters. } /* do_constexpr_std_is_string_literal */ +static a_boolean do_constexpr_std_current_exception( + an_interpreter_state *ips, + a_routine_ptr callee, + an_expr_node_ptr call_node, + ARG_UNUSED a_byte **p_arg_bytes, + a_byte *result_storage, + a_byte *complete_obj) +/* +Implement std::current_exception(), which returns a std::exception_ptr that +refers to the currently handled exception (or a null std::exception_ptr if +there is none). When no handler is active in the evaluation, the result is +known only if the evaluation is a constant evaluation (since otherwise a +handler may be active when the call is executed). The GCC C++ standard library +calls it through __builtin_current_exception (see +builtin_call_needs_adjustment). + +See do_constexpr_intrinsic_call for the meaning of the parameters. +*/ +{ + a_boolean result = TRUE; + a_constexpr_exception_ptr exc = NULL; + + if (ips->handled_exceptions != NULL) { + exc = ips->handled_exceptions->exception; + } /* if */ + if (exc == NULL && !ips->is_constant_evaluated) { + do_constexpr_fail(result); + } else { + result = store_exception_ptr(ips, return_type_of(callee->type), exc, + &call_node->position, result_storage, + complete_obj); + } /* if */ + return result; +} /* do_constexpr_std_current_exception */ + + +static a_boolean do_constexpr_std_rethrow_exception( + an_interpreter_state *ips, + a_routine_ptr callee, + an_expr_node_ptr call_node, + a_byte **p_arg_bytes, + ARG_UNUSED a_byte *result_storage, + ARG_UNUSED a_byte *complete_obj) +/* +Implement std::rethrow_exception(p), which throws the exception object to which +the std::exception_ptr p refers. If successful, the result is FALSE with the +throw pending. + +See do_constexpr_intrinsic_call for the meaning of the parameters. +*/ +{ + a_boolean result = TRUE; + a_param_type_ptr ptp = function_type_params(skip_typerefs(callee->type)); + a_byte_count offset; + + if (ptp == NULL || !exception_ptr_member_offset(ips, ptp->type, &offset)) { + info_with_pos(ec_constexpr_expression_cannot_be_interpreted, + &call_node->position, ips); + do_constexpr_fail(result); + } else { + a_constexpr_address *cap; + a_constexpr_exception_ptr exc; + cap = (a_constexpr_address*)(p_arg_bytes[0]+offset); + exc = exception_at_address(ips, cap); + if (exc != NULL) { + start_throw(ips, exc); + result = FALSE; + } else { + if (is_null_constexpr_address(cap)) { + info_with_pos(ec_constexpr_rethrow_null_exception_ptr, + &call_node->position, ips); + } else { + info_with_pos(ec_constexpr_expression_cannot_be_interpreted, + &call_node->position, ips); + } /* if */ + do_constexpr_fail(result); + } /* if */ + } /* if */ + return result; +} /* do_constexpr_std_rethrow_exception */ + + +static a_boolean do_constexpr_std_uncaught_exceptions( + an_interpreter_state *ips, + ARG_UNUSED a_routine_ptr callee, + ARG_UNUSED an_expr_node_ptr call_node, + ARG_UNUSED a_byte **p_arg_bytes, + a_byte *result_storage, + ARG_UNUSED a_byte *complete_obj) +/* +Implement std::uncaught_exceptions(), which returns the number of exceptions +that have been thrown but not yet caught. The result is known only if the +evaluation is a constant evaluation (since otherwise exceptions thrown outside +the evaluation may be uncaught when the call is executed). + +See do_constexpr_intrinsic_call for the meaning of the parameters. +*/ +{ + a_boolean result = TRUE; + + if (!ips->is_constant_evaluated) { + do_constexpr_fail(result); + } else { + set_integer_value((an_integer_value*)result_storage, + (a_host_large_integer)uncaught_exception_count(ips)); + } /* if */ + return result; +} /* do_constexpr_std_uncaught_exceptions */ + + static a_boolean do_constexpr_std_construct_at( an_interpreter_state *ips, a_routine_ptr callee, @@ -24712,7 +25173,8 @@ callee; the iwp_1st_resume visit completes the call in the latter case. } /* if */ } /* if */ } /* if */ - if (callee->function_def_number == NULL_function_def_number) { + if (callee->function_def_number == NULL_function_def_number || + (callee->is_constexpr_intrinsic && !callee->is_constexpr)) { if (callee->is_constexpr_intrinsic) { /* A standard library function or member function that the front end has marked as "constexpr-intrinsic", which means we should implement @@ -24720,7 +25182,9 @@ callee; the iwp_1st_resume visit completes the call in the latter case. Note that some intrinsics do have a definition, and for those we should perform some more work (like mapping parameters) in case the intrinsic handling falls back to the provided definition (as, e.g., - std::construct_at does). */ + std::construct_at does). That does not apply to an intrinsic that + is not constexpr (see check_for_constexpr_intrinsic): Its + definition (if any) cannot be interpreted. */ up_front_cost = charge_call_cost(ips); push_new_call_frame(ips, callee, &call_node->position, result_cap); result = do_constexpr_intrinsic_call( @@ -27556,6 +28020,16 @@ given complete object). complete_object); if (!result) goto done; opnd_addr = (a_constexpr_address*)result_storage; + if (is_null_constexpr_address(opnd_addr)) { + /* The glvalue was obtained by an indirection through a null pointer, + which causes std::bad_typeid to be thrown (N5054 [expr.typeid]). */ + do_constexpr_fail(result); + if (!throw_library_exception(ips, "bad_typeid", &expr->position)) { + info_with_pos(ec_constexpr_invalid_null_ptr_operation, + &expr->position, ips); + } /* if */ + goto done; + } /* if */ /* Now determine the dynamic type of this object. */ if (is_runtime_data_address(opnd_addr)) { /* The address is represented as an IL constant. Follow the subobject @@ -27812,8 +28286,11 @@ represented by an entry of type a_constant (ck_address or ck_integer). } /* if */ } else { do_constexpr_fail(result); - info_with_pos_type2(ec_constexpr_invalid_dynamic_cast, - &expr->position, tp, opnd_type, ips); + if (!throw_library_exception(ips, "bad_cast", + &expr->position)) { + info_with_pos_type2(ec_constexpr_invalid_dynamic_cast, + &expr->position, tp, opnd_type, ips); + } /* if */ } /* if */ goto done; } else { @@ -27883,8 +28360,12 @@ represented by an entry of type a_constant (ck_address or ck_integer). clear_address(result_storage, NULL); } else { do_constexpr_fail(result); - info_with_pos_type(ec_ambiguous_base_class, - &expr->position, new_bcp->type, ips); + if (!throw_library_exception(ips, "bad_cast", + &expr->position)) { + info_with_pos_type(ec_ambiguous_base_class, + &expr->position, new_bcp->type, + ips); + } /* if */ } /* if */ goto done; } else if (!prev_bcp->has_public_derivation || @@ -27896,8 +28377,12 @@ represented by an entry of type a_constant (ck_address or ck_integer). clear_address(result_storage, NULL); } else { do_constexpr_fail(result); - info_with_pos_type(ec_inaccessible_base_class, - &expr->position, new_bcp->type, ips); + if (!throw_library_exception(ips, "bad_cast", + &expr->position)) { + info_with_pos_type(ec_inaccessible_base_class, + &expr->position, new_bcp->type, + ips); + } /* if */ } /* if */ goto done; } /* if */ @@ -27906,9 +28391,12 @@ represented by an entry of type a_constant (ck_address or ck_integer). clear_address(result_storage, NULL); } else { do_constexpr_fail(result); - info_with_pos_type2(ec_constexpr_invalid_dynamic_cast, - &expr->position, tp, - prev_bcp->derived_class, ips); + if (!throw_library_exception(ips, "bad_cast", + &expr->position)) { + info_with_pos_type2(ec_constexpr_invalid_dynamic_cast, + &expr->position, tp, + prev_bcp->derived_class, ips); + } /* if */ } /* if */ goto done; } /* if */ @@ -28022,8 +28510,10 @@ represented by an entry of type a_constant (ck_address or ck_integer). clear_address(result_storage, NULL); } else { do_constexpr_fail(result); - info_with_pos_type(ec_ambiguous_base_class, - &expr->position, tp, ips); + if (!throw_library_exception(ips, "bad_cast", &expr->position)) { + info_with_pos_type(ec_ambiguous_base_class, + &expr->position, tp, ips); + } /* if */ } /* if */ goto done; } else if (!is_public_base || !baseward_bcp->has_public_derivation) { @@ -28031,8 +28521,10 @@ represented by an entry of type a_constant (ck_address or ck_integer). clear_address(result_storage, NULL); } else { do_constexpr_fail(result); - info_with_pos_type(ec_inaccessible_base_class, - &expr->position, tp, ips); + if (!throw_library_exception(ips, "bad_cast", &expr->position)) { + info_with_pos_type(ec_inaccessible_base_class, + &expr->position, tp, ips); + } /* if */ } /* if */ goto done; } /* if */ @@ -28042,8 +28534,10 @@ represented by an entry of type a_constant (ck_address or ck_integer). clear_address(result_storage, NULL); } else { do_constexpr_fail(result); - info_with_pos_type2(ec_constexpr_invalid_dynamic_cast, - &expr->position, tp, dtp, ips); + if (!throw_library_exception(ips, "bad_cast", &expr->position)) { + info_with_pos_type2(ec_constexpr_invalid_dynamic_cast, + &expr->position, tp, dtp, ips); + } /* if */ } /* if */ goto done; } /* if */ @@ -28114,9 +28608,16 @@ Evaluate the given new-expression. } /* if */ get_int_val_from(length_bytes, length_tp, length, ovflo); if (ovflo || length < 0 || length > MAX_ARRAY_LENGTH) { - info_with_pos_num(ec_constexpr_alloc_too_large, &length_expr->position, - (int32_t)length, ips); + /* An invalid length causes std::bad_array_new_length to be thrown (N5054 + [expr.new]), except with a non-throwing allocation function (which is + a placement allocation function). */ do_constexpr_fail(result); + if ((!ovflo && length > MAX_ARRAY_LENGTH) || ndsp->placement_new || + !throw_library_exception(ips, "bad_array_new_length", + &length_expr->position)) { + info_with_pos_num(ec_constexpr_alloc_too_large, + &length_expr->position, (int32_t)length, ips); + } /* if */ goto done; } /* if */ alloc_length = (a_byte_count)length; @@ -28221,10 +28722,16 @@ Evaluate the given new-expression. n_init_elems = init_cp->variant.string.length; } /* if */ if (n_init_elems > orig_alloc_length) { - info_with_pos_num(ec_constexpr_alloc_too_small, - &length_expr->position, - (int32_t)orig_alloc_length, ips); + /* That also causes std::bad_array_new_length to be thrown + (N5054 [expr.new]). The allocation is freed below. */ do_constexpr_fail(result); + if (allocation == NULL || + !throw_library_exception(ips, "bad_array_new_length", + &length_expr->position)) { + info_with_pos_num(ec_constexpr_alloc_too_small, + &length_expr->position, + (int32_t)orig_alloc_length, ips); + } /* if */ goto done; } /* if */ } /* if */ diff --git a/src/interpret.h b/src/interpret.h index 57f0aa5755..034bd9e9b8 100644 --- a/src/interpret.h +++ b/src/interpret.h @@ -133,6 +133,9 @@ must be defined in interpret.c to implement its evaluation. M(std, construct_at, "(*.,)*.") \ M(std, __report_constexpr_value, "(I)v|(*C)v|(*C,I)v") \ M(std, is_string_literal, "(*G)b") \ + M(std, current_exception, "().") \ + M(std, rethrow_exception, "(.)v") \ + M(std, uncaught_exceptions, "()I") \ M(std_meta, identifier_of, "(r)Sv") \ M(std_meta, u8identifier_of, "(r)Su") \ M(std_meta, display_string_of, "(r)Sv") \ diff --git a/src/pending_changes/gh-368-constexpr-exceptions-library.txt b/src/pending_changes/gh-368-constexpr-exceptions-library.txt new file mode 100644 index 0000000000..228d9aa945 --- /dev/null +++ b/src/pending_changes/gh-368-constexpr-exceptions-library.txt @@ -0,0 +1,42 @@ +[GH #368] +C++26: Library support for exceptions in constant evaluation + +The library support of P3068R6 is now implemented in C++26 mode (or with +--set_flag=constexpr_exceptions): std::current_exception, +std::rethrow_exception, and std::uncaught_exceptions can be called during +constant evaluation (even if the library declares or defines them without +"constexpr"), and so can std::exception_ptr operations if that class has a +single nonstatic data member of pointer type (as in the GCC C++ standard +library). For +example, with --c++26 and a library that supports this: + + #include + constexpr int f() { + std::exception_ptr p; + try { + throw 42; + } catch (...) { + p = std::current_exception(); + } + try { + std::rethrow_exception(p); + } catch (int i) { + return i; + } + } + static_assert(f() == 42); // Previously an error; now okay. + +In g++ modes with gnu_version >= 160000, the builtins +__builtin_eh_ptr_adjust_ref, __builtin_current_exception, and +__builtin_uncaught_exceptions, which the GCC C++ standard library uses for +this, are now supported. + +In addition, a dynamic_cast to a reference type that fails, typeid applied to +a null glvalue, and an array new-expression with an invalid number of elements +now throw std::bad_cast, std::bad_typeid, and std::bad_array_new_length, +respectively, during constant evaluation, provided that the definition of the +corresponding class is available and it has a constexpr default constructor. +Otherwise, those cases are diagnosed as before. + +An exception object that is still referred to by a std::exception_ptr at the +end of the evaluation causes the evaluation to not be constant. diff --git a/src/symbol_tbl.c b/src/symbol_tbl.c index 46142fc5da..2d0a9db4dc 100644 --- a/src/symbol_tbl.c +++ b/src/symbol_tbl.c @@ -19389,7 +19389,11 @@ void check_for_constexpr_intrinsic(a_routine_ptr rp, The given routine is being declared with the given symbol header and that header is associated with an intrinsic. Check whether the routine is actually a "constexpr intrinsic" (i.e., a function handled specially by the constexpr -interpreter) and if so mark it as such. +interpreter) and if so mark it as such. Most intrinsics must be declared +constexpr (or consteval), but std::current_exception, std::rethrow_exception, +and std::uncaught_exceptions are constexpr functions in P3068R6 even though +some library implementations (e.g., the GCC C++ standard library, as of GCC 16) +do not declare them that way. */ { int n = constexpr_intrinsic_descr_table->get(sym_hdr); @@ -19398,7 +19402,17 @@ interpreter) and if so mark it as such. a_constexpr_intrinsic_descr *descr; descr = &constexpr_intrinsic_descriptions[n].descr; check_assertion(descr->kind != cit_error); - if (*descr->p_namespace_sym != NULL && + /* FIXME: N5054 [exception.syn] does not declare std::current_exception + and std::uncaught_exceptions constexpr (only an exposition-only + current-exception(), which corresponds to __builtin_current_exception), + so calls to them are not constant expressions there. Revisit whether + they should still be evaluated during constant evaluation. */ + if (!rp->is_constexpr && + descr->kind != cit_std_current_exception && + descr->kind != cit_std_rethrow_exception && + descr->kind != cit_std_uncaught_exceptions) { + /* Not an intrinsic that may be declared non-constexpr. */ + } else if (*descr->p_namespace_sym != NULL && ((is_namespace_member(rp) && is_member_of_namespace(symbol_for(rp), *descr->p_namespace_sym)) || is_member_of_class_in_namespace(rp, *descr->p_namespace_sym))) { diff --git a/src/sys_predef.h b/src/sys_predef.h index 936b14344c..31175eddd6 100644 --- a/src/sys_predef.h +++ b/src/sys_predef.h @@ -392,6 +392,9 @@ enum a_builtin_user_function_kind { bufk_u8strlen, /* __builtin_u8strlen */ bufk_FUNCSIG, /* __builtin_FUNCSIG */ bufk_FILE_NAME, /* __builtin_FILE_NAME */ + bufk_eh_ptr_adjust_ref, /* __builtin_eh_ptr_adjust_ref */ + bufk_current_exception, /* __builtin_current_exception */ + bufk_uncaught_exceptions, /* __builtin_uncaught_exceptions */ bufk_last /* final entry */ }; @@ -610,6 +613,19 @@ EXTERN a_builtin_user_descr builtin_user_table[] { "__builtin_FUNCSIG", "m+(1935-)", "const char*(void) __edg_throw__()", bufk_FUNCSIG }, + /* The GCC C++ standard library uses these builtin functions to implement + std::exception_ptr during constant evaluation. A call of + __builtin_current_exception or __builtin_uncaught_exceptions is replaced + by a call of std::current_exception or std::uncaught_exceptions, + respectively, when that function is declared, but a back end still + generates the builtin (see builtin_call_needs_adjustment). */ + { "__builtin_eh_ptr_adjust_ref", "g+(160000-)", + "void (void*,int) __edg_throw__()", bufk_eh_ptr_adjust_ref }, + { "__builtin_current_exception", "g+(160000-)", + "void* (void) __edg_throw__()", bufk_current_exception }, + { "__builtin_uncaught_exceptions", "g+(160000-)", + "int (void) __edg_throw__()", bufk_uncaught_exceptions }, + { NULL, NULL, 0, bfk_none } /* end of table marker */ } #endif /* VAR_INITIALIZERS */ diff --git a/tests/expectations/edg_x86_32_cp/changes.log b/tests/expectations/edg_x86_32_cp/changes.log index 0a2b8c068c..724b6100b3 100644 --- a/tests/expectations/edg_x86_32_cp/changes.log +++ b/tests/expectations/edg_x86_32_cp/changes.log @@ -15,3 +15,4 @@ EDGcpfe8330.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[4.1] Microsoft compatibility: Calli EDGcpfe9522.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[4.1] Microsoft compatibility: __ptr64 on 64-bit targets GH36.sft.cpp:fp:-DTEST_NUMBER=1 --c++26 --set_flag reflection:REQUIREMENTS NOT MET:[GH #36] defines __cpp_lib_reflection GH38.sft.cpp:fn:-DTEST_NUMBER=1 --gnu_version 150200:REQUIREMENTS NOT MET:[GH #38] Integer value limits for a 128-bit host integer representation +gh-368-gnu-cppbe.sft.cpp:cppbe:-DTEST_NUMBER=1:PASS: diff --git a/tests/expectations/edg_x86_64/changes.log b/tests/expectations/edg_x86_64/changes.log index 48b765f34e..7aa53cdc0b 100644 --- a/tests/expectations/edg_x86_64/changes.log +++ b/tests/expectations/edg_x86_64/changes.log @@ -83,3 +83,4 @@ EDGcpfe9490.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[4.1] Spurious error on member funct EDGcpfe9494.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[4.1] C++-generating back end: abort with "auto" type specifier EDGcpfe9612.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[4.1] Invalid generated C code for some delayed nested class definitions EDGcpfe9963.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[4.1] #pragma pack(pop) with no corresponding "push" +gh-368-gnu-cppbe.sft.cpp:cppbe:-DTEST_NUMBER=1:REQUIREMENTS NOT MET:C++-generating back end: GCC exception_ptr builtins diff --git a/tests/expectations/edg_x86_64_cp/changes.log b/tests/expectations/edg_x86_64_cp/changes.log index 881fc01e00..ed1a311c20 100644 --- a/tests/expectations/edg_x86_64_cp/changes.log +++ b/tests/expectations/edg_x86_64_cp/changes.log @@ -4,3 +4,4 @@ EDGcpfe21685.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[6.0] C++-generating back end: Abor EDGcpfe21953.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[6.2] Abort on value-initialization of GNU vector type EDGcpfe24520.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[6.3] Microsoft compatibility: __restrict parameters EDGcpfe25938.sft.cpp:fp:-DTEST_NUMBER=1:FAIL:[6.5] C23: Implement typeof and typeof_unqual operators +gh-368-gnu-cppbe.sft.cpp:cppbe:-DTEST_NUMBER=1:PASS: diff --git a/tests/tests/changes/.gh-368-errors.rto/default.1.1.txt b/tests/tests/changes/.gh-368-errors.rto/default.1.1.txt index f8ed72ca51..6226cd06c8 100644 --- a/tests/tests/changes/.gh-368-errors.rto/default.1.1.txt +++ b/tests/tests/changes/.gh-368-errors.rto/default.1.1.txt @@ -1,111 +1,132 @@ fe_only -DTEST_NUMBER=1 --c++26 Test_name.c -"Test_name.c", line 19: warning: statement is unreachable +"Test_name.c", line 35: warning: statement is unreachable constexpr int uncaught() { throw E{1}; return 0; } ^ -"Test_name.c", line 20: error: expression must have a constant value +"Test_name.c", line 36: error: expression must have a constant value static_assert(uncaught() == 0); // Uncaught E. ^ -"Test_name.c", line 19: note: an exception of type "E" is thrown and not caught during constant evaluation +"Test_name.c", line 35: note: an exception of type "E" is thrown and not caught during constant evaluation constexpr int uncaught() { throw E{1}; return 0; } ^ -"Test_name.c", line 24: warning: statement is unreachable +"Test_name.c", line 40: warning: statement is unreachable return 0; ^ -"Test_name.c", line 26: error: expression must have a constant value +"Test_name.c", line 42: error: expression must have a constant value static_assert(wrong_handler() == 1); // Uncaught int. ^ -"Test_name.c", line 23: note: an exception of type "int" is thrown and not caught during constant evaluation +"Test_name.c", line 39: note: an exception of type "int" is thrown and not caught during constant evaluation try { throw 1; } catch (long) { return 1; } ^ -"Test_name.c", line 28: warning: statement is unreachable +"Test_name.c", line 44: warning: statement is unreachable constexpr int bare_rethrow() { throw; return 0; } ^ -"Test_name.c", line 29: error: expression must have a constant value +"Test_name.c", line 45: error: expression must have a constant value static_assert(bare_rethrow() == 0); // Nothing being handled. ^ -"Test_name.c", line 28: note: a rethrow ("throw;") is evaluated when no exception is being handled +"Test_name.c", line 44: note: a rethrow ("throw;") is evaluated when no exception is being handled constexpr int bare_rethrow() { throw; return 0; } ^ -"Test_name.c", line 36: error: expression must have a constant value +"Test_name.c", line 52: error: expression must have a constant value static_assert(noexcept_boundary() == 1); // std::terminate. ^ -"Test_name.c", line 31: note: std::terminate is called because an exception escapes from non-throwing function "nt" (declared at line 31) +"Test_name.c", line 47: note: std::terminate is called because an exception escapes from non-throwing function "nt" (declared at line 47) constexpr void nt() noexcept { throw 1; } ^ -"Test_name.c", line 33: note: called from: +"Test_name.c", line 49: note: called from: try { nt(); } catch (int) { return 1; } ^ -"Test_name.c", line 43: error: expression must have a constant value +"Test_name.c", line 59: error: expression must have a constant value static_assert(implicit_noexcept_dtor() == 1); // std::terminate. ^ -"Test_name.c", line 38: note: std::terminate is called because an exception escapes from non-throwing function "Dtor::~Dtor" (declared at line 38) +"Test_name.c", line 54: note: std::terminate is called because an exception escapes from non-throwing function "Dtor::~Dtor" (declared at line 54) struct Dtor { constexpr ~Dtor() { throw 1; } }; ^ -"Test_name.c", line 40: note: called from: +"Test_name.c", line 56: note: called from: try { Dtor d; } catch (int) { return 1; } ^ -"Test_name.c", line 50: warning: statement is unreachable +"Test_name.c", line 66: warning: statement is unreachable return 0; ^ -"Test_name.c", line 52: error: expression must have a constant value +"Test_name.c", line 68: error: expression must have a constant value static_assert(during_unwinding() == 1); // std::terminate. ^ -"Test_name.c", line 46: note: std::terminate is called because an exception escapes from a destructor during stack unwinding +"Test_name.c", line 62: note: std::terminate is called because an exception escapes from a destructor during stack unwinding constexpr ~ThrowingDtor() noexcept(false) { throw 2; } ^ -"Test_name.c", line 63: error: expression must have a constant value +"Test_name.c", line 79: error: expression must have a constant value static_assert(delete_array() == 1); // std::terminate (element 0). ^ -"Test_name.c", line 56: note: std::terminate is called because an exception escapes from a destructor during stack unwinding +"Test_name.c", line 72: note: std::terminate is called because an exception escapes from a destructor during stack unwinding constexpr ~Logged() noexcept(false) { if (id > 0) throw id; } ^ -"Test_name.c", line 74: error: expression must have a constant value +"Test_name.c", line 90: error: expression must have a constant value static_assert(ctor_handler_member() == 1); // v accessed after destruction. ^ -"Test_name.c", line 68: note: access to uninitialized object +"Test_name.c", line 84: note: access to uninitialized object catch (int) { (void)(v + 1); } ^ -"Test_name.c", line 71: note: called from: +"Test_name.c", line 87: note: called from: try { CtorTry c; } catch (int) { return 1; } ^ -"Test_name.c", line 82: warning: statement is unreachable +"Test_name.c", line 98: warning: statement is unreachable return 0; ^ -"Test_name.c", line 84: error: expression must have a constant value +"Test_name.c", line 100: error: expression must have a constant value static_assert(handler_init() == 1); // std::terminate. ^ -"Test_name.c", line 78: note: std::terminate is called because an exception escapes from the initialization of a handler parameter +"Test_name.c", line 94: note: std::terminate is called because an exception escapes from the initialization of a handler parameter constexpr ThrowingCopy(const ThrowingCopy&) { throw 3; } ^ -"Test_name.c", line 93: error: expression must have a constant value +"Test_name.c", line 111: error: expression must have a constant value + static_assert(bad_cast() == 0); // Uncaught std::bad_cast. + ^ +"Test_name.c", line 108: note: an exception of type "std::bad_cast" is thrown and not caught during constant evaluation + (void)dynamic_cast(b); + ^ + +"Test_name.c", line 117: error: expression must have a constant value + static_assert(bad_typeid(nullptr) == 0); // Uncaught std::bad_typeid. + ^ +"Test_name.c", line 114: note: an exception of type "std::bad_typeid" is thrown and not caught during constant evaluation + (void)typeid(*p); + ^ + +"Test_name.c", line 124: error: expression must have a constant value + static_assert(bad_array_new_length(-2) == 0); // Uncaught + ^ +"Test_name.c", line 120: note: an exception of type "std::bad_array_new_length" is thrown and not caught during constant evaluation + int* p = new int[n]; + ^ + +"Test_name.c", line 134: error: expression must have a constant value static_assert(failed_assumption() == 0); // Null pointer dereference. ^ -"Test_name.c", line 91: note: attempt to access run-time storage +"Test_name.c", line 132: note: attempt to access run-time storage return *p; ^ -"Test_name.c", line 110: error: expression must have a constant value +"Test_name.c", line 151: error: expression must have a constant value static_assert(noexcept_construct_at() == 1); // std::terminate. ^ -"Test_name.c", line 104: note: std::terminate is called because an exception escapes from non-throwing function "std::construct_at(T *) noexcept [with T=ThrowingCtor]" (declared at line 100) +"Test_name.c", line 145: note: std::terminate is called because an exception escapes from non-throwing function "std::construct_at(T *) noexcept [with T=ThrowingCtor]" (declared at line 141) struct ThrowingCtor { constexpr ThrowingCtor() { throw 1; } }; ^ -"Test_name.c", line 107: note: called from: +"Test_name.c", line 148: note: called from: try { std::construct_at(&u.t); } catch (int) { return 1; } ^ -11 errors detected in the compilation of "Test_name.c". +14 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_32_cp.1.1.txt b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_32_cp.1.1.txt new file mode 100644 index 0000000000..7a45e578c6 --- /dev/null +++ b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_32_cp.1.1.txt @@ -0,0 +1,29 @@ +cpfe -DTEST_NUMBER=1 --gn 160100 --c++26 Test_name.c +Generated file content for "Test_name.c": + # 1 "Test_name.c" + # 7 + namespace std { + + + class exception_ptr { + void *p; + + public: exception_ptr() noexcept : p((nullptr)) { } + exception_ptr(const exception_ptr &other) noexcept : p(other.p) { } + ~exception_ptr() noexcept { } + }; + exception_ptr current_exception() noexcept; + int uncaught_exceptions() noexcept; + } + + std::exception_ptr f() { + try { + throw 1; + } catch (...) { + return __builtin_current_exception(); + } + } + + int g() { + return __builtin_uncaught_exceptions(); + } diff --git a/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_32_cp.1.matches.txt b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_32_cp.1.matches.txt new file mode 100644 index 0000000000..18f6902834 --- /dev/null +++ b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_32_cp.1.matches.txt @@ -0,0 +1,8 @@ +Matches for "return __builtin_current_exception\(\);": + <----------------------------------------------------------------------------- + return __builtin_current_exception(); + -----------------------------------------------------------------------------> +Matches for "return __builtin_uncaught_exceptions\(\);": + <----------------------------------------------------------------------------- + return __builtin_uncaught_exceptions(); + -----------------------------------------------------------------------------> diff --git a/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_64_cp.1.1.txt b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_64_cp.1.1.txt new file mode 100644 index 0000000000..7a45e578c6 --- /dev/null +++ b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_64_cp.1.1.txt @@ -0,0 +1,29 @@ +cpfe -DTEST_NUMBER=1 --gn 160100 --c++26 Test_name.c +Generated file content for "Test_name.c": + # 1 "Test_name.c" + # 7 + namespace std { + + + class exception_ptr { + void *p; + + public: exception_ptr() noexcept : p((nullptr)) { } + exception_ptr(const exception_ptr &other) noexcept : p(other.p) { } + ~exception_ptr() noexcept { } + }; + exception_ptr current_exception() noexcept; + int uncaught_exceptions() noexcept; + } + + std::exception_ptr f() { + try { + throw 1; + } catch (...) { + return __builtin_current_exception(); + } + } + + int g() { + return __builtin_uncaught_exceptions(); + } diff --git a/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_64_cp.1.matches.txt b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_64_cp.1.matches.txt new file mode 100644 index 0000000000..18f6902834 --- /dev/null +++ b/tests/tests/changes/.gh-368-gnu-cppbe.rto/edg_x86_64_cp.1.matches.txt @@ -0,0 +1,8 @@ +Matches for "return __builtin_current_exception\(\);": + <----------------------------------------------------------------------------- + return __builtin_current_exception(); + -----------------------------------------------------------------------------> +Matches for "return __builtin_uncaught_exceptions\(\);": + <----------------------------------------------------------------------------- + return __builtin_uncaught_exceptions(); + -----------------------------------------------------------------------------> diff --git a/tests/tests/changes/.gh-368-gnu-defined.rto/default.1.1.txt b/tests/tests/changes/.gh-368-gnu-defined.rto/default.1.1.txt new file mode 100644 index 0000000000..789fc0e8b0 --- /dev/null +++ b/tests/tests/changes/.gh-368-gnu-defined.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --gn 160100 --c++26 Test_name.c diff --git a/tests/tests/changes/.gh-368-gnu-errors.rto/default.1.1.txt b/tests/tests/changes/.gh-368-gnu-errors.rto/default.1.1.txt new file mode 100644 index 0000000000..b554c3a410 --- /dev/null +++ b/tests/tests/changes/.gh-368-gnu-errors.rto/default.1.1.txt @@ -0,0 +1,53 @@ +fe_only -DTEST_NUMBER=1 --gn 160100 --c++26 Test_name.c +"Test_name.c", line 113: error: expression must have a constant value + static_assert(k1() == 0); // error: null exception_ptr + ^ +"Test_name.c", line 110: note: std::rethrow_exception is called with a null std::exception_ptr + std::rethrow_exception(std::exception_ptr()); + ^ +"Test_name.c", line 110: note: called from: + std::rethrow_exception(std::exception_ptr()); + ^ + +"Test_name.c", line 116: error: expression must have a constant value + std::make_exception_ptr(3); // error + ^ +"Test_name.c", line 116: note: invalid use of address of interpreter storage + std::make_exception_ptr(3); // error + ^ + +"Test_name.c", line 122: error: expression must have a constant value + static_assert(k3() == 0); // error: uncaught int + ^ +"Test_name.c", line 98: note: an exception of type "int" is thrown and not caught during constant evaluation + throw __ex; + ^ + +"Test_name.c", line 128: error: expression must have a constant value + static_assert(leaked()); // error: exception object not destroyed + ^ +"Test_name.c", line 128: note: some dynamic allocations (total number = 1) were not deallocated + static_assert(leaked()); // error: exception object not destroyed + ^ +"Test_name.c", line 125: note: allocation occurred here + new std::exception_ptr(std::make_exception_ptr(4)); + ^ +"Test_name.c", line 128: note: an exception object of type "int" is not destroyed at the end of constant evaluation + static_assert(leaked()); // error: exception object not destroyed + ^ +"Test_name.c", line 98: note: the exception was thrown here + throw __ex; + ^ + +"Test_name.c", line 135: warning: statement is unreachable + return 0; + ^ + +"Test_name.c", line 137: error: expression must have a constant value + static_assert(exception_dtor_throws() == 0); // error: std::terminate + ^ +"Test_name.c", line 131: note: std::terminate is called because the destructor of an exception object exits by throwing an exception + constexpr ~ThrowingDtor() noexcept(false) { throw 1; } + ^ + +5 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/changes/.gh-368-gnu.rto/default.1.1.txt b/tests/tests/changes/.gh-368-gnu.rto/default.1.1.txt new file mode 100644 index 0000000000..789fc0e8b0 --- /dev/null +++ b/tests/tests/changes/.gh-368-gnu.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --gn 160100 --c++26 Test_name.c diff --git a/tests/tests/changes/.gh-368.rto/default.1.1.txt b/tests/tests/changes/.gh-368.rto/default.1.1.txt index 323350fe55..1ec2ffd7da 100644 --- a/tests/tests/changes/.gh-368.rto/default.1.1.txt +++ b/tests/tests/changes/.gh-368.rto/default.1.1.txt @@ -1,109 +1,109 @@ fe_only -DTEST_NUMBER=1 --c++26 Test_name.c -"Test_name.c", line 50: warning: statement is unreachable +"Test_name.c", line 70: warning: statement is unreachable return -1; ^ -"Test_name.c", line 58: warning: statement is unreachable +"Test_name.c", line 78: warning: statement is unreachable return -2; ^ -"Test_name.c", line 64: warning: statement is unreachable +"Test_name.c", line 84: warning: statement is unreachable return -1; ^ -"Test_name.c", line 70: warning: statement is unreachable +"Test_name.c", line 90: warning: statement is unreachable return -1; ^ -"Test_name.c", line 76: warning: statement is unreachable +"Test_name.c", line 96: warning: statement is unreachable return -1; ^ -"Test_name.c", line 82: warning: statement is unreachable +"Test_name.c", line 102: warning: statement is unreachable return -1; ^ -"Test_name.c", line 88: warning: statement is unreachable +"Test_name.c", line 108: warning: statement is unreachable return -2; ^ -"Test_name.c", line 95: warning: statement is unreachable +"Test_name.c", line 115: warning: statement is unreachable return -1; ^ -"Test_name.c", line 101: warning: statement is unreachable +"Test_name.c", line 121: warning: statement is unreachable return -2; ^ -"Test_name.c", line 108: warning: statement is unreachable +"Test_name.c", line 128: warning: statement is unreachable return -1; ^ -"Test_name.c", line 114: warning: statement is unreachable +"Test_name.c", line 134: warning: statement is unreachable return -1; ^ -"Test_name.c", line 120: warning: statement is unreachable +"Test_name.c", line 140: warning: statement is unreachable return -2; ^ -"Test_name.c", line 131: warning: statement is unreachable +"Test_name.c", line 151: warning: statement is unreachable return -1; ^ -"Test_name.c", line 138: warning: statement is unreachable +"Test_name.c", line 158: warning: statement is unreachable return -1; ^ -"Test_name.c", line 145: warning: statement is unreachable +"Test_name.c", line 165: warning: statement is unreachable return -2; ^ -"Test_name.c", line 151: warning: statement is unreachable +"Test_name.c", line 171: warning: statement is unreachable return -2; ^ -"Test_name.c", line 161: warning: statement is unreachable +"Test_name.c", line 181: warning: statement is unreachable return -2; ^ -"Test_name.c", line 168: warning: statement is unreachable +"Test_name.c", line 188: warning: statement is unreachable return -1; ^ -"Test_name.c", line 175: warning: statement is unreachable +"Test_name.c", line 195: warning: statement is unreachable return -1; ^ -"Test_name.c", line 181: warning: statement is unreachable +"Test_name.c", line 201: warning: statement is unreachable return -1; ^ -"Test_name.c", line 218: warning: statement is unreachable +"Test_name.c", line 238: warning: statement is unreachable return -1; ^ -"Test_name.c", line 244: warning: statement is unreachable +"Test_name.c", line 264: warning: statement is unreachable return -1; ^ -"Test_name.c", line 282: warning: statement is unreachable +"Test_name.c", line 302: warning: statement is unreachable return -1; ^ -"Test_name.c", line 312: warning: statement is unreachable +"Test_name.c", line 332: warning: statement is unreachable return -1; ^ -"Test_name.c", line 382: warning: statement is unreachable +"Test_name.c", line 402: warning: statement is unreachable return 0; ^ -"Test_name.c", line 552: warning: structured binding "x" was declared but never referenced +"Test_name.c", line 587: warning: structured binding "x" was declared but never referenced auto [x, y, z] = a; ^ -"Test_name.c", line 646: warning: statement is unreachable +"Test_name.c", line 724: warning: statement is unreachable return true; ^ diff --git a/tests/tests/changes/gh-368-errors.sft.cpp b/tests/tests/changes/gh-368-errors.sft.cpp index c3e6183ff6..1798a8781b 100644 --- a/tests/tests/changes/gh-368-errors.sft.cpp +++ b/tests/tests/changes/gh-368-errors.sft.cpp @@ -13,6 +13,22 @@ namespace std { constexpr exception() noexcept {} constexpr virtual ~exception() noexcept {} }; + class bad_cast : public exception { + public: + constexpr bad_cast() noexcept {} + }; + class bad_typeid : public exception { + public: + constexpr bad_typeid() noexcept {} + }; + class bad_alloc : public exception { + public: + constexpr bad_alloc() noexcept {} + }; + class bad_array_new_length : public bad_alloc { + public: + constexpr bad_array_new_length() noexcept {} + }; } struct E { int v; }; @@ -83,6 +99,31 @@ constexpr int handler_init() { } static_assert(handler_init() == 1); // std::terminate. +struct B { virtual ~B() = default; }; +struct D1 : B {}; +struct D2 : B {}; +constexpr int bad_cast() { + D1 d; + B& b = d; + (void)dynamic_cast(b); + return 0; +} +static_assert(bad_cast() == 0); // Uncaught std::bad_cast. + +constexpr int bad_typeid(B* p) { + (void)typeid(*p); + return 0; +} +static_assert(bad_typeid(nullptr) == 0); // Uncaught std::bad_typeid. + +constexpr int bad_array_new_length(int n) { + int* p = new int[n]; + delete[] p; + return 0; +} +static_assert(bad_array_new_length(-2) == 0); // Uncaught + // std::bad_array_new_length. + int nonconstexpr(); constexpr int failed_assumption() { // The failure to evaluate the assumption expression is not reported. diff --git a/tests/tests/changes/gh-368-gnu-cppbe.sft.cpp b/tests/tests/changes/gh-368-gnu-cppbe.sft.cpp new file mode 100644 index 0000000000..72e25234e8 --- /dev/null +++ b/tests/tests/changes/gh-368-gnu-cppbe.sft.cpp @@ -0,0 +1,31 @@ +//type:cppbe +//options_all:--gn 160100 --c++26 +//remark:C++-generating back end: GCC exception_ptr builtins +//match_regex:return __builtin_current_exception\(\); +//match_regex:return __builtin_uncaught_exceptions\(\); + +namespace std { + // As in the GCC C++ standard library, exception_ptr is not trivially + // copyable, so a call that returns it is not just a call node. + class exception_ptr { + void* p; + public: + exception_ptr() noexcept : p(nullptr) {} + exception_ptr(const exception_ptr& other) noexcept : p(other.p) {} + ~exception_ptr() noexcept {} + }; + exception_ptr current_exception() noexcept; + int uncaught_exceptions() noexcept; +} + +std::exception_ptr f() { + try { + throw 1; + } catch (...) { + return __builtin_current_exception(); + } +} + +int g() { + return __builtin_uncaught_exceptions(); +} diff --git a/tests/tests/changes/gh-368-gnu-defined.sft.cpp b/tests/tests/changes/gh-368-gnu-defined.sft.cpp new file mode 100644 index 0000000000..dc8096be6b --- /dev/null +++ b/tests/tests/changes/gh-368-gnu-defined.sft.cpp @@ -0,0 +1,29 @@ +//type:fp +//options_all:--gn 160100 --c++26 +//remark:P3068R6: std::uncaught_exceptions with a non-constexpr definition +// +// std::uncaught_exceptions is evaluated during constant evaluation even if +// the library defines it inline without constexpr (that definition is not +// used). + +namespace std { + inline int uncaught_exceptions() noexcept { return -1; } +} + +static_assert(__builtin_uncaught_exceptions() == 0); +static_assert(std::uncaught_exceptions() == 0); + +struct S { + int *p; + constexpr ~S() { *p = std::uncaught_exceptions(); } +}; +constexpr int during_unwinding() { + int n = -1; + try { + S s{&n}; + throw 1; + } catch (int) { + } + return n; +} +static_assert(during_unwinding() == 1); diff --git a/tests/tests/changes/gh-368-gnu-errors.sft.cpp b/tests/tests/changes/gh-368-gnu-errors.sft.cpp new file mode 100644 index 0000000000..4f72970fe3 --- /dev/null +++ b/tests/tests/changes/gh-368-gnu-errors.sft.cpp @@ -0,0 +1,137 @@ +//type:fn +//options_all:--gn 160100 --c++26 +//remark:P3068R6: Invalid std::exception_ptr uses in constant evaluation + +namespace std { + class exception { + public: + constexpr exception() noexcept {} + constexpr virtual ~exception() noexcept {} + constexpr virtual const char* what() const noexcept { return "exc"; } + }; + namespace __exception_ptr { class exception_ptr; } + using __exception_ptr::exception_ptr; + exception_ptr current_exception() noexcept; + template constexpr exception_ptr make_exception_ptr(_Ex) + noexcept; + void rethrow_exception(exception_ptr) __attribute__ ((__noreturn__)); + int uncaught_exceptions() noexcept __attribute__ ((__pure__)); + namespace __exception_ptr { + using std::rethrow_exception; + class exception_ptr { + void* _M_exception_object; + [[__gnu__::__gnu_inline__]] + constexpr inline explicit exception_ptr(void* __e) noexcept + : _M_exception_object(__e) { + if (_M_exception_object) { +#if __has_builtin(__builtin_eh_ptr_adjust_ref) + if consteval { + __builtin_eh_ptr_adjust_ref(_M_exception_object, 1); + return; + } +#endif + _M_addref(); + } + } + void _M_addref() noexcept; + void _M_release() noexcept; + friend exception_ptr std::current_exception() noexcept; + friend void std::rethrow_exception(exception_ptr); + template + friend constexpr exception_ptr std::make_exception_ptr(_Ex) noexcept; + public: + constexpr exception_ptr() noexcept : _M_exception_object(0) {} + constexpr exception_ptr(const exception_ptr& __other) noexcept + : _M_exception_object(__other._M_exception_object) { + if (_M_exception_object) { +#if __has_builtin(__builtin_eh_ptr_adjust_ref) + if consteval { + __builtin_eh_ptr_adjust_ref(_M_exception_object, 1); + return; + } +#endif + _M_addref(); + } + } + constexpr exception_ptr(decltype(nullptr)) noexcept + : _M_exception_object(nullptr) {} + constexpr exception_ptr(exception_ptr&& __o) noexcept + : _M_exception_object(__o._M_exception_object) { + __o._M_exception_object = nullptr; + } + constexpr exception_ptr& operator=(const exception_ptr& __other) + noexcept { + exception_ptr(__other).swap(*this); + return *this; + } + constexpr exception_ptr& operator=(exception_ptr&& __other) noexcept { + exception_ptr(static_cast(__other)).swap(*this); + return *this; + } + constexpr ~exception_ptr() noexcept { + if (_M_exception_object) { +#if __has_builtin(__builtin_eh_ptr_adjust_ref) + if consteval { + __builtin_eh_ptr_adjust_ref(_M_exception_object, -1); + return; + } +#endif + _M_release(); + } + } + constexpr void swap(exception_ptr& __other) noexcept { + void* __tmp = _M_exception_object; + _M_exception_object = __other._M_exception_object; + __other._M_exception_object = __tmp; + } + constexpr explicit operator bool() const noexcept { + return _M_exception_object; + } + friend constexpr bool operator==(const exception_ptr&, + const exception_ptr&) noexcept + = default; + }; + } + template + constexpr exception_ptr make_exception_ptr(_Ex __ex) noexcept { + try { + throw __ex; + } catch(...) { +#if __has_builtin(__builtin_current_exception) + return __builtin_current_exception(); +#else + return current_exception(); +#endif + } + } +} + +constexpr int k1() { + std::rethrow_exception(std::exception_ptr()); + return 0; +} +static_assert(k1() == 0); // error: null exception_ptr + +[[maybe_unused]] constexpr std::exception_ptr ep = + std::make_exception_ptr(3); // error + +constexpr int k3() { + std::rethrow_exception(std::make_exception_ptr(9)); + return 0; +} +static_assert(k3() == 0); // error: uncaught int + +constexpr bool leaked() { + new std::exception_ptr(std::make_exception_ptr(4)); + return true; +} +static_assert(leaked()); // error: exception object not destroyed + +struct ThrowingDtor { + constexpr ~ThrowingDtor() noexcept(false) { throw 1; } +}; +constexpr int exception_dtor_throws() { + try { throw ThrowingDtor(); } catch (...) { throw 2; } + return 0; +} +static_assert(exception_dtor_throws() == 0); // error: std::terminate diff --git a/tests/tests/changes/gh-368-gnu.sft.cpp b/tests/tests/changes/gh-368-gnu.sft.cpp new file mode 100644 index 0000000000..d22449dddb --- /dev/null +++ b/tests/tests/changes/gh-368-gnu.sft.cpp @@ -0,0 +1,245 @@ +//type:fp +//options_all:--gn 160100 --c++26 +//remark:P3068R6: std::exception_ptr in constant evaluation (GCC library model) + +namespace std { + class exception { + public: + constexpr exception() noexcept {} + constexpr virtual ~exception() noexcept {} + constexpr virtual const char* what() const noexcept { return "exc"; } + }; + namespace __exception_ptr { class exception_ptr; } + using __exception_ptr::exception_ptr; + exception_ptr current_exception() noexcept; + template constexpr exception_ptr make_exception_ptr(_Ex) + noexcept; + void rethrow_exception(exception_ptr) __attribute__ ((__noreturn__)); + int uncaught_exceptions() noexcept __attribute__ ((__pure__)); + namespace __exception_ptr { + using std::rethrow_exception; + class exception_ptr { + void* _M_exception_object; + [[__gnu__::__gnu_inline__]] + constexpr inline explicit exception_ptr(void* __e) noexcept + : _M_exception_object(__e) { + if (_M_exception_object) { +#if __has_builtin(__builtin_eh_ptr_adjust_ref) + if consteval { + __builtin_eh_ptr_adjust_ref(_M_exception_object, 1); + return; + } +#endif + _M_addref(); + } + } + void _M_addref() noexcept; + void _M_release() noexcept; + friend exception_ptr std::current_exception() noexcept; + friend void std::rethrow_exception(exception_ptr); + template + friend constexpr exception_ptr std::make_exception_ptr(_Ex) noexcept; + public: + constexpr exception_ptr() noexcept : _M_exception_object(0) {} + constexpr exception_ptr(const exception_ptr& __other) noexcept + : _M_exception_object(__other._M_exception_object) { + if (_M_exception_object) { +#if __has_builtin(__builtin_eh_ptr_adjust_ref) + if consteval { + __builtin_eh_ptr_adjust_ref(_M_exception_object, 1); + return; + } +#endif + _M_addref(); + } + } + constexpr exception_ptr(decltype(nullptr)) noexcept + : _M_exception_object(nullptr) {} + constexpr exception_ptr(exception_ptr&& __o) noexcept + : _M_exception_object(__o._M_exception_object) { + __o._M_exception_object = nullptr; + } + constexpr exception_ptr& operator=(const exception_ptr& __other) + noexcept { + exception_ptr(__other).swap(*this); + return *this; + } + constexpr exception_ptr& operator=(exception_ptr&& __other) noexcept { + exception_ptr(static_cast(__other)).swap(*this); + return *this; + } + constexpr ~exception_ptr() noexcept { + if (_M_exception_object) { +#if __has_builtin(__builtin_eh_ptr_adjust_ref) + if consteval { + __builtin_eh_ptr_adjust_ref(_M_exception_object, -1); + return; + } +#endif + _M_release(); + } + } + constexpr void swap(exception_ptr& __other) noexcept { + void* __tmp = _M_exception_object; + _M_exception_object = __other._M_exception_object; + __other._M_exception_object = __tmp; + } + constexpr explicit operator bool() const noexcept { + return _M_exception_object; + } + friend constexpr bool operator==(const exception_ptr&, + const exception_ptr&) noexcept + = default; + }; + } + template + constexpr exception_ptr make_exception_ptr(_Ex __ex) noexcept { + try { + throw __ex; + } catch(...) { +#if __has_builtin(__builtin_current_exception) + return __builtin_current_exception(); +#else + return current_exception(); +#endif + } + } +} + +#if !__has_builtin(__builtin_eh_ptr_adjust_ref) || \ + !__has_builtin(__builtin_current_exception) || \ + !__has_builtin(__builtin_uncaught_exceptions) +#error The exception_ptr builtins should be available. +#endif + +constexpr int h1() { + std::exception_ptr p; + try { throw 42; } catch (...) { p = std::current_exception(); } + if (!p) return 1; + try { std::rethrow_exception(p); } catch (int i) { return i; } + return 2; +} +static_assert(h1() == 42); + +constexpr int h2() { + auto p = std::make_exception_ptr(7); + std::exception_ptr q = p; + try { std::rethrow_exception(q); } catch (int i) { return i + (p == q); } + return 0; +} +static_assert(h2() == 8); + +constexpr bool h3() { return !std::current_exception(); } +static_assert(h3()); + +constexpr int h4() { + try { + throw 1; + } catch (...) { + try { + throw 2; + } catch (...) { + try { + std::rethrow_exception(std::current_exception()); + } catch (int i) { + if (i != 2) return 0; + } + } + try { + std::rethrow_exception(std::current_exception()); + } catch (int i) { + return i; + } + } + return 0; +} +static_assert(h4() == 1); + +struct Cnt { + int* n; + constexpr Cnt(int* n) : n(n) {} + constexpr Cnt(const Cnt& o) : n(o.n) {} + constexpr ~Cnt() { ++*n; } +}; +constexpr int h5() { + int n = 0; + { + std::exception_ptr p; + try { throw Cnt(&n); } catch (...) { p = std::current_exception(); } + if (n != 0) return -1; + } + return n; +} +static_assert(h5() == 1); + +constexpr int h6() { + std::exception_ptr p = std::make_exception_ptr(5); + std::exception_ptr q = static_cast(p); + return (!p && q) ? 1 : 0; +} +static_assert(h6() == 1); + +constexpr int h7() { + try { + std::rethrow_exception(std::make_exception_ptr(9)); + } catch (int i) { + return i; + } + return 0; +} +static_assert(h7() == 9); + +constexpr int h8() { + int n = 0; + try { + std::rethrow_exception(std::make_exception_ptr(Cnt(&n))); + } catch (const Cnt&) { + } + return n; +} +static_assert(h8() == 2); + +struct Uncaught { + int* n; + constexpr ~Uncaught() { + *n = __builtin_uncaught_exceptions() * 10 + std::uncaught_exceptions(); + } +}; +constexpr int h9() { + int n = -1; + try { + Uncaught u{&n}; + throw 1; + } catch (int) { + if (__builtin_uncaught_exceptions() != 0) return -2; + } + return n; +} +static_assert(h9() == 11); +static_assert(__builtin_uncaught_exceptions() == 0); + +constexpr bool thrower() { throw 1; } +constexpr int constant_p_throws() { + try { + return __builtin_constant_p(thrower()) + __builtin_uncaught_exceptions(); + } catch (int) { + return -1; + } +} +static_assert(constant_p_throws() == 0); + +// An evaluation whose failure is ignored cannot release an exception that +// existed before it (e.g., by destroying a std::exception_ptr that it did not +// create); that makes the evaluation fail instead. +int nonconstexpr_int; +constexpr int ignored_release() { + auto p = std::make_exception_ptr(7); + void *obj = nullptr; + try { std::rethrow_exception(p); } catch (int &i) { obj = &i; } + int r = __builtin_constant_p((p.~exception_ptr(), nonconstexpr_int)); + r += __builtin_constant_p((__builtin_eh_ptr_adjust_ref(obj, -1), 1)); + [[assume((__builtin_eh_ptr_adjust_ref(obj, -1), true))]]; + try { std::rethrow_exception(p); } catch (int i) { return r + i; } + return -1; +} +static_assert(ignored_release() == 7); diff --git a/tests/tests/changes/gh-368.sft.cpp b/tests/tests/changes/gh-368.sft.cpp index 3b74ceef8c..323c77c019 100644 --- a/tests/tests/changes/gh-368.sft.cpp +++ b/tests/tests/changes/gh-368.sft.cpp @@ -14,6 +14,26 @@ namespace std { constexpr virtual ~exception() noexcept {} constexpr virtual const char* what() const noexcept { return "exc"; } }; + class bad_cast : public exception { + public: + constexpr bad_cast() noexcept {} + constexpr const char* what() const noexcept override { return "bc"; } + }; + class bad_typeid : public exception { + public: + constexpr bad_typeid() noexcept {} + constexpr const char* what() const noexcept override { return "bt"; } + }; + class bad_alloc : public exception { + public: + constexpr bad_alloc() noexcept {} + }; + class bad_array_new_length : public bad_alloc { + public: + constexpr bad_array_new_length() noexcept {} + constexpr const char* what() const noexcept override { return "banl"; } + }; + int uncaught_exceptions() noexcept; } constexpr bool eq(const char* a, const char* b) { @@ -382,6 +402,21 @@ namespace unwinding { return 0; } static_assert(throwing_copy() == 3); + + constexpr int uncaught() { + struct U { + int* out; + constexpr ~U() { *out = std::uncaught_exceptions(); } + }; + int during = -1, before = std::uncaught_exceptions(); + try { + U u{&during}; + throw 1; + } catch (int) { + return before * 100 + during * 10 + std::uncaught_exceptions(); + } + } + static_assert(uncaught() == 10); } namespace function_try_blocks { @@ -620,6 +655,49 @@ namespace partial_construction { static_assert(extended_temp() == 1); } +namespace library { + struct B { virtual ~B() = default; }; + struct D1 : B {}; + struct D2 : B {}; + + constexpr int bad_cast() { + D1 d; + B& b = d; + try { + D2& d2 = dynamic_cast(b); + return 1; + } catch (const std::bad_cast& e) { + return eq(e.what(), "bc") ? 2 : 3; + } + } + static_assert(bad_cast() == 2); + + constexpr int bad_typeid(B* p) { + try { + (void)typeid(*p); + return 1; + } catch (const std::exception& e) { + return eq(e.what(), "bt") ? 2 : 3; + } + } + static_assert(bad_typeid(nullptr) == 2); + + constexpr int bad_array_new_length(int n) { + try { + int* p = new int[n]{1, 2, 3}; + delete[] p; + return 1; + } catch (const std::bad_array_new_length& e) { + return eq(e.what(), "banl") ? 2 : 3; + } catch (const std::bad_alloc&) { + return 4; + } + } + static_assert(bad_array_new_length(3) == 1); + static_assert(bad_array_new_length(2) == 2); + static_assert(bad_array_new_length(-1) == 2); +} + namespace ignored_evaluations { // An exception thrown by an assumption expression (which is not evaluated // at run time) does not propagate. @@ -632,7 +710,7 @@ namespace ignored_evaluations { throw 5; } catch (int i) { [[assume((throw, true))]]; - return i; + return i + std::uncaught_exceptions(); } } static_assert(assume_rethrows() == 5); @@ -651,7 +729,7 @@ namespace ignored_evaluations { throw 6; } catch (int i) { [[assume(fails_in_handler())]]; - return i; + return i + std::uncaught_exceptions(); } } static_assert(assume_fails() == 6); diff --git a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.1.1.txt b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.1.1.txt index 20f0e7e1c4..2879e0f716 100644 --- a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.1.1.txt +++ b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.1.1.txt @@ -535,6 +535,7 @@ fe_only -DTEST_NUMBER=1 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_ctzl */ int __builtin_ctzl(unsigned long); /* __builtin_ctzll */ int __builtin_ctzll(unsigned long long); /* __builtin_ctzs */ unsigned short __builtin_ctzs(unsigned short); +/* __builtin_current_exception */ void *__builtin_current_exception() throw(); /* __builtin_dcgettext */ char *__builtin_dcgettext(const char *, const char *, int); /* __builtin_dgettext */ char *__builtin_dgettext(const char *, const char *); /* __builtin_drem */ double __builtin_drem(double, double); @@ -546,6 +547,7 @@ fe_only -DTEST_NUMBER=1 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_eh_copy_values */ void __builtin_eh_copy_values(int, int); /* __builtin_eh_filter */ long __builtin_eh_filter(int); /* __builtin_eh_pointer */ void *__builtin_eh_pointer(int); +/* __builtin_eh_ptr_adjust_ref */ void __builtin_eh_ptr_adjust_ref(void *, int) throw(); /* __builtin_eh_return */ void __builtin_eh_return(long, void *); /* __builtin_eh_return_data_regno */ int __builtin_eh_return_data_regno(int); /* __builtin_erf */ double __builtin_erf(double); @@ -4950,6 +4952,7 @@ fe_only -DTEST_NUMBER=1 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_umul_overflow */ bool __builtin_umul_overflow(unsigned int, unsigned int, unsigned int *); /* __builtin_umull_overflow */ bool __builtin_umull_overflow(unsigned long, unsigned long, unsigned long *); /* __builtin_umulll_overflow */ bool __builtin_umulll_overflow(unsigned long long, unsigned long long, unsigned long long *); +/* __builtin_uncaught_exceptions */ int __builtin_uncaught_exceptions() throw(); /* __builtin_unreachable */ void __builtin_unreachable(); /* __builtin_unwind_init */ void __builtin_unwind_init(); /* __builtin_unwind_resume */ void __builtin_unwind_resume(void *); diff --git a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.2.1.txt b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.2.1.txt index a7e92ecf51..092e1be0a1 100644 --- a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.2.1.txt +++ b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.2.1.txt @@ -518,6 +518,7 @@ fe_only -DTEST_NUMBER=2 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_ctzl */ int __builtin_ctzl(unsigned long); /* __builtin_ctzll */ int __builtin_ctzll(unsigned long long); /* __builtin_ctzs */ unsigned short __builtin_ctzs(unsigned short); +/* __builtin_current_exception */ void *__builtin_current_exception() throw(); /* __builtin_dcgettext */ char *__builtin_dcgettext(const char *, const char *, int); /* __builtin_dgettext */ char *__builtin_dgettext(const char *, const char *); /* __builtin_drem */ double __builtin_drem(double, double); @@ -529,6 +530,7 @@ fe_only -DTEST_NUMBER=2 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_eh_copy_values */ void __builtin_eh_copy_values(int, int); /* __builtin_eh_filter */ int __builtin_eh_filter(int); /* __builtin_eh_pointer */ void *__builtin_eh_pointer(int); +/* __builtin_eh_ptr_adjust_ref */ void __builtin_eh_ptr_adjust_ref(void *, int) throw(); /* __builtin_eh_return */ void __builtin_eh_return(int, void *); /* __builtin_eh_return_data_regno */ int __builtin_eh_return_data_regno(int); /* __builtin_erf */ double __builtin_erf(double); @@ -4829,6 +4831,7 @@ fe_only -DTEST_NUMBER=2 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_umul_overflow */ bool __builtin_umul_overflow(unsigned int, unsigned int, unsigned int *); /* __builtin_umull_overflow */ bool __builtin_umull_overflow(unsigned long, unsigned long, unsigned long *); /* __builtin_umulll_overflow */ bool __builtin_umulll_overflow(unsigned long long, unsigned long long, unsigned long long *); +/* __builtin_uncaught_exceptions */ int __builtin_uncaught_exceptions() throw(); /* __builtin_unreachable */ void __builtin_unreachable(); /* __builtin_unwind_init */ void __builtin_unwind_init(); /* __builtin_unwind_resume */ void __builtin_unwind_resume(void *); diff --git a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.3.1.txt b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.3.1.txt index 489c1321f2..17a928b4f3 100644 --- a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.3.1.txt +++ b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.3.1.txt @@ -2545,6 +2545,7 @@ fe_only -DTEST_NUMBER=3 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_ctzimax */ int __builtin_ctzimax(unsigned long); /* __builtin_ctzl */ int __builtin_ctzl(unsigned long); /* __builtin_ctzll */ int __builtin_ctzll(unsigned long long); +/* __builtin_current_exception */ void *__builtin_current_exception() throw(); /* __builtin_dcgettext */ char *__builtin_dcgettext(const char *, const char *, int); /* __builtin_dgettext */ char *__builtin_dgettext(const char *, const char *); /* __builtin_drem */ double __builtin_drem(double, double); @@ -2556,6 +2557,7 @@ fe_only -DTEST_NUMBER=3 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_eh_copy_values */ void __builtin_eh_copy_values(int, int); /* __builtin_eh_filter */ long __builtin_eh_filter(int); /* __builtin_eh_pointer */ void *__builtin_eh_pointer(int); +/* __builtin_eh_ptr_adjust_ref */ void __builtin_eh_ptr_adjust_ref(void *, int) throw(); /* __builtin_eh_return */ void __builtin_eh_return(long, void *); /* __builtin_eh_return_data_regno */ int __builtin_eh_return_data_regno(int); /* __builtin_erf */ double __builtin_erf(double); @@ -3300,6 +3302,7 @@ fe_only -DTEST_NUMBER=3 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_umul_overflow */ bool __builtin_umul_overflow(unsigned int, unsigned int, unsigned int *); /* __builtin_umull_overflow */ bool __builtin_umull_overflow(unsigned long, unsigned long, unsigned long *); /* __builtin_umulll_overflow */ bool __builtin_umulll_overflow(unsigned long long, unsigned long long, unsigned long long *); +/* __builtin_uncaught_exceptions */ int __builtin_uncaught_exceptions() throw(); /* __builtin_unreachable */ void __builtin_unreachable(); /* __builtin_unwind_init */ void __builtin_unwind_init(); /* __builtin_unwind_resume */ void __builtin_unwind_resume(void *); diff --git a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.4.1.txt b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.4.1.txt index d1c039b7f4..783daf1c75 100644 --- a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.4.1.txt +++ b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.4.1.txt @@ -556,6 +556,7 @@ fe_only -DTEST_NUMBER=4 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_ctzimax */ int __builtin_ctzimax(unsigned long long); /* __builtin_ctzl */ int __builtin_ctzl(unsigned long); /* __builtin_ctzll */ int __builtin_ctzll(unsigned long long); +/* __builtin_current_exception */ void *__builtin_current_exception() throw(); /* __builtin_cxa_end_cleanup */ void __builtin_cxa_end_cleanup(); /* __builtin_dcgettext */ char *__builtin_dcgettext(const char *, const char *, int); /* __builtin_dgettext */ char *__builtin_dgettext(const char *, const char *); @@ -568,6 +569,7 @@ fe_only -DTEST_NUMBER=4 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_eh_copy_values */ void __builtin_eh_copy_values(int, int); /* __builtin_eh_filter */ int __builtin_eh_filter(int); /* __builtin_eh_pointer */ void *__builtin_eh_pointer(int); +/* __builtin_eh_ptr_adjust_ref */ void __builtin_eh_ptr_adjust_ref(void *, int) throw(); /* __builtin_eh_return */ void __builtin_eh_return(int, void *); /* __builtin_eh_return_data_regno */ int __builtin_eh_return_data_regno(int); /* __builtin_erf */ double __builtin_erf(double); @@ -4208,6 +4210,7 @@ fe_only -DTEST_NUMBER=4 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_umul_overflow */ bool __builtin_umul_overflow(unsigned int, unsigned int, unsigned int *); /* __builtin_umull_overflow */ bool __builtin_umull_overflow(unsigned long, unsigned long, unsigned long *); /* __builtin_umulll_overflow */ bool __builtin_umulll_overflow(unsigned long long, unsigned long long, unsigned long long *); +/* __builtin_uncaught_exceptions */ int __builtin_uncaught_exceptions() throw(); /* __builtin_unreachable */ void __builtin_unreachable(); /* __builtin_unwind_init */ void __builtin_unwind_init(); /* __builtin_unwind_resume */ void __builtin_unwind_resume(void *); diff --git a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.5.1.txt b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.5.1.txt index a07916a4ae..906c7cdf84 100644 --- a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.5.1.txt +++ b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.5.1.txt @@ -531,6 +531,7 @@ fe_only -DTEST_NUMBER=5 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_ctzimax */ int __builtin_ctzimax(unsigned long); /* __builtin_ctzl */ int __builtin_ctzl(unsigned long); /* __builtin_ctzll */ int __builtin_ctzll(unsigned long long); +/* __builtin_current_exception */ void *__builtin_current_exception() throw(); /* __builtin_dcgettext */ char *__builtin_dcgettext(const char *, const char *, int); /* __builtin_dgettext */ char *__builtin_dgettext(const char *, const char *); /* __builtin_drem */ double __builtin_drem(double, double); @@ -542,6 +543,7 @@ fe_only -DTEST_NUMBER=5 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_eh_copy_values */ void __builtin_eh_copy_values(int, int); /* __builtin_eh_filter */ long __builtin_eh_filter(int); /* __builtin_eh_pointer */ void *__builtin_eh_pointer(int); +/* __builtin_eh_ptr_adjust_ref */ void __builtin_eh_ptr_adjust_ref(void *, int) throw(); /* __builtin_eh_return */ void __builtin_eh_return(long, void *); /* __builtin_eh_return_data_regno */ int __builtin_eh_return_data_regno(int); /* __builtin_erf */ double __builtin_erf(double); @@ -1336,6 +1338,7 @@ fe_only -DTEST_NUMBER=5 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_umul_overflow */ bool __builtin_umul_overflow(unsigned int, unsigned int, unsigned int *); /* __builtin_umull_overflow */ bool __builtin_umull_overflow(unsigned long, unsigned long, unsigned long *); /* __builtin_umulll_overflow */ bool __builtin_umulll_overflow(unsigned long long, unsigned long long, unsigned long long *); +/* __builtin_uncaught_exceptions */ int __builtin_uncaught_exceptions() throw(); /* __builtin_unreachable */ void __builtin_unreachable(); /* __builtin_unwind_init */ void __builtin_unwind_init(); /* __builtin_unwind_resume */ void __builtin_unwind_resume(void *); diff --git a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.6.1.txt b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.6.1.txt index 5e2df3de01..87783c5a40 100644 --- a/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.6.1.txt +++ b/tests/tests/edg/cmeerw/.gnats-28642-dump.rto/default.6.1.txt @@ -514,6 +514,7 @@ fe_only -DTEST_NUMBER=6 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_ctzimax */ int __builtin_ctzimax(unsigned long long); /* __builtin_ctzl */ int __builtin_ctzl(unsigned long); /* __builtin_ctzll */ int __builtin_ctzll(unsigned long long); +/* __builtin_current_exception */ void *__builtin_current_exception() throw(); /* __builtin_dcgettext */ char *__builtin_dcgettext(const char *, const char *, int); /* __builtin_dgettext */ char *__builtin_dgettext(const char *, const char *); /* __builtin_drem */ double __builtin_drem(double, double); @@ -525,6 +526,7 @@ fe_only -DTEST_NUMBER=6 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_eh_copy_values */ void __builtin_eh_copy_values(int, int); /* __builtin_eh_filter */ int __builtin_eh_filter(int); /* __builtin_eh_pointer */ void *__builtin_eh_pointer(int); +/* __builtin_eh_ptr_adjust_ref */ void __builtin_eh_ptr_adjust_ref(void *, int) throw(); /* __builtin_eh_return */ void __builtin_eh_return(int, void *); /* __builtin_eh_return_data_regno */ int __builtin_eh_return_data_regno(int); /* __builtin_erf */ double __builtin_erf(double); @@ -1310,6 +1312,7 @@ fe_only -DTEST_NUMBER=6 --gn 160000 --c++ -d-dump_builtins --set_flag preload_bu /* __builtin_umul_overflow */ bool __builtin_umul_overflow(unsigned int, unsigned int, unsigned int *); /* __builtin_umull_overflow */ bool __builtin_umull_overflow(unsigned long, unsigned long, unsigned long *); /* __builtin_umulll_overflow */ bool __builtin_umulll_overflow(unsigned long long, unsigned long long, unsigned long long *); +/* __builtin_uncaught_exceptions */ int __builtin_uncaught_exceptions() throw(); /* __builtin_unreachable */ void __builtin_unreachable(); /* __builtin_unwind_init */ void __builtin_unwind_init(); /* __builtin_unwind_resume */ void __builtin_unwind_resume(void *); diff --git a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/default.2.1.txt b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/default.2.1.txt index ad76d16536..6994860ed7 100644 --- a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/default.2.1.txt +++ b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/default.2.1.txt @@ -488,7 +488,7 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "Test_name.c", line 495: error: expression must have a constant value constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^ -"Test_name.c", line 495: note: attempt to access run-time storage +"Test_name.c", line 495: note: operation not allowed on null pointer constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^ diff --git a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32.2.1.txt b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32.2.1.txt index ad790e2063..9c022fe967 100644 --- a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32.2.1.txt +++ b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32.2.1.txt @@ -497,7 +497,7 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "Test_name.c", line 495: error: expression must have a constant value constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^ -"Test_name.c", line 495: note: attempt to access run-time storage +"Test_name.c", line 495: note: operation not allowed on null pointer constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^ diff --git a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32_cp.2.1.txt b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32_cp.2.1.txt index 41df730ffc..712fa1cace 100644 --- a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32_cp.2.1.txt +++ b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_32_cp.2.1.txt @@ -485,7 +485,7 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "Test_name.c", line 495: error: expression must have a constant value constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^ -"Test_name.c", line 495: note: attempt to access run-time storage +"Test_name.c", line 495: note: operation not allowed on null pointer constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^ diff --git a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_64_cp.2.1.txt b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_64_cp.2.1.txt index 57fddb4333..c2254e8843 100644 --- a/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_64_cp.2.1.txt +++ b/tests/tests/imported/clang/cpp/CXX/expr/expr.const/.p2-0x.rto/edg_x86_64_cp.2.1.txt @@ -476,7 +476,7 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers "Test_name.c", line 495: error: expression must have a constant value constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^ -"Test_name.c", line 495: note: attempt to access run-time storage +"Test_name.c", line 495: note: operation not allowed on null pointer constexpr const std::type_info &ti1 = typeid(*p); // expected-error {{must be initialized by a constant expression}} cxx11-note {{typeid applied to expression of polymorphic type 'S'}} cxx20-note {{dereferencing a null pointer}} ^