On Sat, Jan 11, 2020 at 6:13 AM Jason Merrill <[email protected]> wrote:
>
> This is a pretty rare situation since the C++11 change to make all
> destructors default to noexcept, but it is still possible to define throwing
> destructors, and if a destructor for a local variable throws during the
> return, we've already constructed the return value, so now we need to
> destroy it. I handled this somewhat like the new-expression cleanup; as in
> that case, this cleanup can't properly nest with the cleanups for local
> variables, so I introduce a cleanup region around the whole function and a
> flag variable to indicate whether the return value actually needs to be
> destroyed.
>
> Setting the flag requires giving a COMPOUND_EXPR as the operand of a
> RETURN_EXPR. Simply allowing that in gimplify_return_expr makes the most
> sense to me, is it OK for trunk?
It works for me.
Richard.
> This doesn't currently work with deduced return type because we don't know
> the type when we're deciding whether to introduce the cleanup region.
>
> Tested x86_64-pc-linux-gnu.
>
> gcc/
> * gimplify.c (gimplify_return_expr): Handle COMPOUND_EXPR.
> gcc/cp/
> * cp-tree.h (current_retval_sentinel): New macro.
> * decl.c (start_preparsed_function): Set up cleanup for retval.
> * typeck.c (check_return_expr): Set current_retval_sentinel.
> ---
> gcc/cp/cp-tree.h | 7 ++++++
> gcc/cp/decl.c | 14 +++++++++++
> gcc/cp/typeck.c | 9 +++++++
> gcc/gimplify.c | 8 +++++++
> gcc/testsuite/g++.dg/eh/return1.C | 40 +++++++++++++++++++++++++++++++
> 5 files changed, 78 insertions(+)
> create mode 100644 gcc/testsuite/g++.dg/eh/return1.C
>
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 98572bdbad1..c0f780df685 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1952,6 +1952,13 @@ struct GTY(()) language_function {
>
> #define current_vtt_parm cp_function_chain->x_vtt_parm
>
> +/* A boolean flag to control whether we need to clean up the return value if
> a
> + local destructor throws. Only used in functions that return by value a
> + class with a destructor. Which 'tors don't, so we can use the same
> + field as current_vtt_parm. */
> +
> +#define current_retval_sentinel current_vtt_parm
> +
> /* Set to 0 at beginning of a function definition, set to 1 if
> a return statement that specifies a return value is seen. */
>
> diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
> index 094e32edf58..52da0deef40 100644
> --- a/gcc/cp/decl.c
> +++ b/gcc/cp/decl.c
> @@ -16418,6 +16418,20 @@ start_preparsed_function (tree decl1, tree attrs,
> int flags)
> if (!DECL_OMP_DECLARE_REDUCTION_P (decl1))
> start_lambda_scope (decl1);
>
> + /* If cleaning up locals on return throws an exception, we need to destroy
> + the return value that we just constructed. */
> + if (!processing_template_decl
> + && TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (TREE_TYPE (decl1))))
> + {
> + tree retval = DECL_RESULT (decl1);
> + tree dtor = build_cleanup (retval);
> + current_retval_sentinel = get_temp_regvar (boolean_type_node,
> + boolean_false_node);
> + dtor = build3 (COND_EXPR, void_type_node, current_retval_sentinel,
> + dtor, void_node);
> + push_cleanup (retval, dtor, /*eh-only*/true);
> + }
> +
> return true;
> }
>
> diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
> index 7b653cebca0..8288b662ec0 100644
> --- a/gcc/cp/typeck.c
> +++ b/gcc/cp/typeck.c
> @@ -10088,6 +10088,15 @@ check_return_expr (tree retval, bool *no_warning)
> if (retval && retval != result)
> retval = build2 (INIT_EXPR, TREE_TYPE (result), result, retval);
>
> + if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (valtype)
> + /* FIXME doesn't work with deduced return type. */
> + && current_retval_sentinel)
> + {
> + tree set = build2 (MODIFY_EXPR, boolean_type_node,
> + current_retval_sentinel, boolean_true_node);
> + retval = build2 (COMPOUND_EXPR, void_type_node, retval, set);
> + }
> +
> return retval;
> }
>
> diff --git a/gcc/gimplify.c b/gcc/gimplify.c
> index fe7236de4c3..05d7922116b 100644
> --- a/gcc/gimplify.c
> +++ b/gcc/gimplify.c
> @@ -1599,6 +1599,14 @@ gimplify_return_expr (tree stmt, gimple_seq *pre_p)
>
> if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))))
> result_decl = NULL_TREE;
> + else if (TREE_CODE (ret_expr) == COMPOUND_EXPR)
> + {
> + /* Used in C++ for handling EH cleanup of the return value if a local
> + cleanup throws. Assume the front-end knows what it's doing. */
> + result_decl = DECL_RESULT (current_function_decl);
> + /* But crash if we end up trying to modify ret_expr below. */
> + ret_expr = NULL_TREE;
> + }
> else
> {
> result_decl = TREE_OPERAND (ret_expr, 0);
> diff --git a/gcc/testsuite/g++.dg/eh/return1.C
> b/gcc/testsuite/g++.dg/eh/return1.C
> new file mode 100644
> index 00000000000..7469d3128dc
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/eh/return1.C
> @@ -0,0 +1,40 @@
> +// PR c++/33799
> +// { dg-do run }
> +
> +extern "C" void abort();
> +
> +int c, d;
> +
> +#if __cplusplus >= 201103L
> +#define THROWS noexcept(false)
> +#else
> +#define THROWS
> +#endif
> +
> +struct X
> +{
> + X(bool throws) : throws_(throws) { ++c; }
> + X(const X& x) : throws_(x.throws_) { ++c; }
> + ~X() THROWS
> + {
> + ++d;
> + if (throws_) { throw 1; }
> + }
> +private:
> + bool throws_;
> +};
> +
> +X f()
> +{
> + X x(true);
> + return X(false);
> +}
> +
> +int main()
> +{
> + try { f(); }
> + catch (...) {}
> +
> + if (c != d)
> + throw;
> +}
>
> base-commit: d3cf980217ce13b1eda01d4c42a7e3afd2bf3217
> --
> 2.18.1
>