> On 17 Jun 2024, at 13:15, Iain Sandoe <iains....@gmail.com> wrote:
>
> This patch came out of a discussion on Mattermost about how to deal
> with contracts/coroutines integration. Actually, it would also allow
> some semantic checking to be deferred until the same spot - at which
> time there are no dependent types, which can simplify the process.
>
> NOTE: this is a fix for bugs in the existing '2a' contracts impl. it
> does not attempt to make any of the changes required by P2900 to
> either code-gen or constexpr handling.
>
> Tested on x86_64-darwin, so far, OK for trunk if testing succeeds on
> x86_64/powerpc64 linux too?
Testing was sucessful and these patches have been on the contracts-noattr
compiler explorer instance for a while now.
OK for trunk?
thanks
Iain
> thanks,
> Iain
>
> --- 8< ---
>
> The current implementation of contracts emits the checks into function
> bodies in three places; for pre-conditions at the start of the body,
> for asserts in-line in the function body and for post-conditions as an
> addition to return statements.
>
> In general (at least with existing "2a" contract semantics) the in-line
> contract asserts behave as expected.
>
> However, the mechanism is not applicable to:
>
> * Handling pre conditions in coroutines since, for those, the standard
> specifies a wrapping of the original function body by functionality
> implementing initial and final suspends (along with some housekeeping
> to route exceptions). Thus for such transformed function bodies, the
> preconditions then get actioned after the initial suspend, which does
> not behave as intended.
>
> * Handling post conditions in functions that do not have return
> statements (which applies to coroutines and void functions).
>
> In the following, we identify a potentially transformed function body
> (in the case of coroutines, this is usually called the "ramp()" function).
>
> The patch here re-implements the code insertion in one of the two following
> ways (code for exposition only):
>
> * For functions with no post-conditions we wrap the potentially
> transformed function as follows:
>
> {
> handle_pre_condition_checking ();
> potentially_transformed_function_body ();
> }
>
> This implements the intent that the preconditions are processed after
> the function parameters are initialised but before any other actions.
>
> * For functions with post-conditions:
>
> try
> {
> if (preconditions_exist)
> handle_pre_condition_checking ();
> potentially_transformed_function_body ();
> }
> finally
> {
> handle_post_condition_checking ();
> }
> else [only if the function is not marked noexcept(true) ]
> {
> __rethrow ();
> }
>
> In this, post-conditions [that might apply to the return value etc.]
> are evaluated on every non-exceptional edge out of the function.
>
> At present, the model here is that exceptions thrown by the function
> propagate upwards as if there were no contracts present. If the desired
> semantic becomes that an exception is counted as equivalent to a contract
> violation - then we can add a second handler in place of the rethrow.
>
> At constexpr time we need to evaluate the contract conditions, but not
> the exceptional path, which is handled by a flag on the EH_ELSE_EXPR that
> indicates it is in use for contract handling.
>
> This patch specifically does not address changes to code-gen and constexpr
> handling that are contained in P2900.
>
> PR c++/115434
> PR c++/110871
> PR c++/110872
>
> gcc/cp/ChangeLog:
>
> * constexpr.cc (cxx_eval_constant_expression): Handle EH_ELSE_EXPR.
> * contracts.cc (finish_contract_attribute): Remove excess line.
> (build_contract_condition_function): Post condition handlers are
> void now.
> (emit_postconditions_cleanup): Remove.
> (emit_postconditions): New.
> (add_pre_condition_fn_call): New.
> (add_post_condition_fn_call): New.
> (apply_preconditions): New.
> (apply_postconditions): New.
> (maybe_apply_function_contracts): New.
> (apply_postcondition_to_return): Remove.
> * contracts.h (apply_postcondition_to_return): Remove.
> (maybe_apply_function_contracts): Add.
> * coroutines.cc (coro_build_actor_or_destroy_function): Do not
> copy contracts to coroutine helpers.
> * cp-tree.h (CONTRACT_EH_ELSE_P): New.
> * decl.cc (finish_function): Handle wrapping a possibly
> transformed function body in contract checks.
> * typeck.cc (check_return_expr): Remove handling of post
> conditions on return expressions.
>
> gcc/ChangeLog:
>
> * gimplify.cc (struct gimplify_ctx): Add a flag to show we are
> expending a handler.
> (gimplify_expr): When we are expanding a handler, and the body
> transforms might have re-written DECL_RESULT into a gimple var,
> ensure that hander references to DECL_RESULT are also re-written
> to refer to the gimple var.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/contracts/pr115434.C: New test.
> * g++.dg/coroutines/pr110871.C: New test.
> * g++.dg/coroutines/pr110872.C: New test.
>
> Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>
> ---
> gcc/cp/constexpr.cc | 16 ++
> gcc/cp/contracts.cc | 249 ++++++++++++---------
> gcc/cp/contracts.h | 3 +-
> gcc/cp/coroutines.cc | 2 +
> gcc/cp/cp-tree.h | 3 +
> gcc/cp/decl.cc | 23 +-
> gcc/cp/typeck.cc | 13 +-
> gcc/gimplify.cc | 13 +-
> gcc/testsuite/g++.dg/contracts/pr115434.C | 16 ++
> gcc/testsuite/g++.dg/coroutines/pr110871.C | 62 +++++
> gcc/testsuite/g++.dg/coroutines/pr110872.C | 49 ++++
> 11 files changed, 319 insertions(+), 130 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/contracts/pr115434.C
> create mode 100644 gcc/testsuite/g++.dg/coroutines/pr110871.C
> create mode 100644 gcc/testsuite/g++.dg/coroutines/pr110872.C
>
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index bd72533491e..9b66b1753ad 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -7808,6 +7808,22 @@ cxx_eval_constant_expression (const constexpr_ctx
> *ctx, tree t,
> non_constant_p, overflow_p);
> break;
>
> + case EH_ELSE_EXPR:
> + /* Evaluate any cleanup that applies to non-EH exits, this only for
> + the output of the diagnostics ??? what is really meant to happen
> + at constexpr-time?... */
> + cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0), vc_discard,
> + non_constant_p, overflow_p);
> +
> + /* The presence of a contract should not affect the constexpr. */
> + if (CONTRACT_EH_ELSE_P (t))
> + break;
> + if (!*non_constant_p)
> + /* Also evaluate the EH handler. */
> + cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 1), vc_discard,
> + non_constant_p, overflow_p);
> + break;
> +
> case CLEANUP_STMT:
> r = cxx_eval_constant_expression (ctx, CLEANUP_BODY (t), lval,
> non_constant_p, overflow_p,
> diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc
> index 0822624a910..064f0b6a9d8 100644
> --- a/gcc/cp/contracts.cc
> +++ b/gcc/cp/contracts.cc
> @@ -88,64 +88,7 @@ along with GCC; see the file COPYING3. If not see
> return -v;
> }
>
> - The original decl is left alone and instead calls are generated to
> pre/post
> - functions within the body:
> -
> - void fun.pre(int v)
> - {
> - [[ assert: v > 0 ]];
> - }
> - int fun.post(int v, int __r)
> - {
> - [[ assert: __r < 0 ]];
> - return __r;
> - }
> - int fun(int v)
> - {
> - fun.pre(v);
> - return fun.post(v, -v);
> - }
> -
> - If fun returns in memory, the return value is not passed through the post
> - function; instead, the return object is initialized directly and then
> passed
> - to the post function by invisible reference.
> -
> - This sides steps a number of issues with having to rewrite the bodies or
> - rewrite the parsed conditions as the parameters to the original function
> - changes (as happens during redeclaration). The ultimate goal is to get
> - something that optimizes well along the lines of
> -
> - int fun(int v)
> - {
> - [[ assert: v > 0 ]];
> - auto &&__r = -v;
> - goto out;
> - out:
> - [[ assert: __r < 0 ]];
> - return __r;
> - }
> -
> - With the idea being that multiple return statements could collapse the
> - function epilogue after inlining the pre/post functions. clang is able
> - to collapse common function epilogues, while gcc needs -O3 -Os combined.
> -
> - Directly laying the pre contracts down in the function body doesn't have
> - many issues. The post contracts may need to be repeated multiple times,
> once
> - for each return, or a goto epilogue would need to be generated.
> - For this initial implementation, generating function calls and letting
> - later optimizations decide whether to inline and duplicate the actual
> - checks or whether to collapse the shared epilogue was chosen.
> -
> - For cdtors a post contract is implemented using a CLEANUP_STMT.
> -
> - FIXME the compiler already shores cleanup code on multiple exit paths, so
> - this outlining seems unnecessary if we represent the postcondition as a
> - cleanup for all functions.
> -
> - More helpful for optimization might be to make the contracts a wrapper
> - function (for non-variadic functions), that could be inlined into a
> - caller while preserving the call to the actual function? Either that or
> - mirror a never-continue post contract with an assume in the caller. */
> + TODO: reiterate the revised implementation. */
>
> #include "config.h"
> #include "system.h"
> @@ -801,7 +744,6 @@ finish_contract_attribute (tree identifier, tree contract)
> tree attribute = build_tree_list (build_tree_list (NULL_TREE, identifier),
> build_tree_list (NULL_TREE, contract));
>
> -
> /* Mark the attribute as dependent if the condition is dependent.
>
> TODO: I'm not sure this is strictly necessary. It's going to be marked as
> @@ -1444,10 +1386,8 @@ build_contract_condition_function (tree fndecl, bool
> pre)
> *last = build_tree_list (NULL_TREE, value_type);
> TREE_CHAIN (*last) = void_list_node;
>
> - if (aggregate_value_p (value_type, fndecl))
> - /* If FNDECL returns in memory, don't return the value from the
> - postcondition. */
> - value_type = void_type_node;
> + /* The handler is a void return. */
> + value_type = void_type_node;
> }
>
> TREE_TYPE (fn) = build_function_type (value_type, arg_types);
> @@ -1882,15 +1822,12 @@ emit_preconditions (tree attr)
> return emit_contract_conditions (attr, PRECONDITION_STMT);
> }
>
> -/* Emit statements for postcondition attributes. */
> +/* Emit statements for precondition attributes. */
>
> static void
> -emit_postconditions_cleanup (tree contracts)
> +emit_postconditions (tree attr)
> {
> - tree stmts = push_stmt_list ();
> - emit_contract_conditions (contracts, POSTCONDITION_STMT);
> - stmts = pop_stmt_list (stmts);
> - push_cleanup (NULL_TREE, stmts, /*eh_only*/false);
> + return emit_contract_conditions (attr, POSTCONDITION_STMT);
> }
>
> /* We're compiling the pre/postcondition function CONDFN; remap any FN
> @@ -1993,32 +1930,152 @@ start_function_contracts (tree decl1)
> if (!handle_contracts_p (decl1))
> return;
>
> + /* For cdtors, we evaluate the contracts check inline. */
> if (!outline_contracts_p (decl1))
> - {
> - emit_preconditions (DECL_CONTRACTS (current_function_decl));
> - emit_postconditions_cleanup (DECL_CONTRACTS (current_function_decl));
> - return;
> - }
> + return;
>
> /* Contracts may have just been added without a chance to parse them, though
> we still need the PRE_FN available to generate a call to it. */
> if (!DECL_PRE_FN (decl1))
> build_contract_function_decls (decl1);
>
> +}
> +
> +/* If we have a precondition function and it's valid, call it. */
> +
> +static void
> +add_pre_condition_fn_call (tree fndecl)
> +{
> /* If we're starting a guarded function with valid contracts, we need to
> insert a call to the pre function. */
> - if (DECL_PRE_FN (decl1)
> - && DECL_PRE_FN (decl1) != error_mark_node)
> + gcc_checking_assert (DECL_PRE_FN (fndecl)
> + && DECL_PRE_FN (fndecl) != error_mark_node);
> +
> + releasing_vec args = build_arg_list (fndecl);
> + tree call = build_call_a (DECL_PRE_FN (fndecl), args->length (),
> + args->address ());
> + CALL_FROM_THUNK_P (call) = true;
> + finish_expr_stmt (call);
> +}
> +
> +/* Build and add a call to the post-condition checking function, when that
> + is in use. */
> +
> +static void
> +add_post_condition_fn_call (tree fndecl)
> +{
> + gcc_checking_assert (DECL_POST_FN (fndecl)
> + && DECL_POST_FN (fndecl) != error_mark_node);
> +
> + releasing_vec args = build_arg_list (fndecl);
> + if (get_postcondition_result_parameter (fndecl))
> + vec_safe_push (args, DECL_RESULT (fndecl));
> + tree call = build_call_a (DECL_POST_FN (fndecl), args->length (),
> + args->address ());
> + CALL_FROM_THUNK_P (call) = true;
> + finish_expr_stmt (call);
> +}
> +
> +/* Add a call or a direct evaluation of the pre checks. */
> +
> +static void
> +apply_preconditions (tree fndecl)
> +{
> + if (outline_contracts_p (fndecl))
> + add_pre_condition_fn_call (fndecl);
> + else
> + emit_preconditions (DECL_CONTRACTS (fndecl));
> +}
> +
> +/* Add a call or a direct evaluation of the post checks. */
> +
> +static void
> +apply_postconditions (tree fndecl)
> +{
> + if (outline_contracts_p (fndecl))
> + add_post_condition_fn_call (fndecl);
> + else
> + emit_postconditions (DECL_CONTRACTS (fndecl));
> +}
> +
> +/* Add contract handling to the function in FNDECL.
> +
> + When we have only pre-conditions, this simply prepends a call (or a direct
> + evaluation, for cdtors) to the existing function body.
> +
> + When we have post conditions we build a try-finally block.
> + If the function might throw then the handler in the try-finally is an
> + EH_ELSE expression, where the post condition check is applied to the
> + non-exceptional path, and a rethrow is applied to the EH path. If the
> + function has a non-throwing eh spec, then the handler is simply the post
> + contract checker (either a call or, for cdtors, a direct evaluation). */
> +
> +void
> +maybe_apply_function_contracts (tree fndecl)
> +{
> + if (!handle_contracts_p (fndecl))
> + /* We did nothing and the original function body statement list will be
> + popped by our caller. */
> + return;
> +
> + bool do_pre = has_active_preconditions (fndecl);
> + bool do_post = has_active_postconditions (fndecl);
> + /* We should not have reached here with nothing to do... */
> + gcc_checking_assert (do_pre || do_post);
> +
> + /* This copies the approach used for function try blocks. */
> + tree fnbody = pop_stmt_list (DECL_SAVED_TREE (fndecl));
> + DECL_SAVED_TREE (fndecl) = push_stmt_list ();
> + tree compound_stmt = begin_compound_stmt (0);
> + current_binding_level->artificial = 1;
> +
> + /* FIXME : find some better location to use - perhaps the position of the
> + function opening brace, if that is available. */
> + location_t loc = UNKNOWN_LOCATION;
> +
> + /* For other cases, we call a function to process the check. */
> +
> + /* IF we hsve a pre - but not a post, then just emit that. */
> + if (!do_post)
> {
> - releasing_vec args = build_arg_list (decl1);
> - tree call = build_call_a (DECL_PRE_FN (decl1),
> - args->length (),
> - args->address ());
> - CALL_FROM_THUNK_P (call) = true;
> - finish_expr_stmt (call);
> + apply_preconditions (fndecl);
> + add_stmt (fnbody);
> + finish_compound_stmt (compound_stmt);
> + return;
> }
> +
> + tree try_fin = build_stmt (loc, TRY_FINALLY_EXPR, NULL_TREE, NULL_TREE);
> + add_stmt (try_fin);
> + TREE_OPERAND (try_fin, 0) = push_stmt_list ();
> + if (do_pre)
> + /* Add a precondition call, if we have one. */
> + apply_preconditions (fndecl);
> + add_stmt (fnbody);
> + TREE_OPERAND (try_fin, 0) = pop_stmt_list (TREE_OPERAND (try_fin, 0));
> + TREE_OPERAND (try_fin, 1) = push_stmt_list ();
> + if (!type_noexcept_p (TREE_TYPE (fndecl)))
> + {
> + tree eh_else = build_stmt (loc, EH_ELSE_EXPR, NULL_TREE, NULL_TREE);
> + /* We need to ignore this in constexpr considerations. */
> + CONTRACT_EH_ELSE_P (eh_else) = true;
> + add_stmt (eh_else);
> + TREE_OPERAND (eh_else, 0) = push_stmt_list ();
> + apply_postconditions (fndecl);
> + TREE_OPERAND (eh_else, 0) = pop_stmt_list (TREE_OPERAND (eh_else, 0));
> + TREE_OPERAND (eh_else, 1) = push_stmt_list ();
> + tree rethrow = build_throw (input_location, NULL_TREE,
> tf_warning_or_error);
> + suppress_warning (rethrow);
> + finish_expr_stmt (rethrow);
> + TREE_OPERAND (eh_else, 1) = pop_stmt_list (TREE_OPERAND (eh_else, 1));
> + }
> + else
> + apply_postconditions (fndecl);
> + TREE_OPERAND (try_fin, 1) = pop_stmt_list (TREE_OPERAND (try_fin, 1));
> + finish_compound_stmt (compound_stmt);
> + /* The DECL_SAVED_TREE stmt list will be popped by our caller. */
> }
>
> +
> /* Finish up the pre & post function definitions for a guarded FNDECL,
> and compile those functions all the way to assembler language output. */
>
> @@ -2074,34 +2131,6 @@ finish_function_contracts (tree fndecl)
> }
> }
>
> -/* Rewrite the expression of a returned expression so that it invokes the
> - postcondition function as needed. */
> -
> -tree
> -apply_postcondition_to_return (tree expr)
> -{
> - tree fn = current_function_decl;
> - tree post = DECL_POST_FN (fn);
> - if (!post)
> - return NULL_TREE;
> -
> - /* If FN returns in memory, POST has a void return type and we call it when
> - EXPR is DECL_RESULT (fn). If FN returns a scalar, POST has the same
> - return type and we call it when EXPR is the value being returned. */
> - if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post)))
> - != (expr == DECL_RESULT (fn)))
> - return NULL_TREE;
> -
> - releasing_vec args = build_arg_list (fn);
> - if (get_postcondition_result_parameter (fn))
> - vec_safe_push (args, expr);
> - tree call = build_call_a (post,
> - args->length (),
> - args->address ());
> - CALL_FROM_THUNK_P (call) = true;
> -
> - return call;
> -}
>
> /* A subroutine of duplicate_decls. Diagnose issues in the redeclaration of
> guarded functions. */
> diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h
> index 3e5c30db331..c786affd0e3 100644
> --- a/gcc/cp/contracts.h
> +++ b/gcc/cp/contracts.h
> @@ -295,8 +295,9 @@ extern void update_late_contract (tree, tree,
> tree);
> extern tree splice_out_contracts (tree);
> extern bool all_attributes_are_contracts_p (tree);
> extern void inherit_base_contracts (tree, tree);
> -extern tree apply_postcondition_to_return (tree);
> +//extern tree apply_postcondition_to_return (tree);
> extern void start_function_contracts (tree);
> +extern void maybe_apply_function_contracts (tree);
> extern void finish_function_contracts (tree);
> extern void set_contract_functions (tree, tree, tree);
> extern tree build_contract_check (tree);
> diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc
> index 97bc211ff67..f350fc33e9b 100644
> --- a/gcc/cp/coroutines.cc
> +++ b/gcc/cp/coroutines.cc
> @@ -4014,6 +4014,8 @@ coro_build_actor_or_destroy_function (tree orig, tree
> fn_type,
> DECL_USER_ALIGN (fn) = DECL_USER_ALIGN (orig);
> /* Apply attributes from the original fn. */
> DECL_ATTRIBUTES (fn) = copy_list (DECL_ATTRIBUTES (orig));
> + /* but we do not want ones for contracts. */
> + remove_contract_attributes (fn);
>
> /* A void return. */
> tree resdecl = build_decl (loc, RESULT_DECL, 0, void_type_node);
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 416c60b7311..505ee5d4dc9 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -451,6 +451,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
> ATOMIC_CONSTR_MAP_INSTANTIATED_P (in ATOMIC_CONSTR)
> contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT)
> RETURN_EXPR_LOCAL_ADDR_P (in RETURN_EXPR)
> + CONTRACT_EH_ELSE_P (in EH_ELSE_EXPR)
> 1: IDENTIFIER_KIND_BIT_1 (in IDENTIFIER_NODE)
> TI_PENDING_TEMPLATE_FLAG.
> TEMPLATE_PARMS_FOR_INLINE.
> @@ -723,6 +724,8 @@ typedef struct ptrmem_cst * ptrmem_cst_t;
>
> #define CLEANUP_P(NODE) TREE_LANG_FLAG_0 (TRY_BLOCK_CHECK
> (NODE))
>
> +#define CONTRACT_EH_ELSE_P(NODE) TREE_LANG_FLAG_0 (EH_ELSE_EXPR_CHECK (NODE))
> +
> #define BIND_EXPR_TRY_BLOCK(NODE) \
> TREE_LANG_FLAG_0 (BIND_EXPR_CHECK (NODE))
>
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 03deb1493a4..f3e733a29ba 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -18599,16 +18599,19 @@ finish_function (bool inline_p)
> tree fndecl = current_function_decl;
> tree fntype, ctype = NULL_TREE;
> tree resumer = NULL_TREE, destroyer = NULL_TREE;
> - bool coro_p = flag_coroutines
> - && !processing_template_decl
> - && DECL_COROUTINE_P (fndecl);
> - bool coro_emit_helpers = false;
>
> /* When we get some parse errors, we can end up without a
> current_function_decl, so cope. */
> - if (fndecl == NULL_TREE)
> + if (fndecl == NULL_TREE || fndecl == error_mark_node)
> return error_mark_node;
>
> + bool coro_p = flag_coroutines
> + && !processing_template_decl
> + && DECL_COROUTINE_P (fndecl);
> + bool coro_emit_helpers = false;
> + bool do_contracts = DECL_HAS_CONTRACTS_P (fndecl)
> + && !processing_template_decl;
> +
> if (!DECL_OMP_DECLARE_REDUCTION_P (fndecl))
> finish_lambda_scope ();
>
> @@ -18644,6 +18647,10 @@ finish_function (bool inline_p)
> finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS
> (TREE_TYPE (fndecl)),
> current_eh_spec_block);
> +
> + /* If outlining succeeded, then add contracts handling if needed. */
> + if (coro_emit_helpers && do_contracts)
> + maybe_apply_function_contracts (fndecl);
> }
> else
> /* For a cloned function, we've already got all the code we need;
> @@ -18659,6 +18666,10 @@ finish_function (bool inline_p)
> finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS
> (TREE_TYPE (current_function_decl)),
> current_eh_spec_block);
> +
> + if (do_contracts)
> + maybe_apply_function_contracts (current_function_decl);
> +
> }
>
> /* If we're saving up tree structure, tie off the function now. */
> @@ -18911,7 +18922,7 @@ finish_function (bool inline_p)
> --function_depth;
>
> /* Clean up. */
> - current_function_decl = NULL_TREE;
> + gcc_checking_assert (current_function_decl == NULL_TREE);
>
> invoke_plugin_callbacks (PLUGIN_FINISH_PARSE_FUNCTION, fndecl);
>
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 5970ac3d398..4434ba6e2f4 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -11416,13 +11416,7 @@ check_return_expr (tree retval, bool *no_warning,
> bool *dangling)
>
> /* Actually copy the value returned into the appropriate location. */
> if (retval && retval != result)
> - {
> - /* If there's a postcondition for a scalar return value, wrap
> - retval in a call to the postcondition function. */
> - if (tree post = apply_postcondition_to_return (retval))
> - retval = post;
> - retval = cp_build_init_expr (result, retval);
> - }
> + retval = cp_build_init_expr (result, retval);
>
> if (current_function_return_value == bare_retval)
> INIT_EXPR_NRV_P (retval) = true;
> @@ -11430,11 +11424,6 @@ check_return_expr (tree retval, bool *no_warning,
> bool *dangling)
> if (tree set = maybe_set_retval_sentinel ())
> retval = build2 (COMPOUND_EXPR, void_type_node, retval, set);
>
> - /* If there's a postcondition for an aggregate return value, call the
> - postcondition function after the return object is initialized. */
> - if (tree post = apply_postcondition_to_return (result))
> - retval = build2 (COMPOUND_EXPR, void_type_node, retval, post);
> -
> return retval;
> }
>
> diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
> index 622c51d5c3f..43bd4c56d7f 100644
> --- a/gcc/gimplify.cc
> +++ b/gcc/gimplify.cc
> @@ -227,6 +227,7 @@ struct gimplify_ctx
> unsigned keep_stack : 1;
> unsigned save_stack : 1;
> unsigned in_switch_expr : 1;
> + unsigned in_handler_expr : 1;
> };
>
> enum gimplify_defaultmap_kind
> @@ -18401,10 +18402,12 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p,
> gimple_seq *post_p,
> input_location = UNKNOWN_LOCATION;
> eval = cleanup = NULL;
> gimplify_and_add (TREE_OPERAND (*expr_p, 0), &eval);
> + bool save_in_handler_expr = gimplify_ctxp->in_handler_expr;
> if (TREE_CODE (*expr_p) == TRY_FINALLY_EXPR
> && TREE_CODE (TREE_OPERAND (*expr_p, 1)) == EH_ELSE_EXPR)
> {
> gimple_seq n = NULL, e = NULL;
> + gimplify_ctxp->in_handler_expr = true;
> gimplify_and_add (TREE_OPERAND (TREE_OPERAND (*expr_p, 1),
> 0), &n);
> gimplify_and_add (TREE_OPERAND (TREE_OPERAND (*expr_p, 1),
> @@ -18416,7 +18419,11 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p,
> gimple_seq *post_p,
> }
> }
> else
> - gimplify_and_add (TREE_OPERAND (*expr_p, 1), &cleanup);
> + {
> + gimplify_ctxp->in_handler_expr = true;
> + gimplify_and_add (TREE_OPERAND (*expr_p, 1), &cleanup);
> + }
> + gimplify_ctxp->in_handler_expr = save_in_handler_expr;
> /* Don't create bogus GIMPLE_TRY with empty cleanup. */
> if (gimple_seq_empty_p (cleanup))
> {
> @@ -18516,6 +18523,10 @@ gimplify_expr (tree *expr_p, gimple_seq *pre_p,
> gimple_seq *post_p,
> /* When within an OMP context, notice uses of variables. */
> if (gimplify_omp_ctxp)
> omp_notice_variable (gimplify_omp_ctxp, *expr_p, true);
> + /* Handlers can refer to the function result; if that has been
> + moved, we need to track it. */
> + if (gimplify_ctxp->in_handler_expr && gimplify_ctxp->return_temp)
> + *expr_p = gimplify_ctxp->return_temp;
> ret = GS_ALL_DONE;
> break;
>
> diff --git a/gcc/testsuite/g++.dg/contracts/pr115434.C
> b/gcc/testsuite/g++.dg/contracts/pr115434.C
> new file mode 100644
> index 00000000000..e9c847f8969
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/contracts/pr115434.C
> @@ -0,0 +1,16 @@
> +// We were failing to apply post conditions to void functions.
> +
> +// { dg-do run }
> +// { dg-options "-std=c++20 -fcontracts -fcontract-continuation-mode=on" }
> +
> +
> +void foo (const int b)
> +[[ post: b == 9 ]] // contract not checked
> +{}
> +
> +int main()
> +{
> + foo(3);
> +}
> +
> +// { dg-output "contract violation in function foo at .*.C:8: b ==
> 9.*(\n|\r\n|\r)" }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr110871.C
> b/gcc/testsuite/g++.dg/coroutines/pr110871.C
> new file mode 100644
> index 00000000000..8a667c856ae
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr110871.C
> @@ -0,0 +1,62 @@
> +// { dg-additional-options "-fcontracts -fcontract-continuation-mode=on" }
> +// { dg-do run }
> +#include <iostream>
> +#include <coroutine>
> +
> +// In order to test the contract violation diagnostic, we have to set
> +// -fcontract-continuation-mode=on; this means that the code will emit
> +// the message below - but before that the contract should have been checked.
> +void process(int from, int to)
> +{
> + if (from > to)
> + std::cout << "would have been a disaster!" << std::endl;
> +}
> +
> +template <typename T>
> +struct generator
> +{
> + struct promise_type
> + {
> + template <typename... Args>
> + promise_type(Args&&... args) {
> + std::cout << "promise init" << std::endl;
> + process(args...);
> + }
> +
> + std::suspend_always yield_value(T) { return {}; }
> +
> + std::suspend_always initial_suspend() const noexcept { return {}; }
> + std::suspend_never final_suspend() const noexcept { return {}; }
> + void unhandled_exception() noexcept {}
> +
> + generator<T> get_return_object() noexcept { return {}; }
> + };
> +};
> +
> +namespace std {
> +template <typename T, typename... Args>
> +struct coroutine_traits<generator<T>, Args...>
> +{
> + using promise_type = typename generator<T>::promise_type;
> +};
> +
> +};
> +
> +generator<int> seq(int from, int to) [[pre: from <= to]]
> +
> +{
> + std::cout << "coro initial" << std::endl;
> + for (int i = from; i <= to; ++i) {
> + co_yield i;
> + std::cout << "coro resumed" << std::endl;
> + }
> +}
> +
> +int main() {
> + std::cout << "main initial" << std::endl;
> + generator<int> s = seq(10, 5);
> + (void)s;
> + std::cout << "main continues" << std::endl;
> +}
> +
> +// { dg-output "contract violation in function seq at .*.C:45: from \<=
> to.*(\n|\r\n|\r)" }
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr110872.C
> b/gcc/testsuite/g++.dg/coroutines/pr110872.C
> new file mode 100644
> index 00000000000..a809986f296
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr110872.C
> @@ -0,0 +1,49 @@
> +// { dg-additional-options "-fcontracts -fcontract-continuation-mode=on" }
> +// { dg-do run }
> +
> +#include <iostream>
> +#include <coroutine>
> +
> +
> +template <typename T>
> +struct generator
> +{
> + struct promise_type
> + {
> + std::suspend_always yield_value(T) { return {}; }
> +
> + std::suspend_always initial_suspend() const noexcept { return {}; }
> + std::suspend_never final_suspend() const noexcept { return {}; }
> + void unhandled_exception() noexcept {}
> +
> + generator<T> get_return_object() noexcept { return {}; }
> + };
> +
> + bool is_valid() { return false; }
> +};
> +
> +namespace std {
> +template <typename T, typename... Args>
> +struct coroutine_traits<generator<T>, Args...>
> +{
> + using promise_type = typename generator<T>::promise_type;
> +};
> +
> +};
> +
> +generator<int> val(int v)
> +[[post g: g.is_valid()]]
> +{
> + std::cout << "coro initial" << std::endl;
> + co_yield v;
> + std::cout << "coro resumed" << std::endl;
> +}
> +
> +int main() {
> + std::cout << "main initial" << std::endl;
> + generator<int> s = val(1);
> + (void)s;
> + std::cout << "main continues" << std::endl;
> +}
> +
> +// { dg-output "contract violation in function val at .*.C:35:
> g.is_valid().*(\n|\r\n|\r)" }
> --
> 2.39.2 (Apple Git-143)
>