public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-03 23:06 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-03 23:06 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:4bb9a73bb407d8a0a84ca27c0af0d16392a20dd7
commit 4bb9a73bb407d8a0a84ca27c0af0d16392a20dd7
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Sep 2 20:33:15 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 553 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-noret.c | 2 +-
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 55 +-
.../g++.dg/harden-cfr-throw-returning-O0.C | 10 +
.../torture/harden-cfr-noret-never-no-nothrow.C | 5 +-
.../g++.dg/torture/harden-cfr-noret-no-nothrow.C | 5 +-
.../g++.dg/torture/harden-cfr-throw-always.C | 2 +-
.../g++.dg/torture/harden-cfr-throw-nocleanup.C | 2 +-
.../g++.dg/torture/harden-cfr-throw-returning.C | 31 ++
gcc/testsuite/g++.dg/torture/harden-cfr-throw.C | 4 +-
gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c | 40 ++
15 files changed, 740 insertions(+), 163 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 455853301e3..983cb4db7c6 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1795,6 +1795,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index c47cee28eaf..32e5ba2ed2c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -626,6 +626,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16566,12 +16567,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16582,6 +16594,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..7b86e68ff5f 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,293 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && gimple_assign_single_p (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ basic_block bb = path.pop ();
+ gimple *stop = hardcfr_scan_block (bb, &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+ else
+ continue;
+
+ gcc_checking_assert (!path.is_empty ());
+ edge e = single_succ_edge (path.last ());
+ int i = EDGE_COUNT (bb->preds);
+ while (i--)
+ if (EDGE_PRED (bb, i) == e)
+ break;
+ gcc_checking_assert (i >= 0);
+ retptr = gimple_phi_arg_def_ptr (retphi, i);
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +550,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +634,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +659,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +746,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +985,48 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1090,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1127,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1236,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1286,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1327,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1357,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
+
+ auto_sbitmap combined_blocks (last_basic_block_for_fn (fun));
+ bitmap_copy (combined_blocks, chkcall_blocks);
+ int i;
+ edge *e;
+ FOR_EACH_VEC_ELT (chk_edges, i, e)
+ if (!bitmap_set_bit (combined_blocks, (*e)->src->index))
+ gcc_unreachable ();
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1390,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (combined_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
index a58afd7944c..fdd803109a4 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
@@ -7,7 +7,7 @@
#define ATTR_NOTHROW_OPT __attribute__ ((__nothrow__))
#endif
-extern void __attribute__ ((__noreturn__)) ATTR_NOTHROW_OPT g(void);
+extern void __attribute__ ((__noreturn__)) ATTR_NOTHROW_OPT g (void);
void f(int i) {
if (i)
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..6d11487bbba
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..d5467eafa9f 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,52 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
-/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+/* Check that we insert CFR checking so as to not disrupt tail calls.
+ Mandatory tail calls are not available in C, and optimizing calls as tail
+ calls only takes place after hardcfr, so we insert checking before calls
+ followed by copies and return stmts with the same return value, that might
+ (or might not) end up optimized to tail calls. */
-extern int g(int i);
+extern int g (int i);
-int f(int i) {
+int f1(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2 (int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+void f4(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f5(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f4, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 4 "hardcfr" } } */
+/* Check before tail-call in all but f5, but f4 is out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
new file mode 100644
index 00000000000..f0338ccc361
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -O0" } */
+
+/* -fhardcfr-check-returning-calls gets implicitly disabled because,
+ -at O0, -foptimize-sibling-calls has no effect. */
+
+#include "torture/harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C
index 33e1ae26f80..b7d247ff43c 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C
@@ -10,8 +10,9 @@
/* All 3 noreturn calls. */
/* { dg-final { scan-tree-dump-times "Associated cleanup" 3 "hardcfr" } } */
/* Out-of-line checks in f. */
-/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in noreturn block" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in block \[0-9]*'s edge to exit" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
/* Inline checks in h and h2. */
-/* { dg-final { scan-tree-dump-times "Inserting inline check in noreturn block" 2 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C
index b47d880ada2..62c58cfd406 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C
@@ -15,8 +15,9 @@ void __attribute__ ((__noreturn__)) h (void);
/* All 3 noreturn calls. */
/* { dg-final { scan-tree-dump-times "Associated cleanup" 3 "hardcfr" } } */
/* Out-of-line checks in f. */
-/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in noreturn block" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in block \[0-9]*'s edge to exit" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
/* Inline checks in h and h2. */
-/* { dg-final { scan-tree-dump-times "Inserting inline check in noreturn block" 2 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 52ef7bc601a..0286f6e6d3f 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -1,5 +1,5 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr -ffat-lto-objects" } */
/* Check that we insert cleanups for checking around the bodies of
maybe-throwing functions, and also checking before noreturn
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
index da7c9cf1033..885b0b236af 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
/* Check that we do not insert cleanups for checking around the bodies
of maybe-throwing functions. h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
new file mode 100644
index 00000000000..d150470c618
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+ maybe-throwing functions. These results depend on checking before
+ returning calls, which is only enabled when sibcall optimizations
+ are enabled, so change the optimization mode to -O1 for f and f2,
+ so that -foptimize-sibling-calls can take effect and enable
+ -fhardcfr-check-returning-calls, so that we get the same results.
+ There is a separate test for -O0. */
+
+#if ! __OPTIMIZE__
+void __attribute__ ((__optimize__ (1))) f(int i);
+void __attribute__ ((__optimize__ (1))) f2(int i);
+void __attribute__ ((__optimize__ (1))) h3(void);
+#endif
+
+#include "harden-cfr-throw.C"
+
+/* f gets out-of-line checks before the unwrapped tail call and in the
+ else edge. */
+/* f2 gets out-of-line checks before both unwrapped tail calls. */
+/* h gets out-of-line checks before the implicit return and in the
+ cleanup block. */
+/* h2 and h2b get out-of-line checks before the cleanup returning
+ call, and in the cleanup block. */
+/* h3 gets an inline check before the __cxa_end_catch returning call. */
+/* h4 gets an inline check in the cleanup block. */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 2dbc67c34d9..992fbdad381 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,5 +1,5 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
/* Check that we insert cleanups for checking around the bodies of
maybe-throwing functions. */
@@ -57,8 +57,8 @@ void h3(void) {
}
void h4(void) {
- /* Inline check before the __cxa_throw noreturn call. */
throw 1;
+ /* Inline check in the cleanup around the __cxa_throw noreturn call. */
}
/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c b/gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c
new file mode 100644
index 00000000000..634d98f1ffc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
+
+/* In C only, check some additional cases (comparing with
+ c-c++-common/torture/harden-cfr-tail.c) of falling off the end of non-void
+ function. C++ would issue an unreachable call in these cases. */
+
+extern int g (int i);
+
+int f1(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+extern void g2 (int i);
+
+int f2(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f3(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+/* Out-of-line checks in f3, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
+/* Check before tail-call in all functions, but f3 is out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-03 23:58 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-03 23:58 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:d3696cfc39a6f429ae9a2cc930f95ad35d66a272
commit d3696cfc39a6f429ae9a2cc930f95ad35d66a272
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Sep 2 20:33:15 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 553 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-noret.c | 2 +-
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 55 +-
.../g++.dg/harden-cfr-throw-returning-O0.C | 10 +
.../torture/harden-cfr-noret-always-no-nothrow.C | 4 +-
.../torture/harden-cfr-noret-never-no-nothrow.C | 5 +-
.../g++.dg/torture/harden-cfr-noret-no-nothrow.C | 5 +-
.../g++.dg/torture/harden-cfr-throw-always.C | 2 +-
.../g++.dg/torture/harden-cfr-throw-nocleanup.C | 2 +-
.../g++.dg/torture/harden-cfr-throw-returning.C | 31 ++
gcc/testsuite/g++.dg/torture/harden-cfr-throw.C | 4 +-
gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c | 40 ++
16 files changed, 742 insertions(+), 165 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 455853301e3..983cb4db7c6 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1795,6 +1795,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index c47cee28eaf..32e5ba2ed2c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -626,6 +626,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16566,12 +16567,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16582,6 +16594,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..7b86e68ff5f 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,293 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && gimple_assign_single_p (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ basic_block bb = path.pop ();
+ gimple *stop = hardcfr_scan_block (bb, &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+ else
+ continue;
+
+ gcc_checking_assert (!path.is_empty ());
+ edge e = single_succ_edge (path.last ());
+ int i = EDGE_COUNT (bb->preds);
+ while (i--)
+ if (EDGE_PRED (bb, i) == e)
+ break;
+ gcc_checking_assert (i >= 0);
+ retptr = gimple_phi_arg_def_ptr (retphi, i);
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +550,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +634,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +659,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +746,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +985,48 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1090,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1127,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1236,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1286,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1327,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1357,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
+
+ auto_sbitmap combined_blocks (last_basic_block_for_fn (fun));
+ bitmap_copy (combined_blocks, chkcall_blocks);
+ int i;
+ edge *e;
+ FOR_EACH_VEC_ELT (chk_edges, i, e)
+ if (!bitmap_set_bit (combined_blocks, (*e)->src->index))
+ gcc_unreachable ();
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1390,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (combined_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
index a58afd7944c..fdd803109a4 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
@@ -7,7 +7,7 @@
#define ATTR_NOTHROW_OPT __attribute__ ((__nothrow__))
#endif
-extern void __attribute__ ((__noreturn__)) ATTR_NOTHROW_OPT g(void);
+extern void __attribute__ ((__noreturn__)) ATTR_NOTHROW_OPT g (void);
void f(int i) {
if (i)
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..6d11487bbba
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..d5467eafa9f 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,52 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
-/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+/* Check that we insert CFR checking so as to not disrupt tail calls.
+ Mandatory tail calls are not available in C, and optimizing calls as tail
+ calls only takes place after hardcfr, so we insert checking before calls
+ followed by copies and return stmts with the same return value, that might
+ (or might not) end up optimized to tail calls. */
-extern int g(int i);
+extern int g (int i);
-int f(int i) {
+int f1(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2 (int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+void f4(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f5(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f4, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 4 "hardcfr" } } */
+/* Check before tail-call in all but f5, but f4 is out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
new file mode 100644
index 00000000000..f0338ccc361
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -O0" } */
+
+/* -fhardcfr-check-returning-calls gets implicitly disabled because,
+ -at O0, -foptimize-sibling-calls has no effect. */
+
+#include "torture/harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-always-no-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-always-no-nothrow.C
index dad9693e1d2..0d35920c7ee 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-always-no-nothrow.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-always-no-nothrow.C
@@ -9,8 +9,8 @@
/* All 3 noreturn calls. */
/* { dg-final { scan-tree-dump-times "Bypassing cleanup" 3 "hardcfr" } } */
/* Out-of-line checks in f. */
-/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in noreturn block" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in block \[0-9]*'s edge to exit" 1 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
/* Inline checks in h and h2. */
-/* { dg-final { scan-tree-dump-times "Inserting inline check in noreturn block" 2 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C
index 33e1ae26f80..b7d247ff43c 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C
@@ -10,8 +10,9 @@
/* All 3 noreturn calls. */
/* { dg-final { scan-tree-dump-times "Associated cleanup" 3 "hardcfr" } } */
/* Out-of-line checks in f. */
-/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in noreturn block" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in block \[0-9]*'s edge to exit" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
/* Inline checks in h and h2. */
-/* { dg-final { scan-tree-dump-times "Inserting inline check in noreturn block" 2 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C
index b47d880ada2..62c58cfd406 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C
@@ -15,8 +15,9 @@ void __attribute__ ((__noreturn__)) h (void);
/* All 3 noreturn calls. */
/* { dg-final { scan-tree-dump-times "Associated cleanup" 3 "hardcfr" } } */
/* Out-of-line checks in f. */
-/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in noreturn block" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check in block \[0-9]*'s edge to exit" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
/* Inline checks in h and h2. */
-/* { dg-final { scan-tree-dump-times "Inserting inline check in noreturn block" 2 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 52ef7bc601a..0286f6e6d3f 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -1,5 +1,5 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr -ffat-lto-objects" } */
/* Check that we insert cleanups for checking around the bodies of
maybe-throwing functions, and also checking before noreturn
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
index da7c9cf1033..885b0b236af 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
/* Check that we do not insert cleanups for checking around the bodies
of maybe-throwing functions. h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
new file mode 100644
index 00000000000..d150470c618
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+ maybe-throwing functions. These results depend on checking before
+ returning calls, which is only enabled when sibcall optimizations
+ are enabled, so change the optimization mode to -O1 for f and f2,
+ so that -foptimize-sibling-calls can take effect and enable
+ -fhardcfr-check-returning-calls, so that we get the same results.
+ There is a separate test for -O0. */
+
+#if ! __OPTIMIZE__
+void __attribute__ ((__optimize__ (1))) f(int i);
+void __attribute__ ((__optimize__ (1))) f2(int i);
+void __attribute__ ((__optimize__ (1))) h3(void);
+#endif
+
+#include "harden-cfr-throw.C"
+
+/* f gets out-of-line checks before the unwrapped tail call and in the
+ else edge. */
+/* f2 gets out-of-line checks before both unwrapped tail calls. */
+/* h gets out-of-line checks before the implicit return and in the
+ cleanup block. */
+/* h2 and h2b get out-of-line checks before the cleanup returning
+ call, and in the cleanup block. */
+/* h3 gets an inline check before the __cxa_end_catch returning call. */
+/* h4 gets an inline check in the cleanup block. */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 2dbc67c34d9..992fbdad381 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,5 +1,5 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
/* Check that we insert cleanups for checking around the bodies of
maybe-throwing functions. */
@@ -57,8 +57,8 @@ void h3(void) {
}
void h4(void) {
- /* Inline check before the __cxa_throw noreturn call. */
throw 1;
+ /* Inline check in the cleanup around the __cxa_throw noreturn call. */
}
/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c b/gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c
new file mode 100644
index 00000000000..634d98f1ffc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/harden-cfr-tail-ub.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
+
+/* In C only, check some additional cases (comparing with
+ c-c++-common/torture/harden-cfr-tail.c) of falling off the end of non-void
+ function. C++ would issue an unreachable call in these cases. */
+
+extern int g (int i);
+
+int f1(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+extern void g2 (int i);
+
+int f2(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f3(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+/* Out-of-line checks in f3, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
+/* Check before tail-call in all functions, but f3 is out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 1 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-02 23:34 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-02 23:34 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:63d8572aac464c75f41868da446dc6f625689552
commit 63d8572aac464c75f41868da446dc6f625689552
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Sep 2 20:33:15 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 553 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 71 ++-
6 files changed, 667 insertions(+), 151 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 455853301e3..983cb4db7c6 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1795,6 +1795,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index c47cee28eaf..32e5ba2ed2c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -626,6 +626,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16566,12 +16567,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16582,6 +16594,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..7b86e68ff5f 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,293 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && gimple_assign_single_p (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ basic_block bb = path.pop ();
+ gimple *stop = hardcfr_scan_block (bb, &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+ else
+ continue;
+
+ gcc_checking_assert (!path.is_empty ());
+ edge e = single_succ_edge (path.last ());
+ int i = EDGE_COUNT (bb->preds);
+ while (i--)
+ if (EDGE_PRED (bb, i) == e)
+ break;
+ gcc_checking_assert (i >= 0);
+ retptr = gimple_phi_arg_def_ptr (retphi, i);
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +550,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +634,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +659,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +746,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +985,48 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1090,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1127,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1236,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1286,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1327,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1357,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
+
+ auto_sbitmap combined_blocks (last_basic_block_for_fn (fun));
+ bitmap_copy (combined_blocks, chkcall_blocks);
+ int i;
+ edge *e;
+ FOR_EACH_VEC_ELT (chk_edges, i, e)
+ if (!bitmap_set_bit (combined_blocks, (*e)->src->index))
+ gcc_unreachable ();
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1390,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (combined_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..98b745f16e9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..2f956b7af32 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,74 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
int f(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2(int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+int f4(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f5(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+void f6(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f7(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f8(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f6 and f7, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 4 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 6 "hardcfr" } } */
+/* Check before tail-call in all but f8, but f6 and f7 are out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-01 6:10 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-01 6:10 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:ea37979f29d5f3b05ae5cb868f9b582707eaf559
commit ea37979f29d5f3b05ae5cb868f9b582707eaf559
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 553 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 71 ++-
6 files changed, 667 insertions(+), 151 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..f03106883ad 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16585,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..7b86e68ff5f 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,293 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && gimple_assign_single_p (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ basic_block bb = path.pop ();
+ gimple *stop = hardcfr_scan_block (bb, &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+ else
+ continue;
+
+ gcc_checking_assert (!path.is_empty ());
+ edge e = single_succ_edge (path.last ());
+ int i = EDGE_COUNT (bb->preds);
+ while (i--)
+ if (EDGE_PRED (bb, i) == e)
+ break;
+ gcc_checking_assert (i >= 0);
+ retptr = gimple_phi_arg_def_ptr (retphi, i);
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +550,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +634,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +659,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +746,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +985,48 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1090,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1127,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1236,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1286,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1327,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1357,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
+
+ auto_sbitmap combined_blocks (last_basic_block_for_fn (fun));
+ bitmap_copy (combined_blocks, chkcall_blocks);
+ int i;
+ edge *e;
+ FOR_EACH_VEC_ELT (chk_edges, i, e)
+ if (!bitmap_set_bit (combined_blocks, (*e)->src->index))
+ gcc_unreachable ();
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1390,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (combined_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..98b745f16e9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..2f956b7af32 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,74 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
int f(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2(int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+int f4(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f5(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+void f6(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f7(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f8(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f6 and f7, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 4 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 6 "hardcfr" } } */
+/* Check before tail-call in all but f8, but f6 and f7 are out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-01 5:20 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-01 5:20 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:dfe85fcf69ff842505e119b0d90cf13fe4d16c27
commit dfe85fcf69ff842505e119b0d90cf13fe4d16c27
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 554 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 71 ++-
6 files changed, 666 insertions(+), 153 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..f03106883ad 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16585,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..1a7896ca677 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,293 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && gimple_assign_single_p (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ basic_block bb = path.pop ();
+ gimple *stop = hardcfr_scan_block (bb, &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+ else
+ continue;
+
+ gcc_checking_assert (!path.is_empty ());
+ edge e = single_succ_edge (path.last ());
+ int i = EDGE_COUNT (bb->preds);
+ while (i--)
+ if (EDGE_PRED (bb, i) == e)
+ break;
+ gcc_checking_assert (i >= 0);
+ retptr = gimple_phi_arg_def_ptr (retphi, i);
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +550,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +634,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +659,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +746,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +985,45 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
- rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
-
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
+
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1087,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1124,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1233,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1283,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1324,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1354,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
+
+ auto_sbitmap combined_blocks (last_basic_block_for_fn (fun));
+ bitmap_copy (combined_blocks, chkcall_blocks);
+ int i;
+ edge *e;
+ FOR_EACH_VEC_ELT (chk_edges, i, e)
+ if (!bitmap_set_bit (combined_blocks, (*e)->src->index))
+ gcc_unreachable ();
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1387,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (combined_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..98b745f16e9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..2f956b7af32 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,74 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
int f(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2(int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+int f4(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f5(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+void f6(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f7(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f8(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f6 and f7, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 4 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 6 "hardcfr" } } */
+/* Check before tail-call in all but f8, but f6 and f7 are out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-01 4:39 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-01 4:39 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:dd456a9d522d1558b0637dca0a20259c4e0d0b9e
commit dd456a9d522d1558b0637dca0a20259c4e0d0b9e
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 546 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 71 ++-
6 files changed, 658 insertions(+), 153 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..f03106883ad 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16585,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..e2b67c45f88 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,293 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && gimple_assign_single_p (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ basic_block bb = path.pop ();
+ gimple *stop = hardcfr_scan_block (bb, &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+ else
+ continue;
+
+ gcc_checking_assert (!path.is_empty ());
+ edge e = single_succ_edge (path.last ());
+ int i = EDGE_COUNT (bb->preds);
+ while (i--)
+ if (EDGE_PRED (bb, i) == e)
+ break;
+ gcc_checking_assert (i >= 0);
+ retptr = gimple_phi_arg_def_ptr (retphi, i);
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +550,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +634,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +659,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +746,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +985,45 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
- rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
-
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
+
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1087,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1124,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1233,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1283,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1324,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1354,19 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1379,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (chkcall_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..98b745f16e9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..2f956b7af32 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,74 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
int f(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2(int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+int f4(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f5(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+void f6(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f7(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f8(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f6 and f7, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 4 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 6 "hardcfr" } } */
+/* Check before tail-call in all but f8, but f6 and f7 are out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-01 4:22 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-01 4:22 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:81ab581a6775b5aaca8083815ef54ec91b7b4fbf
commit 81ab581a6775b5aaca8083815ef54ec91b7b4fbf
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 523 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 71 ++-
6 files changed, 635 insertions(+), 153 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..f03106883ad 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16585,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..42dee6196bb 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,270 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && gimple_assign_single_p (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ gimple *stop = hardcfr_scan_block (path.pop (), &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +527,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +611,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +636,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +723,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +962,45 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
- rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
-
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
+
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1064,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1101,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1210,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1260,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1301,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1331,19 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1356,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (chkcall_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..98b745f16e9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..2f956b7af32 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,74 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
int f(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2(int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+int f4(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f5(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+void f6(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f7(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f8(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f6 and f7, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 4 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 6 "hardcfr" } } */
+/* Check before tail-call in all but f8, but f6 and f7 are out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-01 4:05 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-01 4:05 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:90264239c6725122c98c19daf5e883fb19a2a242
commit 90264239c6725122c98c19daf5e883fb19a2a242
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 523 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 71 ++-
6 files changed, 635 insertions(+), 153 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..f03106883ad 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16585,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..dbb6fa0078c 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,270 @@ public:
}
+/* Return TRUE iff CFR checks should be inserted before returning
+ calls. */
+
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_harden_control_flow_redundancy_check_returning_calls < 0
+ /* Gates pass_tail_calls. */
+ && flag_optimize_sibling_calls
+ /* Gates pass_all_ptimizations. */
+ && optimize >= 1 && !optimize_debug);
+}
+
+/* Scan BB from the end, updating *RETPTR if given as return stmts and
+ copies are found. Return a call or a stmt that cannot appear after
+ a tail call, or NULL if the top of the block is reached without
+ finding any. */
+
+static gimple *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return stmt;
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && is_gimple_assign (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ return stmt;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ gimple *stop = hardcfr_scan_block (path.pop (), &retptr);
+ if (stop)
+ {
+ if (stop != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gimple *stmt = hardcfr_scan_block (bb, &retptr);
+ if (!stmt)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ if (!is_a <gcall *> (stmt))
+ return false;
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ gcall *call = as_a <gcall *> (stmt);
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +527,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +611,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +636,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -491,93 +723,115 @@ public:
rtcfg));
gimple_seq_add_stmt (&ckseq, call_chk);
+ gimple *clobber = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, clobber);
+
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
- gimple *last = gsi_stmt (gsi_last (ckseq));
+ gimple *last = gimple_build_assign (visited,
+ build_clobber
+ (TREE_TYPE (visited)));
+ gimple_seq_add_stmt (&ckseq, last);
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +962,45 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
- rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
-
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
+
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1064,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1101,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1210,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1260,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1301,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1331,19 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1356,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (chkcall_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..98b745f16e9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..2f956b7af32 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,74 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
int f(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2(int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+int f4(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f5(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+void f6(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f7(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f8(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f6 and f7, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 4 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 6 "hardcfr" } } */
+/* Check before tail-call in all but f8, but f6 and f7 are out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-09-01 2:03 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-09-01 2:03 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:483f3ff43ede693d77143aa93f0876304adf3a2b
commit 483f3ff43ede693d77143aa93f0876304adf3a2b
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 140 +++++-
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 42 +-
gcc/gimple-harden-control-flow.cc | 498 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 +
.../c-c++-common/torture/harden-cfr-tail.c | 71 ++-
6 files changed, 611 insertions(+), 152 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..54614145a97 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,127 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
-Verification may also be performed before noreturn calls, whether only
-nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
-or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+Verification is performed just before a subprogram returns. The
+following fragment:
+
+.. code-block:: ada
+
+ if X then
+ Y := F (Z);
+ return;
+ end if;
+
+
+gets turned into:
+
+.. code-block:: ada
+
+ type Visited_Bitmap is array (1..N) of Boolean with Pack;
+ Visited : Visited_Bitmap := (others => False);
+ -- Bitmap of visited blocks. N is the basic block count.
+ [...]
+ -- Basic block #I
+ Visited(I) := True;
+ if X then
+ -- Basic block #J
+ Visited(J) := True;
+ Y := F (Z);
+ CFR.Check (N, Visited'Access, CFG'Access);
+ -- CFR is a hypothetical package whose Check procedure calls
+ -- libgcc's __hardcfr_check, that traps if the Visited bitmap
+ -- does not hold a valid path in CFG, the run-time
+ -- representation of the control flow graph in the enclosing
+ -- subprogram.
+ return;
+ end if;
+ -- Basic block #K
+ Visited(K) := True;
+
+
+Verification would also be performed before must-tail calls, and
+before early-marked potential tail calls, but these do not occur in
+practice, as potential tail-calls are only detected in late
+optimization phases, too late for this transformation to act on it.
+
+In order to avoid adding verification after potential tail calls,
+which would prevent tail-call optimization, we recognize returning
+calls, i.e., calls whose result, if any, is returned by the calling
+subprogram to its caller immediately after the call returns.
+Verification is performed before such calls, whether or not they are
+ultimately optimized to tail calls. This behavior is enabled by
+default whenever sibcall optimization is enabled (see
+:switch:`-foptimize-sibling-calls`); it may be disabled with
+:switch:`-fno-hardcfr-check-returning-calls`, or enabled with
+:switch:`-fhardcfr-check-returning-calls`, regardless of the
+optimization, but the lack of other optimizations may prevent calls
+from being recognized as returning calls:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ P (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return;
+
+or:
+
+.. code-block:: ada
+
+ -- -fhardcfr-check-returning-calls: CFR.Check here.
+ R := F (X);
+ -- -fno-hardcfr-check-returning-calls: CFR.Check here.
+ return R;
+
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+conceptually enclosed by a cleanup handler that performs verification,
+unless this is disabled with :switch:`-fno-hardcfr-check-exceptions`.
+With this feature enabled, a subprogram body containing:
+
+.. code-block:: ada
+
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+
+
+gets modified as follows:
+
+.. code-block:: ada
+
+ begin
+ -- ...
+ Y := F (X); -- May raise exceptions.
+ -- ...
+ raise E; -- Not handled internally.
+ -- ...
+ exception
+ when others =>
+ CFR.Check (N, Visited'Access, CFG'Access);
+ raise;
+ end;
+
+
+Verification may also be performed before No_Return calls, whether
+only nothrow ones, with
+:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
+:switch:`-fhardcfr-check-noreturn-calls=always`. The default is
+:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
+disables checking before No_Return calls.
+
+When a No_Return call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked No_Return calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but a local exception handler,
+if present, will modify the set of visited blocks, and checking will
+take place again when the caller reaches the next verification point,
+whether it is a return or reraise statement after the exception is
+otherwise handled, or even another No_Return call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..f03106883ad 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,23 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify and trap, at function exits, when the booleans do not form an
+execution path that is compatible with the control flow graph.
+
+Verification takes place before returns, before mandatory tail calls
+(see below) and, optionally, before escaping exceptions with
+@option{-fhardcfr-check-exceptions}, before returning calls with
+@option{-fhardcfr-check-returning-calls}, and before noreturn calls with
+@option{-fhardcfr-check-noreturn-calls}). Tuning options
+@option{--param hardcfr-max-blocks} and @option{--param
+hardcfr-max-inline-blocks} are available.
+
+Tail call optimization takes place too late to affect control flow
+redundancy, but calls annotated as mandatory tail calls by language
+front-ends, and any calls marked early enough as potential tail calls
+would also have verification issued before the call, but these
+possibilities are merely theoretical, as these conditions can only be
+met when using custom compiler plugins.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16585,24 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before any
+function call immediately followed by a return of its result, if any, so
+as to not prevent tail-call optimization, whether or not it is
+ultimately optimized to a tail call.
+
+This option is enabled by default, whenever
+@option{-foptimize-sibling-calls} is enabled, but it can be enabled (or
+disabled, using its negated form) explicitly, regardless of the
+optimization. Note, however, that without other optimizations, the
+initially-unified return block for each function remains separated from
+any preceding statements, and therefore calls will not be found to be
+immediately followed by a return statement, and so they won't be
+recognized nor handled as returning calls.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..63a0716fea5 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,255 @@ public:
}
+static bool
+check_returning_calls_p ()
+{
+ return
+ flag_harden_control_flow_redundancy_check_returning_calls > 0
+ || (flag_optimize_sibling_calls
+ && flag_harden_control_flow_redundancy_check_returning_calls < 0);
+}
+
+static gcall *
+hardcfr_scan_block (basic_block bb, tree **retptr)
+{
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_last_bb (bb); !gsi_end_p (gsi); gsi_prev (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ /* Ignore labels, returns, nops, clobbers and debug stmts. */
+ if (gimple_code (stmt) == GIMPLE_LABEL
+ || gimple_code (stmt) == GIMPLE_NOP
+ || gimple_code (stmt) == GIMPLE_PREDICT
+ || gimple_clobber_p (stmt)
+ || is_gimple_debug (stmt))
+ continue;
+
+ if (gimple_code (stmt) == GIMPLE_RETURN)
+ {
+ greturn *gret = as_a <greturn *> (stmt);
+ if (retptr)
+ {
+ gcc_checking_assert (!*retptr);
+ *retptr = gimple_return_retval_ptr (gret);
+ }
+ continue;
+ }
+
+ /* Check for a call. */
+ if (is_gimple_call (stmt))
+ return as_a <gcall *> (stmt);
+
+ /* Allow simple copies to the return value, updating the return
+ value to be found in earlier assignments. */
+ if (retptr && *retptr && is_gimple_assign (stmt)
+ && **retptr == gimple_assign_lhs (stmt))
+ {
+ *retptr = gimple_assign_rhs1_ptr (stmt);
+ continue;
+ }
+
+ break;
+ }
+
+ /* Any other kind of stmt will prevent a tail call. */
+ return NULL;
+}
+
+/* Return TRUE iff CALL is to be preceded by a CFR checkpoint, i.e.,
+ if it's a returning call (one whose result is ultimately returned
+ without intervening non-copy statements) and we're checking
+ returning calls, a __builtin_return call (noreturn with a path to
+ the exit block), a must-tail call, or a tail call. */
+
+static bool
+returning_call_p (gcall *call)
+{
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || check_returning_calls_p ()))
+ return false;
+
+ /* Quickly check that there's a path to exit compatible with a
+ returning call. Detect infinite loops through the counter. */
+ basic_block bb = gimple_bb (call);
+ auto_vec<basic_block, 10> path;
+ for (int i = n_basic_blocks_for_fn (cfun);
+ bb != EXIT_BLOCK_PTR_FOR_FN (cfun) && i--;
+ bb = single_succ (bb))
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+ else
+ path.safe_push (bb);
+
+ /* Check the stmts in the blocks and trace the return value. */
+ tree *retptr = NULL;
+ for (;;)
+ {
+ gcc_checking_assert (!path.is_empty ());
+ gcall *found = hardcfr_scan_block (path.pop (), &retptr);
+ if (found)
+ {
+ if (found != call)
+ return false;
+ gcc_checking_assert (path.is_empty ());
+ break;
+ }
+ }
+
+ return (gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ()));
+}
+
+typedef auto_vec<edge, 10> chk_edges_t;
+
+/* Declare for mutual recursion. */
+static bool hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr);
+
+/* Search backwards from the end of BB for a mandatory or potential
+ sibcall. Schedule the block to be handled sort-of like noreturn if
+ so. Recurse to preds, with updated RETPTR, if the block only
+ contains stmts that may follow such a call, scheduling checking at
+ edges and marking blocks as post-check as needed. Return true iff,
+ at the end of the block, a check will have already been
+ performed. */
+
+static bool
+hardcfr_sibcall_search_block (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* Conditionals and internal exceptions rule out tail calls. */
+ if (!single_succ_p (bb)
+ || (single_succ_edge (bb)->flags & EDGE_EH) != 0)
+ return false;
+
+ gcall *call = hardcfr_scan_block (bb, &retptr);
+ if (!call)
+ return hardcfr_sibcall_search_preds (bb, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ retptr);
+
+ /* Avoid disrupting mandatory or early-marked tail calls,
+ inserting the check before them. This works for
+ must-tail calls, but tail calling as an optimization is
+ detected too late for us.
+
+ Also check for noreturn calls here. Noreturn calls won't
+ normally have edges to exit, so they won't be found here,
+ but __builtin_return does, and we must check before
+ it, so handle it like a tail call. */
+ if (!(gimple_call_noreturn_p (call)
+ || gimple_call_must_tail_p (call)
+ || gimple_call_tail_p (call)
+ || (gimple_call_lhs (call) == (retptr ? *retptr : NULL)
+ && check_returning_calls_p ())))
+ return false;
+
+ gcc_checking_assert (returning_call_p (call));
+
+ /* We found a call that is to be preceded by checking. */
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ ++count_chkcall;
+ else
+ gcc_unreachable ();
+ return true;
+}
+
+
+/* Search preds of BB for a mandatory or potential sibcall or
+ returning call, and arrange for the blocks containing them to have
+ a check inserted before the call, like noreturn calls. If any
+ preds are found to perform checking, schedule checks at the edges
+ of those that don't, and mark BB as postcheck.. */
+
+static bool
+hardcfr_sibcall_search_preds (basic_block bb,
+ chk_edges_t &chk_edges,
+ int &count_chkcall,
+ auto_sbitmap &chkcall_blocks,
+ int &count_postchk,
+ auto_sbitmap &postchk_blocks,
+ tree *retptr)
+{
+ /* For the exit block, we wish to force a check at every
+ predecessor, so pretend we've already found a pred that had
+ checking, so that we schedule checking at every one of its pred
+ edges. */
+ bool first = bb->index >= NUM_FIXED_BLOCKS;
+ bool postchecked = true;
+
+ gphi *retphi = NULL;
+ if (retptr && *retptr && TREE_CODE (*retptr) == SSA_NAME
+ && !SSA_NAME_IS_DEFAULT_DEF (*retptr)
+ && SSA_NAME_DEF_STMT (*retptr)
+ && is_a <gphi *> (SSA_NAME_DEF_STMT (*retptr))
+ && gimple_bb (SSA_NAME_DEF_STMT (*retptr)) == bb)
+ {
+ retphi = as_a <gphi *> (SSA_NAME_DEF_STMT (*retptr));
+ gcc_checking_assert (gimple_phi_result (retphi) == *retptr);
+ }
+
+ for (int i = EDGE_COUNT (bb->preds); i--; first = false)
+ {
+ edge e = EDGE_PRED (bb, i);
+
+ bool checked
+ = hardcfr_sibcall_search_block (e->src, chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ !retphi ? retptr
+ : gimple_phi_arg_def_ptr (retphi, i));
+
+ if (first)
+ {
+ postchecked = checked;
+ continue;
+ }
+
+ /* When we first find a checked block, force a check at every
+ other incoming edge we've already visited, and those we
+ visit afterwards that don't have their own check, so that
+ when we reach BB, the check has already been performed. */
+ if (!postchecked && checked)
+ {
+ for (int j = EDGE_COUNT (bb->preds); --j > i; )
+ chk_edges.safe_push (EDGE_PRED (bb, j));
+ postchecked = true;
+ }
+ if (postchecked && !checked)
+ chk_edges.safe_push (EDGE_PRED (bb, i));
+ }
+
+ if (postchecked && bb->index >= NUM_FIXED_BLOCKS)
+ {
+ if (bitmap_set_bit (postchk_blocks, bb->index))
+ count_postchk++;
+ else
+ gcc_unreachable ();
+ }
+
+ return postchecked;
+}
+
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -263,7 +512,7 @@ class rt_bb_visited
public:
/* Prepare to add control flow redundancy testing to CFUN. */
- rt_bb_visited (int noreturn_blocks)
+ rt_bb_visited (int checkpoints)
: nblocks (n_basic_blocks_for_fn (cfun)),
vword_type (NULL), ckseq (NULL), rtcfg (NULL)
{
@@ -347,8 +596,7 @@ public:
gimple_seq_add_stmt (&ckseq, detach);
if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
- || (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
- + noreturn_blocks > 1))
+ || checkpoints > 1)
{
/* Make sure vword_bits is wide enough for the representation
of nblocks in rtcfg. Compare with vword_bits << vword_bits,
@@ -373,63 +621,32 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
- /* Insert SEQ before a resx, or noreturn or tail call at the end of
- INSBB, and return TRUE, otherwise return FALSE. */
- bool insert_exit_check (gimple_seq seq, basic_block insbb)
+ /* Insert SEQ before a resx or a call in INSBB. */
+ void insert_exit_check_in_block (gimple_seq seq, basic_block insbb)
{
- /* If the returning block ends with a noreturn call, insert
- checking before it. This is particularly important for
- __builtin_return. Other noreturn calls won't have an edge to
- the exit block, so they won't even be considered as exit paths.
-
- Insert-on-edge inserts before other return stmts, but not
- before calls, and if a single-block function had the check
- sequence inserted after a noreturn call, it would be useless,
- but checking would still flag it as malformed if block 2 has a
- fallthru edge to the exit block.
-
- Also avoid disrupting tail calls, inserting the check before
- them. This works for must-tail calls, but tail calling as an
- optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
+ while (!gsi_end_p (gsi))
+ if (is_a <gresx *> (gsi_stmt (gsi))
+ || is_a <gcall *> (gsi_stmt (gsi)))
+ break;
+ else
gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- else
- return false;
- return true;
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
}
- /* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ /* Insert SEQ on E. */
+ void insert_exit_check_on_edge (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
- gsi_insert_seq_on_edge_immediate (e, seq);
+ gsi_insert_seq_on_edge_immediate (e, seq);
}
- /* Add checking code on every exit edge, and initialization code on
+ /* Add checking code to CHK_EDGES, and initialization code on
the entry edge. Before this point, the CFG has been undisturbed,
and all the needed data has been collected and safely stowed. */
- void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
+ void check (chk_edges_t &chk_edges,
+ int count_chkcall, auto_sbitmap const &chkcall_blocks)
{
/* If we're using out-of-line checking, create and statically
initialize the CFG checking representation, generate the
@@ -493,91 +710,105 @@ public:
/* If we have multiple exit edges, insert (copies of)
ckseq in all of them. */
- for (int i = EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds);
- i--; )
+ for (int i = chk_edges.length (); i--; )
{
gimple_seq seq = ckseq;
/* Copy the sequence, unless we're dealing with the
last edge (we're counting down to zero). */
- if (i || count_noreturn)
+ if (i || count_chkcall)
seq = gimple_seq_copy (seq);
- edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ edge e = chk_edges[i];
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
- e->src->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ }
- insert_exit_check (seq, e);
+ insert_exit_check_on_edge (seq, e);
- gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+ gcc_checking_assert (!bitmap_bit_p (chkcall_blocks, e->src->index));
}
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting out-of-line check in noreturn block %i.\n",
+ "Inserting out-of-line check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
else
{
/* Inline checking requires a single exit edge. */
gimple *last = gsi_stmt (gsi_last (ckseq));
- if (!count_noreturn)
+ if (!count_chkcall)
{
+ edge e = single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun));
+
if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ {
+ if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun))
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to postcheck block %i.\n",
+ e->src->index, e->dest->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ insert_exit_check_on_edge (ckseq, e);
}
else
{
- gcc_checking_assert (count_noreturn == 1);
+ gcc_checking_assert (count_chkcall == 1);
sbitmap_iterator it;
unsigned i;
- EXECUTE_IF_SET_IN_BITMAP (noreturn_blocks, 0, i, it)
+ EXECUTE_IF_SET_IN_BITMAP (chkcall_blocks, 0, i, it)
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, i);
gimple_seq seq = ckseq;
- gcc_checking_assert (count_noreturn > 0);
- if (--count_noreturn)
+ gcc_checking_assert (count_chkcall > 0);
+ if (--count_chkcall)
seq = gimple_seq_copy (seq);
if (dump_file)
fprintf (dump_file,
- "Inserting inline check in noreturn block %i.\n",
+ "Inserting inline check before stmt in block %i.\n",
bb->index);
- if (!insert_exit_check (seq, bb))
- gcc_unreachable ();
+ insert_exit_check_in_block (seq, bb);
}
- gcc_checking_assert (count_noreturn == 0);
+ gcc_checking_assert (count_chkcall == 0);
}
/* The inserted ckseq computes CKFAIL at LAST. Now we have to
@@ -708,37 +939,45 @@ public:
/* Add to BB code to set its bit in VISITED, and add to RTCFG or
CKSEQ the data or code needed to check BB's predecessors and
- successors. Do NOT change the CFG. */
- void visit (basic_block bb, bool noreturn)
+ successors. If NORETURN, assume the block is a checkpoint,
+ whether or not it has an edge to EXIT. If POSTCHECK, assume the
+ block post-dominates checkpoints and therefore no bitmap setting
+ or checks are to be performed in or for it. Do NOT change the
+ CFG. */
+ void visit (basic_block bb, bool noreturn, bool postcheck)
{
/* Set the bit in VISITED when entering the block. */
gimple_stmt_iterator gsi = gsi_after_labels (bb);
- gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
+ if (!postcheck)
+ gsi_insert_seq_before (&gsi, vset (bb), GSI_SAME_STMT);
if (rtcfg)
{
- /* Build a list of (index, mask) terminated by (NULL, 0).
- Consolidate masks with the same index when they're
- adjacent. First, predecessors. Count backwards, because
- we're going to reverse the list. The order shouldn't
- matter, but let's not make it surprising. */
- for (int i = EDGE_COUNT (bb->preds); i--; )
- if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
- ENTRY_BLOCK_PTR_FOR_FN (cfun)))
- break;
- rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
-
- /* Then, successors. */
- if (!noreturn
- || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
- bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
- for (int i = EDGE_COUNT (bb->succs); i--; )
- if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
- EXIT_BLOCK_PTR_FOR_FN (cfun)))
- break;
+ if (!postcheck)
+ {
+ /* Build a list of (index, mask) terminated by (NULL, 0).
+ Consolidate masks with the same index when they're
+ adjacent. First, predecessors. Count backwards, because
+ we're going to reverse the list. The order shouldn't
+ matter, but let's not make it surprising. */
+ for (int i = EDGE_COUNT (bb->preds); i--; )
+ if (push_rtcfg_pair (EDGE_PRED (bb, i)->src, bb,
+ ENTRY_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
+
+ /* Then, successors. */
+ if (!noreturn
+ || !push_rtcfg_pair (EXIT_BLOCK_PTR_FOR_FN (cfun),
+ bb, EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ for (int i = EDGE_COUNT (bb->succs); i--; )
+ if (push_rtcfg_pair (EDGE_SUCC (bb, i)->dest, bb,
+ EXIT_BLOCK_PTR_FOR_FN (cfun)))
+ break;
+ }
rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
}
- else
+ else if (!postcheck)
{
/* Schedule test to fail if the block was reached but somehow none
of its predecessors were. */
@@ -802,8 +1041,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
= (flag_exceptions
&& check_before_noreturn_calls
&& flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
- basic_block bb_eh_cleanup = NULL;
basic_block bb;
+ basic_block bb_eh_cleanup = NULL;
if (check_at_escaping_exceptions)
{
@@ -839,12 +1078,13 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
if (is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || returning_call_p (as_a <gcall *> (stmt))))
continue;
if (!gsi_one_before_end_p (gsi))
@@ -947,13 +1187,18 @@ pass_harden_control_flow_redundancy::execute (function *fun)
}
}
+ /* These record blocks with calls that are to be preceded by
+ checkpoints, such as noreturn calls (if so chosen), must-tail
+ calls, potential early-marked tail calls, and returning calls (if
+ so chosen). */
+ int count_chkcall = 0;
+ auto_sbitmap chkcall_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (chkcall_blocks);
+
/* We wish to add verification at blocks without successors, such as
noreturn calls (raising or not) and the reraise at the cleanup
block, but not other reraises: they will go through the cleanup
block. */
- int count_noreturn = 0;
- auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
- bitmap_clear (noreturn_blocks);
if (check_before_noreturn_calls)
FOR_EACH_BB_FN (bb, fun)
{
@@ -992,8 +1237,8 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
@@ -1033,27 +1278,27 @@ pass_harden_control_flow_redundancy::execute (function *fun)
print_gimple_stmt (dump_file, stmt, 0);
}
- if (bitmap_set_bit (noreturn_blocks, bb->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
}
else if (bb_eh_cleanup)
{
- if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
- count_noreturn++;
+ if (bitmap_set_bit (chkcall_blocks, bb_eh_cleanup->index))
+ count_chkcall++;
else
gcc_unreachable ();
}
gcc_checking_assert (!bb_eh_cleanup
- || bitmap_bit_p (noreturn_blocks, bb_eh_cleanup->index));
+ || bitmap_bit_p (chkcall_blocks, bb_eh_cleanup->index));
/* If we don't have edges to exit nor noreturn calls (including the
cleanup reraise), then we may skip instrumentation: that would
amount to a function that ends with an infinite loop. */
- if (!count_noreturn
+ if (!count_chkcall
&& EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
{
if (dump_file)
@@ -1063,7 +1308,19 @@ pass_harden_control_flow_redundancy::execute (function *fun)
return 0;
}
- rt_bb_visited vstd (count_noreturn);
+ /* Search for must-tail calls, early-marked potential tail calls,
+ and, if requested, returning calls. As we introduce early
+ checks, */
+ int count_postchk = 0;
+ auto_sbitmap postchk_blocks (last_basic_block_for_fn (fun));
+ bitmap_clear (postchk_blocks);
+ chk_edges_t chk_edges;
+ hardcfr_sibcall_search_preds (EXIT_BLOCK_PTR_FOR_FN (fun), chk_edges,
+ count_chkcall, chkcall_blocks,
+ count_postchk, postchk_blocks,
+ NULL);
+
+ rt_bb_visited vstd (chk_edges.length () + count_chkcall);
/* Visit blocks in index order, because building rtcfg depends on
that. Blocks must be compact, which the cleanup_cfg requirement
@@ -1076,10 +1333,11 @@ pass_harden_control_flow_redundancy::execute (function *fun)
{
bb = BASIC_BLOCK_FOR_FN (fun, i);
gcc_checking_assert (bb->index == i);
- vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+ vstd.visit (bb, bitmap_bit_p (chkcall_blocks, i),
+ bitmap_bit_p (postchk_blocks, i));
}
- vstd.check (count_noreturn, noreturn_blocks);
+ vstd.check (chk_edges, count_chkcall, chkcall_blocks);
return
TODO_update_ssa
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..98b745f16e9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..2f956b7af32 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -1,17 +1,74 @@
/* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects -Wno-return-type" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
int f(int i) {
+ /* Inline check before the returning call. */
return g (i);
}
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+extern void g2(int i);
+
+void f2(int i) {
+ /* Inline check before the returning call, that ignores the returned value,
+ matching the value-less return. */
+ g2 (i);
+ return;
+}
+
+void f3(int i) {
+ /* Inline check before the returning call. */
+ g (i);
+}
+
+int f4(int i) {
+ /* Inline check before the returning call, that disregards its return
+ value. */
+ g2 (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g2, and is recognized as a returning call. */
+}
+
+int f5(int i) {
+ /* Inline check before the returning call, that doesn't return anything. */
+ g (i);
+ /* Implicit return without value, despite the return type; this combination
+ enables tail-calling of g, and is recognized as a returning call. */
+}
+
+void f6(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g2 (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f7(int i) {
+ if (i)
+ /* Out-of-line check before the returning call. */
+ return g (i);
+ /* Out-of-line check before implicit return. */
+}
+
+int f8(int i) {
+ /* Not regarded as a returning call, returning value other than callee's
+ returned value. */
+ g (i);
+ /* Inline check after the non-returning call. */
+ return i;
+}
+
+/* Out-of-line checks in f6 and f7, before returning calls and before return. */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 4 "hardcfr" } } */
+/* Inline checking in all other functions. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 6 "hardcfr" } } */
+/* Check before tail-call in all but f8, but f6 and f7 are out-of-line. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 5 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting out-of-line check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-08-27 3:37 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-08-27 3:37 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:72739a9a3d77c953104d06c6e041b8c62b623088
commit 72739a9a3d77c953104d06c6e041b8c62b623088
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
.../doc/gnat_rm/security_hardening_features.rst | 40 +++---
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 27 +++-
gcc/gimple-harden-control-flow.cc | 149 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 ++
.../c-c++-common/torture/harden-cfr-tail.c | 25 +++-
6 files changed, 190 insertions(+), 63 deletions(-)
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 9d762e7c8cc..8c065de49d5 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,25 +263,33 @@ For each block that is marked as visited, the mechanism checks that at
least one of its predecessors, and at least one of its successors, are
also marked as visited.
-Verification is performed just before returns and tail calls.
+Verification is performed just before a subprogram returns.
+
+Verification may optionally also be performed before returning calls, i.e.,
+calls whose result, if any, is immediately returned to the caller,
+whether or not they are optimized to tail calls. This may be disabled
+with :switch:`-fno-hardcfr-check-returning-calls`.
+
+Any subprogram from which an exception may escape, i.e., that may
+raise or propagate an exception that isn't handled internally, is
+automatically enclosed by a cleanup handler that performs
+verification, unless this is disabled with
+:switch:`-fno-hardcfr-check-exceptions`.
+
Verification may also be performed before noreturn calls, whether only
nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`,
or all of them, with :switch:`-fhardcfr-check-noreturn-calls=always`.
-Furthermore, any subprogram from which an exception may escape, i.e.,
-that may raise or propagate an exception that isn't handled
-internally, is automatically enclosed by a cleanup handler that
-performs verification, unless this is disabled with
-:switch:`-fno-hardcfr-check-exceptions`. When a noreturn call returns
-control to its caller through an exception, verification may have
-already been performed before the call, assuming
-:switch:`-fhardcfr-check-noreturn-calls=always` is in effect. The
-compiler arranges for already-checked noreturn calls without a
-preexisting handler to bypass the implicitly-added cleanup handler and
-thus the redundant check, but calls with a local handler will use it,
-which modifies the set of visited blocks, and checking will take place
-againwhen the caller reaches the next verification point, whether it
-is a return or reraise statement after the exception is otherwise
-handled, or even another noreturn call.
+
+When a noreturn call returns control to its caller through an
+exception, verification may have already been performed before the
+call, assuming :switch:`-fhardcfr-check-noreturn-calls=always` is in
+effect. The compiler arranges for already-checked noreturn calls
+without a preexisting handler to bypass the implicitly-added cleanup
+handler and thus the redundant check, but calls with a local handler
+will use it, which modifies the set of visited blocks, and checking
+will take place againwhen the caller reaches the next verification
+point, whether it is a return or reraise statement after the exception
+is otherwise handled, or even another noreturn call.
The instrumentation for hardening with control flow redundancy can be
observed in dump files generated by the command-line option
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..3fcf3db6046 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,13 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify, at function exits (returns, and optionally, before escaping
+exceptions with @option{-fhardcfr-check-exceptions}, before returning
+calls with @option{-fhardcfr-check-returning-calls}, and before noreturn
+calls with @option{-fhardcfr-check-noreturn-calls}), and trap when they
+indicate an execution path that is incompatible with the control flow
+graph. Tuning options @option{--param hardcfr-max-blocks} and
+@option{--param hardcfr-max-inline-blocks} are available.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16575,19 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before function
+calls immediately followed by returning their result, whether or not
+they are optimized to tail calls. Calls are optimized to tail calls too
+late to affect control flow redundancy, but calls annotated as mandatory
+tail calls, and any calls marked early enough as potential tail calls
+get the same treatment, even if not followed by a return of their
+result. This option is enabled by default; use
+@option{-fno-hardcfr-check-returning-calls} to disable it.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..7e2bee881aa 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,38 @@ public:
}
+/* Return TRUE if GSI is a potential tail call, and should be
+ handled as such. */
+static bool
+potential_tail_call_p (gimple_stmt_iterator gsi)
+{
+ basic_block bb = gsi_bb (gsi);
+ if (!single_succ_p (bb)
+ || single_succ (bb) != EXIT_BLOCK_PTR_FOR_FN (cfun))
+ return false;
+
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *call = gsi_stmt (gsi);
+ if (!call || !is_a <gcall *> (call))
+ return false;
+
+ gsi_next_nondebug (&gsi);
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *ret = gsi_stmt (gsi);
+ if (!is_a <greturn *> (ret))
+ return false;
+
+ if (gimple_return_retval (as_a <greturn *> (ret))
+ != gimple_call_lhs (as_a <gcall *> (call)))
+ return false;
+
+ return true;
+}
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -373,6 +405,45 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
+ /* Return TRUE if a checkpoint is to be inserted before the
+ statement at GSIP (it may move). */
+ bool check_before_p (gimple_stmt_iterator *gsip)
+ {
+ bool moved = false;
+ gimple_stmt_iterator gsi = *gsip;
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *ret = gsi_stmt (gsi);
+
+ if (ret && is_a <gresx *> (ret))
+ return true;
+
+ if (ret && is_a <greturn *> (ret))
+ {
+ moved = true;
+ gsi_prev_nondebug (&gsi);
+ if (!gsi_end_p (gsi))
+ ret = gsi_stmt (gsi);
+ }
+
+ if (!ret || !is_a <gcall *> (ret))
+ return false;
+
+ if (gimple_call_noreturn_p (ret)
+ || (flag_harden_control_flow_redundancy_check_returning_calls
+ && (gimple_call_must_tail_p (as_a <gcall *> (ret))
+ || gimple_call_tail_p (as_a <gcall *> (ret))
+ || potential_tail_call_p (gsi))))
+ {
+ if (moved)
+ *gsip = gsi;
+ return true;
+ }
+
+ return false;
+ }
+
/* Insert SEQ before a resx, or noreturn or tail call at the end of
INSBB, and return TRUE, otherwise return FALSE. */
bool insert_exit_check (gimple_seq seq, basic_block insbb)
@@ -392,25 +463,8 @@ public:
them. This works for must-tail calls, but tail calling as an
optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
-
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
- if (ret && is_a <greturn *> (ret))
- {
- gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
+ if (check_before_p (&gsi))
gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
else
return false;
@@ -419,11 +473,14 @@ public:
}
/* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ call at the end of E->src). Return TRUE if insertion was before
+ a stmt in E->src rather than at the edge. */
+ bool insert_exit_check (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
+ bool ret = insert_exit_check (seq, e->src);
+ if (!ret)
gsi_insert_seq_on_edge_immediate (e, seq);
+ return ret;
}
/* Add checking code on every exit edge, and initialization code on
@@ -504,13 +561,21 @@ public:
edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ bool before_stmt_p = insert_exit_check (seq, e);
+
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
+ {
+ if (before_stmt_p)
+ fprintf (dump_file,
+ "Inserting out-of-line check"
+ " before stmt in block %i.\n",
e->src->index);
-
- insert_exit_check (seq, e);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
}
@@ -544,14 +609,24 @@ public:
if (!count_noreturn)
{
- if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ bool before_stmt_p
+ = insert_exit_check (ckseq,
+ single_pred_edge
+ (EXIT_BLOCK_PTR_FOR_FN (cfun)));
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ if (dump_file)
+ {
+ if (before_stmt_p)
+ fprintf (dump_file,
+ "Inserting inline check"
+ " before stmt in block %i.\n",
+ single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ }
}
else
{
@@ -839,12 +914,14 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
- if (is_a <gcall *> (stmt)
+ if (flag_harden_control_flow_redundancy_check_returning_calls
+ && is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || potential_tail_call_p (gsi)))
continue;
if (!gsi_one_before_end_p (gsi))
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..92566403ec7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..18f03295193 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -2,8 +2,10 @@
/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
@@ -11,7 +13,20 @@ int f(int i) {
return g (i);
}
+extern void g2(int i);
+
+int f2(int i) {
+ g2 (i);
+}
+
+int f3(int i) {
+ /* Not regarded as tail call, returning value other than callee's returned
+ value. */
+ g (i);
+ return i;
+}
+
/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* f and f2 only. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-08-27 2:58 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-08-27 2:58 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:de1c8f7bbdfe022721fa4f316de9d7545b6f2242
commit de1c8f7bbdfe022721fa4f316de9d7545b6f2242
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 27 +++-
gcc/gimple-harden-control-flow.cc | 147 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 ++
.../c-c++-common/torture/harden-cfr-tail.c | 25 +++-
5 files changed, 164 insertions(+), 47 deletions(-)
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..3fcf3db6046 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,13 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify, at function exits (returns, and optionally, before escaping
+exceptions with @option{-fhardcfr-check-exceptions}, before returning
+calls with @option{-fhardcfr-check-returning-calls}, and before noreturn
+calls with @option{-fhardcfr-check-noreturn-calls}), and trap when they
+indicate an execution path that is incompatible with the control flow
+graph. Tuning options @option{--param hardcfr-max-blocks} and
+@option{--param hardcfr-max-inline-blocks} are available.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16575,19 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before function
+calls immediately followed by returning their result, whether or not
+they are optimized to tail calls. Calls are optimized to tail calls too
+late to affect control flow redundancy, but calls annotated as mandatory
+tail calls, and any calls marked early enough as potential tail calls
+get the same treatment, even if not followed by a return of their
+result. This option is enabled by default; use
+@option{-fno-hardcfr-check-returning-calls} to disable it.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..3e3020174b9 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,38 @@ public:
}
+/* Return TRUE if GSI is a potential tail call, and should be
+ handled as such. */
+static bool
+potential_tail_call_p (gimple_stmt_iterator gsi)
+{
+ basic_block bb = gsi_bb (gsi);
+ if (!single_succ_p (bb)
+ || single_succ (bb) != EXIT_BLOCK_PTR_FOR_FN (cfun))
+ return false;
+
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *call = gsi_stmt (gsi);
+ if (!call || !is_a <gcall *> (call))
+ return false;
+
+ gsi_next_nondebug (&gsi);
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *ret = gsi_stmt (gsi);
+ if (!is_a <greturn *> (ret))
+ return false;
+
+ if (gimple_return_retval (as_a <greturn *> (ret))
+ != gimple_call_lhs (as_a <gcall *> (call)))
+ return false;
+
+ return true;
+}
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -373,6 +405,45 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
+ /* Return TRUE if a checkpoint is to be inserted before the
+ statement at GSIP (it may move). */
+ bool check_before_p (gimple_stmt_iterator *gsip)
+ {
+ bool moved = false;
+ gimple_stmt_iterator gsi = *gsip;
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *ret = gsi_stmt (gsi);
+
+ if (ret && is_a <gresx *> (ret))
+ return true;
+
+ if (ret && is_a <greturn *> (ret))
+ {
+ moved = true;
+ gsi_prev_nondebug (&gsi);
+ if (!gsi_end_p (gsi))
+ ret = gsi_stmt (gsi);
+ }
+
+ if (!ret || !is_a <gcall *> (ret))
+ return false;
+
+ if (gimple_call_noreturn_p (ret)
+ || (flag_harden_control_flow_redundancy_check_returning_calls
+ && (gimple_call_must_tail_p (as_a <gcall *> (ret))
+ || gimple_call_tail_p (as_a <gcall *> (ret))
+ || potential_tail_call_p (gsi))))
+ {
+ if (moved)
+ *gsip = gsi;
+ return true;
+ }
+
+ return false;
+ }
+
/* Insert SEQ before a resx, or noreturn or tail call at the end of
INSBB, and return TRUE, otherwise return FALSE. */
bool insert_exit_check (gimple_seq seq, basic_block insbb)
@@ -392,25 +463,8 @@ public:
them. This works for must-tail calls, but tail calling as an
optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
- gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
+ if (check_before_p (&gsi))
gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
else
return false;
@@ -419,11 +473,14 @@ public:
}
/* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ call at the end of E->src). Return TRUE if insertion was before
+ a stmt in E->src rather than at the edge. */
+ bool insert_exit_check (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
+ bool ret = insert_exit_check (seq, e->src);
+ if (!ret)
gsi_insert_seq_on_edge_immediate (e, seq);
+ return ret;
}
/* Add checking code on every exit edge, and initialization code on
@@ -504,13 +561,21 @@ public:
edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ bool before_stmt_p = insert_exit_check (seq, e);
+
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
+ {
+ if (before_stmt_p)
+ fprintf (dump_file,
+ "Inserting out-of-line check"
+ " before stmt in block %i.\n",
e->src->index);
-
- insert_exit_check (seq, e);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
}
@@ -544,14 +609,22 @@ public:
if (!count_noreturn)
{
- if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ bool before_stmt_p
+ = insert_exit_check (ckseq,
+ single_pred_edge
+ (EXIT_BLOCK_PTR_FOR_FN (cfun)));
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ if (dump_file)
+ if (before_stmt_p)
+ fprintf (dump_file,
+ "Inserting inline check"
+ " before stmt in block %i.\n",
+ single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
}
else
{
@@ -839,12 +912,14 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
- if (is_a <gcall *> (stmt)
+ if (flag_harden_control_flow_redundancy_check_returning_calls
+ && is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || potential_tail_call_p (gsi)))
continue;
if (!gsi_one_before_end_p (gsi))
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..92566403ec7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..18f03295193 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -2,8 +2,10 @@
/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
@@ -11,7 +13,20 @@ int f(int i) {
return g (i);
}
+extern void g2(int i);
+
+int f2(int i) {
+ g2 (i);
+}
+
+int f3(int i) {
+ /* Not regarded as tail call, returning value other than callee's returned
+ value. */
+ g (i);
+ return i;
+}
+
/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* f and f2 only. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls
@ 2022-08-27 2:55 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2022-08-27 2:55 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:32cb7aa2ef459d5e162c6ecfe89d6823b5034859
commit 32cb7aa2ef459d5e162c6ecfe89d6823b5034859
Author: Alexandre Oliva <oliva@gnu.org>
Date: Fri Aug 26 02:51:16 2022 -0300
hardcfr: check before potential sibcalls
Diff:
---
gcc/common.opt | 4 +
gcc/doc/invoke.texi | 27 +++-
gcc/gimple-harden-control-flow.cc | 147 ++++++++++++++++-----
.../c-c++-common/torture/harden-cfr-notail.c | 8 ++
.../c-c++-common/torture/harden-cfr-tail.c | 25 +++-
5 files changed, 164 insertions(+), 47 deletions(-)
diff --git a/gcc/common.opt b/gcc/common.opt
index 57f01330fcf..09cc46d7dbc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,10 @@ fharden-control-flow-redundancy
Common Var(flag_harden_control_flow_redundancy) Optimization
Harden control flow by recording and checking execution paths.
+fhardcfr-check-returning-calls
+Common Var(flag_harden_control_flow_redundancy_check_returning_calls) Init(-1) Optimization
+Check CFR execution paths also before calls followed by returns of their results.
+
fhardcfr-check-exceptions
Common Var(flag_harden_control_flow_redundancy_check_exceptions) Init(-1) Optimization
Check CFR execution paths also when exiting a function through an exception.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a8409d6e3e..3fcf3db6046 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,6 +625,7 @@ Objective-C and Objective-C++ Dialects}.
-fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
-fharden-compares -fharden-conditional-branches @gol
-fharden-control-flow-redundancy -fhardcfr-check-exceptions @gol
+-fhardcfr-check-returning-calls @gol
-fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]} @gol
-fstack-protector -fstack-protector-all -fstack-protector-strong @gol
-fstack-protector-explicit -fstack-check @gol
@@ -16557,12 +16558,13 @@ conditionals.
@item -fharden-control-flow-redundancy
@opindex fharden-control-flow-redundancy
Emit extra code to set booleans when entering basic blocks, and to
-verify, at function exits (returns, before tail calls, and optionally,
-before escaping exceptions with @option{-fhardcfr-check-exceptions}, and
-before noreturn calls with @option{-fhardcfr-check-noreturn-calls}), and
-trap when they indicate an execution path that is incompatible with the
-control flow graph. Tuning options @option{--param hardcfr-max-blocks}
-and @option{--param hardcfr-max-inline-blocks} are available.
+verify, at function exits (returns, and optionally, before escaping
+exceptions with @option{-fhardcfr-check-exceptions}, before returning
+calls with @option{-fhardcfr-check-returning-calls}, and before noreturn
+calls with @option{-fhardcfr-check-noreturn-calls}), and trap when they
+indicate an execution path that is incompatible with the control flow
+graph. Tuning options @option{--param hardcfr-max-blocks} and
+@option{--param hardcfr-max-inline-blocks} are available.
@item -fhardcfr-check-exceptions
@opindex fhardcfr-check-exceptions
@@ -16573,6 +16575,19 @@ escape points, as if the function body was wrapped with a cleanup
handler that performed the check and reraised. This option is enabled
by default; use @option{-fno-hardcfr-check-exceptions} to disable it.
+@item -fhardcfr-check-returning-calls
+@opindex fhardcfr-check-returning-calls
+@opindex fno-hardcfr-check-returning-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before function
+calls immediately followed by returning their result, whether or not
+they are optimized to tail calls. Calls are optimized to tail calls too
+late to affect control flow redundancy, but calls annotated as mandatory
+tail calls, and any calls marked early enough as potential tail calls
+get the same treatment, even if not followed by a return of their
+result. This option is enabled by default; use
+@option{-fno-hardcfr-check-returning-calls} to disable it.
+
@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
@opindex fhardcfr-check-noreturn-calls
When @option{-fharden-control-flow-redundancy} is active, check the
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6d03e24d38a..3e3020174b9 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -128,6 +128,38 @@ public:
}
+/* Return TRUE if GSI is a potential tail call, and should be
+ handled as such. */
+static bool
+potential_tail_call_p (gimple_stmt_iterator gsi)
+{
+ basic_block bb = gsi_bb (gsi);
+ if (!single_succ_p (bb)
+ || single_succ (bb) != EXIT_BLOCK_PTR_FOR_FN (cfun))
+ return false;
+
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *call = gsi_stmt (gsi);
+ if (!call || !is_a <gcall *> (call))
+ return false;
+
+ gsi_next_nondebug (&gsi);
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *ret = gsi_stmt (gsi);
+ if (!is_a <greturn *> (ret))
+ return false;
+
+ if (gimple_return_retval (as_a <greturn *> (ret))
+ != gimple_call_lhs (as_a <gcall *> (call)))
+ return false;
+
+ return true;
+}
+
class rt_bb_visited
{
/* Use a sufficiently wide unsigned type to hold basic block numbers. */
@@ -373,6 +405,45 @@ public:
gimple_seq_add_stmt (&ckseq, ckfail_init);
}
+ /* Return TRUE if a checkpoint is to be inserted before the
+ statement at GSIP (it may move). */
+ bool check_before_p (gimple_stmt_iterator *gsip)
+ {
+ bool moved = false;
+ gimple_stmt_iterator gsi = *gsip;
+ if (gsi_end_p (gsi))
+ return false;
+
+ gimple *ret = gsi_stmt (gsi);
+
+ if (ret && is_a <gresx *> (ret))
+ return true;
+
+ if (ret && is_a <greturn *> (ret))
+ {
+ moved = true;
+ gsi_prev_nondebug (&gsi);
+ if (!gsi_end_p (gsi))
+ ret = gsi_stmt (gsi);
+ }
+
+ if (!ret || !is_a <gcall *> (ret))
+ return false;
+
+ if (gimple_call_noreturn_p (ret)
+ || (flag_harden_control_flow_redundancy_check_returning_calls
+ && (gimple_call_must_tail_p (as_a <gcall *> (ret))
+ || gimple_call_tail_p (as_a <gcall *> (ret))
+ || potential_tail_call_p (gsi))))
+ {
+ if (moved)
+ *gsip = gsi;
+ return true;
+ }
+
+ return false;
+ }
+
/* Insert SEQ before a resx, or noreturn or tail call at the end of
INSBB, and return TRUE, otherwise return FALSE. */
bool insert_exit_check (gimple_seq seq, basic_block insbb)
@@ -392,25 +463,8 @@ public:
them. This works for must-tail calls, but tail calling as an
optimization is detected too late for us. */
gimple_stmt_iterator gsi = gsi_last_bb (insbb);
- gimple *ret = gsi_stmt (gsi);
- if (ret && is_a <gresx *> (ret))
- {
- gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
- return true;
- }
-
- if (ret && is_a <greturn *> (ret))
- {
- gsi_prev (&gsi);
- if (!gsi_end_p (gsi))
- ret = gsi_stmt (gsi);
- }
- if (ret
- && is_a <gcall *> (ret)
- && (gimple_call_noreturn_p (ret)
- || gimple_call_must_tail_p (as_a <gcall *> (ret))
- || gimple_call_tail_p (as_a <gcall *> (ret))))
+ if (check_before_p (&gsi))
gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
else
return false;
@@ -419,11 +473,14 @@ public:
}
/* Insert SEQ on E, or close enough (e.g., before a noreturn or tail
- call at the end of E->src). */
- void insert_exit_check (gimple_seq seq, edge e)
+ call at the end of E->src). Return TRUE if insertion was before
+ a stmt in E->src rather than at the edge. */
+ bool insert_exit_check (gimple_seq seq, edge e)
{
- if (!insert_exit_check (seq, e->src))
+ bool ret = insert_exit_check (seq, e->src);
+ if (!ret)
gsi_insert_seq_on_edge_immediate (e, seq);
+ return ret;
}
/* Add checking code on every exit edge, and initialization code on
@@ -504,13 +561,21 @@ public:
edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+ bool before_stmt_p = insert_exit_check (seq, e);
+
if (dump_file)
- fprintf (dump_file,
- "Inserting out-of-line check in"
- " block %i's edge to exit.\n",
+ {
+ if (before_stmt_p)
+ fprintf (dump_file,
+ "Inserting out-of-line check"
+ " before stmt in block %i.\n",
e->src->index);
-
- insert_exit_check (seq, e);
+ else
+ fprintf (dump_file,
+ "Inserting out-of-line check in"
+ " block %i's edge to exit.\n",
+ e->src->index);
+ }
gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
}
@@ -544,14 +609,22 @@ public:
if (!count_noreturn)
{
- if (dump_file)
- fprintf (dump_file,
- "Inserting inline check in"
- " block %i's edge to exit.\n",
- single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ bool before_stmt_p
+ = insert_exit_check (ckseq,
+ single_pred_edge
+ (EXIT_BLOCK_PTR_FOR_FN (cfun)));
- insert_exit_check (ckseq,
- single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+ if (dump_file)
+ if (before_stmt_p)
+ fprintf (dump_file,
+ "Inserting inline check"
+ " before stmt in block %i.\n",
+ single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
+ else
+ fprintf (dump_file,
+ "Inserting inline check in"
+ " block %i's edge to exit.\n",
+ single_pred (EXIT_BLOCK_PTR_FOR_FN (cfun))->index);
}
else
{
@@ -839,12 +912,14 @@ pass_harden_control_flow_redundancy::execute (function *fun)
if (lookup_stmt_eh_lp (stmt) != 0)
continue;
- /* Don't split blocks at, nor add EH edvges to, tail
+ /* Don't split blocks at, nor add EH edges to, tail
calls, we will add verification before the call
anyway. */
- if (is_a <gcall *> (stmt)
+ if (flag_harden_control_flow_redundancy_check_returning_calls
+ && is_a <gcall *> (stmt)
&& (gimple_call_must_tail_p (as_a <gcall *> (stmt))
- || gimple_call_tail_p (as_a <gcall *> (stmt))))
+ || gimple_call_tail_p (as_a <gcall *> (stmt))
+ || potential_tail_call_p (gsi)))
continue;
if (!gsi_one_before_end_p (gsi))
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
new file mode 100644
index 00000000000..92566403ec7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-notail.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fno-hardcfr-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+#include "harden-cfr-tail.c"
+
+/* Inline checking after the calls, disabling tail calling. */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 09daa70fa3a..18f03295193 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
@@ -2,8 +2,10 @@
/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
/* We'd like to check that we insert checking so as to not disrupt tail calls,
- but unfortunately mandatory tail calls are not available in C, and optimizing
- calls as tail calls only takes place after hardcfr. */
+ but unfortunately mandatory tail calls are not available in C, and
+ optimizing calls as tail calls only takes place after hardcfr, so we insert
+ checking before calls followed by return stmts with the same return value,
+ because they might end up as tail calls. */
extern int g(int i);
@@ -11,7 +13,20 @@ int f(int i) {
return g (i);
}
+extern void g2(int i);
+
+int f2(int i) {
+ g2 (i);
+}
+
+int f3(int i) {
+ /* Not regarded as tail call, returning value other than callee's returned
+ value. */
+ g (i);
+ return i;
+}
+
/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Inline checking before the tail call. */
-/* { dg-final { scan-tree-dump-times "\\\[tail\]" 1 "hardcfr" { xfail *-*-* } } } */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 3 "hardcfr" } } */
+/* f and f2 only. */
+/* { dg-final { scan-tree-dump-times "Inserting inline check before stmt" 2 "hardcfr" } } */
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2022-09-03 23:58 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-03 23:06 [gcc(refs/users/aoliva/heads/testme)] hardcfr: check before potential sibcalls Alexandre Oliva
-- strict thread matches above, loose matches on Subject: below --
2022-09-03 23:58 Alexandre Oliva
2022-09-02 23:34 Alexandre Oliva
2022-09-01 6:10 Alexandre Oliva
2022-09-01 5:20 Alexandre Oliva
2022-09-01 4:39 Alexandre Oliva
2022-09-01 4:22 Alexandre Oliva
2022-09-01 4:05 Alexandre Oliva
2022-09-01 2:03 Alexandre Oliva
2022-08-27 3:37 Alexandre Oliva
2022-08-27 2:58 Alexandre Oliva
2022-08-27 2:55 Alexandre Oliva
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).