Commit a24549d4 by Jason Merrill Committed by Jason Merrill

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
parent 7a3c4b3e
2007-10-03 Jason Merrill <jason@redhat.com>
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 <dougkwan@google.com>
Richard Guenther <rguenther@suse.de>
2007-10-03 Jason Merrill <jason@redhat.com>
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 <jason@redhat.com>
PR c++/33094
......
......@@ -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);
}
......
......@@ -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);
......
// 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 ();
}
......@@ -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 */
};
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment