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? 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