From bcfc2227c556f2801a657ce3007374732baa8333 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Sun, 19 Jan 2020 09:14:54 -0500 Subject: [PATCH] PR c++/33799 - destroy return value, take 2. This patch differs from the reverted patch for 33799 in that it adds the CLEANUP_STMT for the return value at the end of the function, and only if we've seen a cleanup that might throw, so it should not affect most C++11 code. * cp-tree.h (current_retval_sentinel): New macro. (struct language_function): Add throwing_cleanup bitfield. * decl.c (cxx_maybe_build_cleanup): Set it. * except.c (maybe_set_retval_sentinel) (maybe_splice_retval_cleanup): New functions. * parser.c (cp_parser_compound_statement): Call maybe_splice_retval_cleanup. * typeck.c (check_return_expr): Call maybe_set_retval_sentinel. --- gcc/cp/ChangeLog | 10 +++++ gcc/cp/cp-tree.h | 11 +++++ gcc/cp/decl.c | 3 ++ gcc/cp/except.c | 72 +++++++++++++++++++++++++++++++ gcc/cp/parser.c | 4 ++ gcc/cp/typeck.c | 3 ++ gcc/testsuite/g++.dg/eh/return1.C | 20 ++++++++- 7 files changed, 122 insertions(+), 1 deletion(-) diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 98a4d0fa7dc..ef8048260a3 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,5 +1,15 @@ 2020-01-19 Jason Merrill + PR c++/33799 - destroy return value, take 2. + * cp-tree.h (current_retval_sentinel): New macro. + (struct language_function): Add throwing_cleanup bitfield. + * decl.c (cxx_maybe_build_cleanup): Set it. + * except.c (maybe_set_retval_sentinel) + (maybe_splice_retval_cleanup): New functions. + * parser.c (cp_parser_compound_statement): Call + maybe_splice_retval_cleanup. + * typeck.c (check_return_expr): Call maybe_set_retval_sentinel. + * parser.c (cp_parser_lambda_body): Use cp_parser_function_body. 2020-01-18 Jakub Jelinek diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 3d76096b041..890d5a27350 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -1904,6 +1904,7 @@ struct GTY(()) language_function { BOOL_BITFIELD can_throw : 1; BOOL_BITFIELD invalid_constexpr : 1; + BOOL_BITFIELD throwing_cleanup : 1; hash_table *x_named_labels; @@ -1954,6 +1955,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. */ @@ -6686,6 +6694,9 @@ extern tree begin_eh_spec_block (void); extern void finish_eh_spec_block (tree, tree); extern tree build_eh_type_type (tree); extern tree cp_protect_cleanup_actions (void); +extern void maybe_splice_retval_cleanup (tree); +extern tree maybe_set_retval_sentinel (void); + extern tree template_parms_to_args (tree); extern tree template_parms_level_to_args (tree); extern tree generic_targs_for (tree); diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index e58fecc9de7..28a79029d92 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -17402,6 +17402,9 @@ cxx_maybe_build_cleanup (tree decl, tsubst_flags_t complain) && !mark_used (decl, complain) && !(complain & tf_error)) return error_mark_node; + if (cleanup && cfun && !expr_noexcept_p (cleanup, tf_none)) + cp_function_chain->throwing_cleanup = true; + return cleanup; } diff --git a/gcc/cp/except.c b/gcc/cp/except.c index 55b4b6af442..0b40234e228 100644 --- a/gcc/cp/except.c +++ b/gcc/cp/except.c @@ -1325,4 +1325,76 @@ build_noexcept_spec (tree expr, tsubst_flags_t complain) } } +/* If the current function has a cleanup that might throw, and the return value + has a non-trivial destructor, return a MODIFY_EXPR to set + current_retval_sentinel so that we know that the return value needs to be + destroyed on throw. Otherwise, returns NULL_TREE. */ + +tree +maybe_set_retval_sentinel () +{ + if (processing_template_decl) + return NULL_TREE; + tree retval = DECL_RESULT (current_function_decl); + if (!TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (retval))) + return NULL_TREE; + if (!cp_function_chain->throwing_cleanup) + return NULL_TREE; + + if (!current_retval_sentinel) + { + /* Just create the temporary now, maybe_splice_retval_cleanup + will do the rest. */ + current_retval_sentinel = create_temporary_var (boolean_type_node); + DECL_INITIAL (current_retval_sentinel) = boolean_false_node; + pushdecl_outermost_localscope (current_retval_sentinel); + } + + return build2 (MODIFY_EXPR, boolean_type_node, + current_retval_sentinel, boolean_true_node); +} + +/* COMPOUND_STMT is the STATEMENT_LIST for the current function body. If + current_retval_sentinel was set in this function, wrap the body in a + CLEANUP_STMT to destroy the return value on throw. */ + +void +maybe_splice_retval_cleanup (tree compound_stmt) +{ + /* If need_retval_cleanup set current_retval_sentinel, wrap the function body + in a CLEANUP_STMT to handle destroying the return value. */ + if (!DECL_CONSTRUCTOR_P (current_function_decl) + && !DECL_DESTRUCTOR_P (current_function_decl) + && current_retval_sentinel) + { + location_t loc = DECL_SOURCE_LOCATION (current_function_decl); + + /* Add a DECL_EXPR for current_retval_sentinel. */ + tree_stmt_iterator iter = tsi_start (compound_stmt); + tree retval = DECL_RESULT (current_function_decl); + tree decl_expr = build_stmt (loc, DECL_EXPR, current_retval_sentinel); + tsi_link_before (&iter, decl_expr, TSI_SAME_STMT); + + /* Skip past other decls, they can't contain a return. */ + while (TREE_CODE (tsi_stmt (iter)) == DECL_EXPR) + tsi_next (&iter); + gcc_assert (!tsi_end_p (iter)); + + /* Wrap the rest of the STATEMENT_LIST in a CLEANUP_STMT. */ + tree stmts = NULL_TREE; + while (!tsi_end_p (iter)) + { + append_to_statement_list_force (tsi_stmt (iter), &stmts); + tsi_delink (&iter); + } + tree dtor = build_cleanup (retval); + tree cond = build3 (COND_EXPR, void_type_node, current_retval_sentinel, + dtor, void_node); + tree cleanup = build_stmt (loc, CLEANUP_STMT, + stmts, cond, retval); + CLEANUP_EH_ONLY (cleanup) = true; + append_to_statement_list_force (cleanup, &compound_stmt); + } +} + #include "gt-cp-except.h" diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 98c1beb400f..caafbefda8e 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -11716,6 +11716,10 @@ cp_parser_compound_statement (cp_parser *parser, tree in_statement_expr, cp_parser_label_declaration (parser); /* Parse an (optional) statement-seq. */ cp_parser_statement_seq_opt (parser, in_statement_expr); + + if (function_body) + maybe_splice_retval_cleanup (compound_stmt); + /* Finish the compound-statement. */ finish_compound_stmt (compound_stmt); /* Consume the `}'. */ diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c index 8955442349f..5964c34272e 100644 --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -10090,6 +10090,9 @@ check_return_expr (tree retval, bool *no_warning) if (retval && retval != result) retval = build2 (INIT_EXPR, TREE_TYPE (result), result, retval); + if (tree set = maybe_set_retval_sentinel ()) + retval = build2 (COMPOUND_EXPR, void_type_node, retval, set); + return retval; } diff --git a/gcc/testsuite/g++.dg/eh/return1.C b/gcc/testsuite/g++.dg/eh/return1.C index ca0b8046e1b..5ef2f1dee85 100644 --- a/gcc/testsuite/g++.dg/eh/return1.C +++ b/gcc/testsuite/g++.dg/eh/return1.C @@ -1,5 +1,5 @@ // PR c++/33799 -// { dg-do run { xfail *-*-* } } +// { dg-do run } extern "C" void abort(); @@ -30,11 +30,29 @@ X f() return X(false); } +X g() +{ + return X(true),X(false); +} + +void h() +{ +#if __cplusplus >= 201103L + []{ return X(true),X(false); }(); +#endif +} + int main() { try { f(); } catch (...) {} + try { g(); } + catch (...) {} + + try { h(); } + catch (...) {} + if (c != d) throw; } -- 2.30.2