From a24549d472e2235a6042b96e08a1278d4856fabd Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Wed, 3 Oct 2007 06:43:42 -0400 Subject: [PATCH] re PR c++/15764 (no cleanup if temporary's dtor terminates with an exception) PR c++/15764 * cp/decl.c (wrap_cleanups_r): New fn. (wrap_temporary_cleanups): New fn. (initialize_local_var): Call it. * tree-eh.c (same_handler_p): New fn. (optimize_double_finally): New fn. (refactor_eh_r): New fn. (refactor_eh): New fn. (pass_refactor_eh): New pass. * tree-pass.h: Declare it. * passes.c (init_optimization_passes): Add it. From-SVN: r128979 --- gcc/ChangeLog | 11 ++ gcc/cp/ChangeLog | 7 ++ gcc/cp/decl.c | 109 +++++++++++++------ gcc/passes.c | 1 + gcc/testsuite/g++.dg/eh/init-temp1.C | 44 ++++++++ gcc/tree-eh.c | 156 +++++++++++++++++++++++++++ gcc/tree-pass.h | 1 + 7 files changed, 296 insertions(+), 33 deletions(-) create mode 100644 gcc/testsuite/g++.dg/eh/init-temp1.C diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 02f3040d37a..73010ea671c 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,14 @@ +2007-10-03 Jason Merrill + + PR c++/15764 + * tree-eh.c (same_handler_p): New fn. + (optimize_double_finally): New fn. + (refactor_eh_r): New fn. + (refactor_eh): New fn. + (pass_refactor_eh): New pass. + * tree-pass.h: Declare it. + * passes.c (init_optimization_passes): Add it. + 2007-10-03 Doug Kwan Richard Guenther diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 5609f1a1626..02d549f87e9 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,10 @@ +2007-10-03 Jason Merrill + + PR c++/15764 + * decl.c (wrap_cleanups_r): New fn. + (wrap_temporary_cleanups): New fn. + (initialize_local_var): Call it. + 2007-09-29 Jason Merrill PR c++/33094 diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index 469e6b836ee..d653fc490d1 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -5136,6 +5136,41 @@ make_rtl_for_nonlocal_decl (tree decl, tree init, const char* asmspec) rest_of_decl_compilation (decl, toplev, at_eof); } +/* walk_tree helper for wrap_temporary_cleanups, below. */ + +static tree +wrap_cleanups_r (tree *stmt_p, int *walk_subtrees, void *data) +{ + if (TYPE_P (*stmt_p)) + { + *walk_subtrees = 0; + return NULL_TREE; + } + + if (TREE_CODE (*stmt_p) == TARGET_EXPR) + { + tree guard = (tree)data; + tree tcleanup = TARGET_EXPR_CLEANUP (*stmt_p); + + tcleanup = build2 (TRY_CATCH_EXPR, void_type_node, tcleanup, guard); + + TARGET_EXPR_CLEANUP (*stmt_p) = tcleanup; + } + + return NULL_TREE; +} + +/* We're initializing a local variable which has a cleanup GUARD. If there + are any temporaries used in the initializer INIT of this variable, we + need to wrap their cleanups with TRY_CATCH_EXPR (, GUARD) so that the + variable will be cleaned up properly if one of them throws. */ + +static void +wrap_temporary_cleanups (tree init, tree guard) +{ + cp_walk_tree_without_duplicates (&init, wrap_cleanups_r, (void *)guard); +} + /* Generate code to initialize DECL (a local variable). */ static void @@ -5143,6 +5178,7 @@ initialize_local_var (tree decl, tree init) { tree type = TREE_TYPE (decl); tree cleanup; + int already_used; gcc_assert (TREE_CODE (decl) == VAR_DECL || TREE_CODE (decl) == RESULT_DECL); @@ -5153,46 +5189,53 @@ initialize_local_var (tree decl, tree init) /* If we used it already as memory, it must stay in memory. */ DECL_INITIAL (decl) = NULL_TREE; TREE_ADDRESSABLE (decl) = TREE_USED (decl); + return; } - if (DECL_SIZE (decl) && type != error_mark_node) - { - int already_used; + if (type == error_mark_node) + return; - /* Compute and store the initial value. */ - already_used = TREE_USED (decl) || TREE_USED (type); + /* Compute and store the initial value. */ + already_used = TREE_USED (decl) || TREE_USED (type); - /* Perform the initialization. */ - if (init) - { - int saved_stmts_are_full_exprs_p; + /* Generate a cleanup, if necessary. */ + cleanup = cxx_maybe_build_cleanup (decl); - gcc_assert (building_stmt_tree ()); - saved_stmts_are_full_exprs_p = stmts_are_full_exprs_p (); - current_stmt_tree ()->stmts_are_full_exprs_p = 1; - finish_expr_stmt (init); - current_stmt_tree ()->stmts_are_full_exprs_p = - saved_stmts_are_full_exprs_p; - } + /* Perform the initialization. */ + if (init) + { + int saved_stmts_are_full_exprs_p; - /* Set this to 0 so we can tell whether an aggregate which was - initialized was ever used. Don't do this if it has a - destructor, so we don't complain about the 'resource - allocation is initialization' idiom. Now set - attribute((unused)) on types so decls of that type will be - marked used. (see TREE_USED, above.) */ - if (TYPE_NEEDS_CONSTRUCTING (type) - && ! already_used - && TYPE_HAS_TRIVIAL_DESTRUCTOR (type) - && DECL_NAME (decl)) - TREE_USED (decl) = 0; - else if (already_used) - TREE_USED (decl) = 1; - } + /* If we're only initializing a single object, guard the destructors + of any temporaries used in its initializer with its destructor. + This isn't right for arrays because each element initialization is + a full-expression. */ + if (cleanup && TREE_CODE (type) != ARRAY_TYPE) + wrap_temporary_cleanups (init, cleanup); - /* Generate a cleanup, if necessary. */ - cleanup = cxx_maybe_build_cleanup (decl); - if (DECL_SIZE (decl) && cleanup) + gcc_assert (building_stmt_tree ()); + saved_stmts_are_full_exprs_p = stmts_are_full_exprs_p (); + current_stmt_tree ()->stmts_are_full_exprs_p = 1; + finish_expr_stmt (init); + current_stmt_tree ()->stmts_are_full_exprs_p = + saved_stmts_are_full_exprs_p; + } + + /* Set this to 0 so we can tell whether an aggregate which was + initialized was ever used. Don't do this if it has a + destructor, so we don't complain about the 'resource + allocation is initialization' idiom. Now set + attribute((unused)) on types so decls of that type will be + marked used. (see TREE_USED, above.) */ + if (TYPE_NEEDS_CONSTRUCTING (type) + && ! already_used + && TYPE_HAS_TRIVIAL_DESTRUCTOR (type) + && DECL_NAME (decl)) + TREE_USED (decl) = 0; + else if (already_used) + TREE_USED (decl) = 1; + + if (cleanup) finish_decl_cleanup (decl, cleanup); } diff --git a/gcc/passes.c b/gcc/passes.c index 74f48a83401..25644bc8b5a 100644 --- a/gcc/passes.c +++ b/gcc/passes.c @@ -480,6 +480,7 @@ init_optimization_passes (void) NEXT_PASS (pass_mudflap_1); NEXT_PASS (pass_lower_omp); NEXT_PASS (pass_lower_cf); + NEXT_PASS (pass_refactor_eh); NEXT_PASS (pass_lower_eh); NEXT_PASS (pass_build_cfg); NEXT_PASS (pass_lower_complex_O0); diff --git a/gcc/testsuite/g++.dg/eh/init-temp1.C b/gcc/testsuite/g++.dg/eh/init-temp1.C new file mode 100644 index 00000000000..29eae6972d6 --- /dev/null +++ b/gcc/testsuite/g++.dg/eh/init-temp1.C @@ -0,0 +1,44 @@ +// PR c++/15764 + +extern "C" void abort (); + +int counter = 0; +int thrown; +struct a { + ~a () { if (thrown++ == 0) throw 42; } +}; + +int f (a const&) { return 1; } +int f (a const&, a const&) { return 1; } + +struct b { + b (...) { ++counter; } + ~b () { --counter; } +}; + +bool p; +void g() +{ + if (p) throw 42; +} + +int main () { + thrown = 0; + try { + b tmp(f (a(), a())); + + g(); + } + catch (...) {} + + thrown = 0; + try { + b tmp(f (a())); + + g(); + } + catch (...) {} + + if (counter != 0) + abort (); +} diff --git a/gcc/tree-eh.c b/gcc/tree-eh.c index f8aed147ccf..3ea582f7b84 100644 --- a/gcc/tree-eh.c +++ b/gcc/tree-eh.c @@ -2093,3 +2093,159 @@ maybe_clean_or_replace_eh_stmt (tree old_stmt, tree new_stmt) return false; } + +/* Returns TRUE if oneh and twoh are exception handlers (op 1 of + TRY_CATCH_EXPR or TRY_FINALLY_EXPR that are similar enough to be + considered the same. Currently this only handles handlers consisting of + a single call, as that's the important case for C++: a destructor call + for a particular object showing up in multiple handlers. */ + +static bool +same_handler_p (tree oneh, tree twoh) +{ + tree_stmt_iterator i; + tree ones, twos; + int ai; + + i = tsi_start (oneh); + if (!tsi_one_before_end_p (i)) + return false; + ones = tsi_stmt (i); + + i = tsi_start (twoh); + if (!tsi_one_before_end_p (i)) + return false; + twos = tsi_stmt (i); + + if (TREE_CODE (ones) != CALL_EXPR + || TREE_CODE (twos) != CALL_EXPR + || !operand_equal_p (CALL_EXPR_FN (ones), CALL_EXPR_FN (twos), 0) + || call_expr_nargs (ones) != call_expr_nargs (twos)) + return false; + + for (ai = 0; ai < call_expr_nargs (ones); ++ai) + if (!operand_equal_p (CALL_EXPR_ARG (ones, ai), + CALL_EXPR_ARG (twos, ai), 0)) + return false; + + return true; +} + +/* Optimize + try { A() } finally { try { ~B() } catch { ~A() } } + try { ... } finally { ~A() } + into + try { A() } catch { ~B() } + try { ~B() ... } finally { ~A() } + + This occurs frequently in C++, where A is a local variable and B is a + temporary used in the initializer for A. */ + +static void +optimize_double_finally (tree one, tree two) +{ + tree oneh; + tree_stmt_iterator i; + + i = tsi_start (TREE_OPERAND (one, 1)); + if (!tsi_one_before_end_p (i)) + return; + + oneh = tsi_stmt (i); + if (TREE_CODE (oneh) != TRY_CATCH_EXPR) + return; + + if (same_handler_p (TREE_OPERAND (oneh, 1), TREE_OPERAND (two, 1))) + { + tree twoh; + + tree b = TREE_OPERAND (oneh, 0); + TREE_OPERAND (one, 1) = b; + TREE_SET_CODE (one, TRY_CATCH_EXPR); + + b = tsi_stmt (tsi_start (b)); + twoh = TREE_OPERAND (two, 0); + /* same_handler_p only handles single-statement handlers, + so there must only be one statement. */ + i = tsi_start (twoh); + tsi_link_before (&i, unshare_expr (b), TSI_SAME_STMT); + } +} + +/* Perform EH refactoring optimizations that are simpler to do when code + flow has been lowered but EH structurs haven't. */ + +static void +refactor_eh_r (tree t) +{ + tailrecurse: + switch (TREE_CODE (t)) + { + case TRY_FINALLY_EXPR: + case TRY_CATCH_EXPR: + refactor_eh_r (TREE_OPERAND (t, 0)); + t = TREE_OPERAND (t, 1); + goto tailrecurse; + + case CATCH_EXPR: + t = CATCH_BODY (t); + goto tailrecurse; + + case EH_FILTER_EXPR: + t = EH_FILTER_FAILURE (t); + goto tailrecurse; + + case STATEMENT_LIST: + { + tree_stmt_iterator i; + tree one = NULL_TREE, two = NULL_TREE; + /* Try to refactor double try/finally. */ + for (i = tsi_start (t); !tsi_end_p (i); tsi_next (&i)) + { + one = two; + two = tsi_stmt (i); + if (one && two + && TREE_CODE (one) == TRY_FINALLY_EXPR + && TREE_CODE (two) == TRY_FINALLY_EXPR) + optimize_double_finally (one, two); + if (one) + refactor_eh_r (one); + } + if (two) + { + t = two; + goto tailrecurse; + } + } + break; + + default: + /* A type, a decl, or some kind of statement that we're not + interested in. Don't walk them. */ + break; + } +} + +static unsigned +refactor_eh (void) +{ + refactor_eh_r (DECL_SAVED_TREE (current_function_decl)); + return 0; +} + +struct tree_opt_pass pass_refactor_eh = +{ + "ehopt", /* name */ + NULL, /* gate */ + refactor_eh, /* execute */ + NULL, /* sub */ + NULL, /* next */ + 0, /* static_pass_number */ + TV_TREE_EH, /* tv_id */ + PROP_gimple_lcf, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_dump_func, /* todo_flags_finish */ + 0 /* letter */ +}; diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index b19d3d05931..1f748e82635 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -246,6 +246,7 @@ extern struct tree_opt_pass pass_mudflap_1; extern struct tree_opt_pass pass_mudflap_2; extern struct tree_opt_pass pass_remove_useless_stmts; extern struct tree_opt_pass pass_lower_cf; +extern struct tree_opt_pass pass_refactor_eh; extern struct tree_opt_pass pass_lower_eh; extern struct tree_opt_pass pass_build_cfg; extern struct tree_opt_pass pass_tree_profile; -- 2.30.2