public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
From: Alexandre Oliva <aoliva@gcc.gnu.org>
To: gcc-cvs@gcc.gnu.org
Subject: [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls: tweaks
Date: Wed, 24 Aug 2022 17:18:55 +0000 (GMT)	[thread overview]
Message-ID: <20220824171856.4E6AE3858C33@sourceware.org> (raw)

https://gcc.gnu.org/g:972a1750d4b53481e3084f370be4881fd1503b1f

commit 972a1750d4b53481e3084f370be4881fd1503b1f
Author: Alexandre Oliva <oliva@gnu.org>
Date:   Wed Aug 24 13:37:03 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls: tweaks

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  29 +-
 gcc/common.opt                                     |  29 ++
 gcc/doc/invoke.texi                                |  51 ++-
 gcc/flag-types.h                                   |  10 +
 gcc/gimple-harden-control-flow.cc                  | 351 ++++++++++++++++-----
 .../c-c++-common/harden-cfr-noret-never-O0.c       |  12 +
 .../c-c++-common/torture/harden-cfr-noret-never.c  |  18 ++
 .../torture/harden-cfr-noret-noexcept.c            |  16 +
 .../torture/harden-cfr-noret-nothrow.c             |  13 +
 .../c-c++-common/torture/harden-cfr-noret.c        |  38 +++
 gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C  |  11 +
 .../torture/harden-cfr-noret-always-no-nothrow.C   |  16 +
 .../torture/harden-cfr-noret-never-no-nothrow.C    |  17 +
 .../g++.dg/torture/harden-cfr-noret-no-nothrow.C   |  22 ++
 .../g++.dg/torture/harden-cfr-throw-always.C       |  20 ++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C    |  11 +
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C    |  65 ++++
 .../gcc.dg/torture/harden-cfr-noret-no-nothrow.c   |  15 +
 libgcc/hardcfr.c                                   | 119 ++++++-
 19 files changed, 768 insertions(+), 95 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index b7803cde588..9d762e7c8cc 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,16 +263,25 @@ 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, tail- and noreturn
-calls.  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.  When a noreturn call returns control to
-its caller through an exception, verification will have already been
-performed before the call, but it will take place again when the
-caller reaches the next verification point, whether it is the end of
-the enclosing cleanup handler, a return or reraise statement after the
-exception is otherwise handled, or even another noreturn call.
+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.
 
 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 e0a002e6b16..8f97eebb210 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,35 @@ fharden-control-flow-redundancy
 Common Var(flag_harden_control_flow_redundancy) Init(-1) Optimization
 Harden control flow by recording and checking execution paths.
 
+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.
+
+fhardcfr-check-noreturn-calls=
+Common Joined RejectNegative Enum(hardcfr_check_noreturn_calls) Var(flag_harden_control_flow_redundancy_check_noreturn) Init(HCFRNR_UNSPECIFIED) Optimization
+-fhardcfr-check-noreturn-calls=[always|nothrow|never]	Check CFR execution paths also before calling noreturn functions.
+
+Enum
+Name(hardcfr_check_noreturn_calls) Type(enum hardcfr_noret) UnknownError(unknown hardcfr noreturn checking level %qs)
+
+EnumValue
+Enum(hardcfr_check_noreturn_calls) String(never) Value(HCFRNR_NEVER)
+
+EnumValue
+Enum(hardcfr_check_noreturn_calls) String(nothrow) Value(HCFRNR_NOTHROW)
+
+; ??? There could be yet another option here, that checked before
+; noreturn calls, except for those known to always throw, if we had
+; means to distinguish noreturn functions known to always throw, such
+; as those used to (re)raise exceptions, from those that merely might
+; throw.  "not always" stands for "not always-throwing", but it also
+; contrasts with "always" below.
+; EnumValue
+; Enum(hardcfr_check_noreturn_calls) String(not-always) Value(HCFRNR_NOT_ALWAYS)
+
+EnumValue
+Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
+
 ; Nonzero means ignore `#ident' directives.  0 means handle them.
 ; Generate position-independent code for executables if possible
 ; On SVR4 targets, it also controls whether or not to emit a
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9c8f2eecb6c..1a8409d6e3e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -624,7 +624,8 @@ Objective-C and Objective-C++ Dialects}.
 -fsanitize-undefined-trap-on-error  -fbounds-check @gol
 -fcf-protection=@r{[}full@r{|}branch@r{|}return@r{|}none@r{|}check@r{]} @gol
 -fharden-compares -fharden-conditional-branches @gol
--fharden-control-flow-redundancy @gol
+-fharden-control-flow-redundancy  -fhardcfr-check-exceptions  @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
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
@@ -16556,11 +16557,49 @@ 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, escaping exceptions, and before tail
-and 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, 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.
+
+@item -fhardcfr-check-exceptions
+@opindex fhardcfr-check-exceptions
+@opindex fno-hardcfr-check-exceptions
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph at exception
+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-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@opindex fhardcfr-check-noreturn-calls
+When @option{-fharden-control-flow-redundancy} is active, check the
+recorded execution path against the control flow graph before
+@code{noreturn} calls, either all of them (@option{always}), those that
+may not return control to the caller through an exception either
+(@option{nothrow}), or none of them (@option{never}, the default).
+
+Checking before a @code{noreturn} function that may return control to
+the caller through an exception may cause checking to be performed more
+than once, if the exception is caught in the caller, whether by a
+handler or a cleanup.  When @option{-fhardcfr-check-exceptions} is also
+enabled, the compiler will avoid associating a @code{noreturn} call with
+the implicitly-added cleanup handler, since it would be redundant with
+the check performed before the call, but other handlers or cleanups in
+the function, if activated, will modify the recorded execution path and
+check it again when another checkpoint is hit.  The checkpoint may even
+be another @code{noreturn} call, so checking may end up performed
+multiple times.
+
+Various optimizers may cause calls to be marked as @code{noreturn}
+and/or @code{nothrow}, even in the absence of the corresponding
+attributes, which may affect the placement of checks before calls, as
+well as the addition of implicit cleanup handlers for them.  This
+unpredictability, and the fact that raising and reraising exceptions
+frequently amounts to implicitly calling @code{noreturn} functions, have
+made @option{never} the default setting for this option.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index a11f99af887..02926184559 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -163,6 +163,16 @@ enum stack_reuse_level
   SR_ALL
 };
 
+/* Control Flow Redundancy hardening options for noreturn calls.  */
+enum hardcfr_noret
+{
+  HCFRNR_NEVER,
+  HCFRNR_NOTHROW,
+  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_ALWAYS,
+  HCFRNR_UNSPECIFIED = -1
+};   
+
 /* The live patching level.  */
 enum live_patching_level
 {
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 5066a43fe6c..149b2b674e7 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -29,7 +29,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-pass.h"
 #include "ssa.h"
 #include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
 #include "tree-cfg.h"
+#include "tree-cfgcleanup.h"
 #include "tree-eh.h"
 #include "except.h"
 #include "sbitmap.h"
@@ -113,7 +115,8 @@ public:
       }
 
     if (param_hardcfr_max_blocks > 0
-	&& n_basic_blocks_for_fn (fun) - 2 > param_hardcfr_max_blocks)
+	&& (n_basic_blocks_for_fn (fun) - NUM_FIXED_BLOCKS
+	    > param_hardcfr_max_blocks))
       {
 	if (flag_harden_control_flow_redundancy < 0)
 	  return false;
@@ -173,8 +176,8 @@ class rt_bb_visited
      neither ENTRY nor EXIT, but maybe one-past-the-end, to compute
      the visited array length.  */
   blknum num2idx (blknum n) {
-    gcc_checking_assert (n >= 2 && n <= nblocks);
-    return (n - 2);
+    gcc_checking_assert (n >= NUM_FIXED_BLOCKS && n <= nblocks);
+    return (n - NUM_FIXED_BLOCKS);
   }
   /* Return the block vindex for BB, that must not be ENTRY or
      EXIT.  */
@@ -246,8 +249,7 @@ class rt_bb_visited
   }
 
   /* Set the bit corresponding to BB in VISITED.  Add to SEQ any
-     required gimple statements, and return SEQ, possibly
-     modified.  */
+     required gimple stmts, and return SEQ, possibly modified.  */
   gimple_seq vset (basic_block bb, gimple_seq seq = NULL)
   {
     tree bit, setme = vword (bb, &bit);
@@ -350,8 +352,7 @@ public:
 					 NULL, NULL);
     gimple_seq_add_stmt (&ckseq, detach);
 
-    if (nblocks - 2 > blknum (param_hardcfr_max_inline_blocks)
-	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun))
+    if (nblocks - NUM_FIXED_BLOCKS > blknum (param_hardcfr_max_inline_blocks)
 	|| (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
 	    + noreturn_blocks > 1))
       {
@@ -509,6 +510,12 @@ public:
 
 	    edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
 
+	    if (dump_file)
+	      fprintf (dump_file,
+		       "Inserting out-of-line check in"
+		       " block %i's edge to exit.\n",
+		       e->src->index);
+
 	    insert_exit_check (seq, e);
 
 	    gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
@@ -525,6 +532,11 @@ public:
 	    if (--count_noreturn)
 	      seq = gimple_seq_copy (seq);
 
+	    if (dump_file)
+	      fprintf (dump_file,
+		       "Inserting out-of-line check in noreturn block %i.\n",
+		       bb->index);
+
 	    if (!insert_exit_check (seq, bb))
 	      gcc_unreachable ();
 	  }
@@ -536,8 +548,43 @@ public:
 	/* Inline checking requires a single exit edge.  */
 	gimple *last = gsi_stmt (gsi_last (ckseq));
 
-	insert_exit_check (ckseq,
-			   single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+	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);
+
+	    insert_exit_check (ckseq,
+			       single_pred_edge (EXIT_BLOCK_PTR_FOR_FN (cfun)));
+	  }
+	else
+	  {
+	    gcc_checking_assert (count_noreturn == 1);
+
+	    sbitmap_iterator it;
+	    unsigned i;
+	    EXECUTE_IF_SET_IN_BITMAP (noreturn_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)
+		  seq = gimple_seq_copy (seq);
+
+		if (dump_file)
+		  fprintf (dump_file,
+			   "Inserting inline check in noreturn block %i.\n",
+			   bb->index);
+
+		if (!insert_exit_check (seq, bb))
+		  gcc_unreachable ();
+	      }
+
+	    gcc_checking_assert (count_noreturn == 0);
+	  }
 
 	/* The inserted ckseq computes CKFAIL at LAST.  Now we have to
 	   conditionally trap on it.  */
@@ -558,8 +605,7 @@ public:
 	  add_bb_to_loop (trp, current_loops->tree_root);
 
 	/* Insert a conditional branch to the trap block.  If the
-	   conditional wouldn't be the last statement, split the
-	   block.  */
+	   conditional wouldn't be the last stmt, split the block.  */
 	gimple_stmt_iterator gsi = gsi_for_stmt (last);
 	if (!gsi_one_before_end_p (gsi))
 	  split_block (gsi_bb (gsi), gsi_stmt (gsi));
@@ -643,7 +689,7 @@ public:
     return false;
   }
 
-  /* Add to CKSEQ statements to clear CKPART if OBB is visited.  */
+  /* Add to CKSEQ stmts to clear CKPART if OBB is visited.  */
   void
   build_block_check (basic_block obb)
   {
@@ -669,7 +715,7 @@ 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)
+  void visit (basic_block bb, bool noreturn)
   {
     /* Set the bit in VISITED when entering the block.  */
     gimple_stmt_iterator gsi = gsi_after_labels (bb);
@@ -689,10 +735,13 @@ public:
 	rtcfg = tree_cons (NULL_TREE, build_int_cst (vword_type, 0), rtcfg);
 
 	/* Then, successors.  */
-	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 (!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
@@ -713,6 +762,8 @@ public:
 	gassign *blkruns = gimple_build_assign (ckpart, unshare_expr (bit));
 	gimple_seq_add_stmt (&ckseq, blkruns);
 
+	if (noreturn)
+	  build_block_check (EXIT_BLOCK_PTR_FOR_FN (cfun));
 	for (int i = 0, e = EDGE_COUNT (bb->succs); i < e; i++)
 	  build_block_check (EDGE_SUCC (bb, i)->dest);
 
@@ -723,16 +774,44 @@ public:
   }
 };
 
+/* It might be useful to avoid checking before noreturn calls that are
+   known to always finish by throwing an exception, rather than by
+   ending the program or looping forever.  Such functions would have
+   to be annotated somehow, with an attribute or flag.
+   Exception-raising functions, such as C++'s __cxa_throw,
+   __cxa_rethrow, and Ada's */
+static bool
+always_throwing_noreturn_call_p (gimple *)
+{
+  return false;
+}
+
 /* Control flow redundancy hardening: record the execution path, and
    verify at exit that an expect path was taken.  */
 
 unsigned int
 pass_harden_control_flow_redundancy::execute (function *fun)
 {
+  bool const check_at_escaping_exceptions
+    = (flag_exceptions
+       && flag_harden_control_flow_redundancy_check_exceptions);
+  bool const check_before_noreturn_calls
+    = flag_harden_control_flow_redundancy_check_noreturn > HCFRNR_NEVER;
+  bool const check_before_nothrow_noreturn_calls
+    = (check_before_noreturn_calls
+       && flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_NOTHROW);
+  bool const check_before_throwing_noreturn_calls
+    = (flag_exceptions
+       && check_before_noreturn_calls
+       && flag_harden_control_flow_redundancy_check_noreturn > HCFRNR_NOTHROW);
+  bool const check_before_always_throwing_noreturn_calls
+    = (flag_exceptions
+       && check_before_noreturn_calls
+       && flag_harden_control_flow_redundancy_check_noreturn >= HCFRNR_ALWAYS);
   basic_block bb_eh_cleanup = NULL;
   basic_block bb;
 
-  if (flag_exceptions)
+  if (check_at_escaping_exceptions)
     {
       int lp_eh_cleanup = -1;
 
@@ -758,7 +837,7 @@ pass_harden_control_flow_redundancy::execute (function *fun)
 	       !gsi_end_p (gsi); gsi_prev (&gsi))
 	    {
 	      gimple *stmt = gsi_stmt (gsi);
-	      if (!gimple_could_trap_p (stmt))
+	      if (!stmt_could_throw_p (fun, stmt))
 		continue;
 
 	      /* If it must not throw, or if it already has a handler,
@@ -766,8 +845,48 @@ pass_harden_control_flow_redundancy::execute (function *fun)
 	      if (lookup_stmt_eh_lp (stmt) != 0)
 		continue;
 
-	      if (!stmt_ends_bb_p (stmt))
+	      /* Don't split blocks at, nor add EH edvges 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))))
+		continue;
+
+	      if (!gsi_one_before_end_p (gsi))
 		split_block (bb, stmt);
+	      /* A resx or noreturn call needs not be associated with
+		 the cleanup handler if we're going to add checking
+		 before it.  We only test cases that didn't require
+		 block splitting because noreturn calls would always
+		 be at the end of blocks, and we test for zero
+		 successors because if there is an edge, it's not
+		 noreturn, as any EH edges would have already been
+		 caught by the lookup_stmt_eh_lp test above.  */
+	      else if (check_before_noreturn_calls
+		       && EDGE_COUNT (bb->succs) == 0
+		       && (is_a <gresx *> (stmt)
+			   ? check_before_always_throwing_noreturn_calls
+			   : (!is_a <gcall *> (stmt)
+			      || !gimple_call_noreturn_p (stmt))
+			   ? (gcc_unreachable (), false)
+			   : (!flag_exceptions
+			      || gimple_call_nothrow_p (as_a <gcall *> (stmt)))
+			   ? check_before_nothrow_noreturn_calls
+			   : always_throwing_noreturn_call_p (stmt)
+			   ? check_before_always_throwing_noreturn_calls
+			   : check_before_throwing_noreturn_calls))
+		{
+		  if (dump_file)
+		    {
+		      fprintf (dump_file,
+			       "Bypassing cleanup for noreturn stmt"
+			       " in block %i:\n",
+			       bb->index);
+		      print_gimple_stmt (dump_file, stmt, 0);
+		    }
+		  continue;
+		}
 
 	      if (!bb_eh_cleanup)
 		{
@@ -790,15 +909,47 @@ pass_harden_control_flow_redundancy::execute (function *fun)
 		  ehgsi = gsi_after_labels (bb_eh_cleanup);
 		  gresx *resx = gimple_build_resx (new_r->index);
 		  gsi_insert_before (&ehgsi, resx, GSI_SAME_STMT);
+
+		  if (dump_file)
+		    fprintf (dump_file,
+			     "Created cleanup block %i:\n",
+			     bb_eh_cleanup->index);
+		}
+	      else if (dom_info_available_p (CDI_DOMINATORS))
+		{
+		  basic_block immdom;
+		  immdom = get_immediate_dominator (CDI_DOMINATORS,
+						    bb_eh_cleanup);
+		  if (!dominated_by_p (CDI_DOMINATORS, bb, immdom))
+		    {
+		      immdom = nearest_common_dominator (CDI_DOMINATORS,
+							 immdom, bb);
+		      set_immediate_dominator (CDI_DOMINATORS,
+					       bb_eh_cleanup, immdom);
+		    }
 		}
-	      else
+
+	      if (dump_file)
 		{
-		  // Update immedite dominator and loop?
+		  fprintf (dump_file,
+			   "Associated cleanup block with stmt in block %i:\n",
+			   bb->index);
+		  print_gimple_stmt (dump_file, stmt, 0);
 		}
 
 	      add_stmt_to_eh_lp (stmt, lp_eh_cleanup);
 	      /* Finally, wire the EH cleanup block into the CFG.  */
-	      make_eh_edges (stmt);		}
+	      make_eh_edges (stmt);
+	    }
+	}
+
+      if (bb_eh_cleanup)
+	{
+	  /* A cfg_cleanup after bb_eh_cleanup makes for a more compact
+	     rtcfg, and it avoids bb numbering differences when we split
+	     blocks because of trailing debug insns only.  */
+	  cleanup_tree_cfg ();
+	  gcc_checking_assert (EDGE_COUNT (bb_eh_cleanup->succs) == 0);
 	}
     }
 
@@ -809,56 +960,97 @@ pass_harden_control_flow_redundancy::execute (function *fun)
   int count_noreturn = 0;
   auto_sbitmap noreturn_blocks (last_basic_block_for_fn (fun));
   bitmap_clear (noreturn_blocks);
-  FOR_EACH_BB_FN (bb, fun)
-    {
-      if (EDGE_COUNT (bb->succs) == 0)
-	{
-	  if (bitmap_set_bit (noreturn_blocks, bb->index))
-	    count_noreturn++;
+  if (check_before_noreturn_calls)
+    FOR_EACH_BB_FN (bb, fun)
+      {
+	gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	if (gsi_end_p (gsi))
 	  continue;
-	}
+	gimple *stmt = gsi_stmt (gsi);
 
-      /* If there are no exceptions, then any noreturn call must have
-	 zero successor edges.  Otherwise, check for blocks without
-	 non-EH successors, but skip those with resx stmts and edges
-	 (i.e., those other than that in bb_eh_cleanup), since those
-	 will go through bb_eh_cleanup, that will have been counted as
-	 noreturn above because it has no successors.  */
-      gcc_checking_assert (bb != bb_eh_cleanup);
-      if (!flag_exceptions)
-	continue;
-
-      bool found_non_eh_edge = false;
-      bool found_eh_edge = false;
-      edge e;
-      edge_iterator ei;
-      FOR_EACH_EDGE (e, ei, bb->succs)
-	{
-	  if ((e->flags & EDGE_EH))
-	    found_eh_edge = true;
-	  else
-	    found_non_eh_edge = true;
-	  if (found_non_eh_edge && found_eh_edge)
-	    break;
-	}
-
-      if (found_non_eh_edge)
-	continue;
-
-      if (found_eh_edge)
-	{
-	  /* We don't wish to check before (re?)raises, those will
-	     have checking performed at bb_eh_cleanup.  The one
-	     exception is bb_eh_cleanup itself.  */
-	  gimple_stmt_iterator gsi = gsi_last_bb (bb);
-	  gcc_checking_assert (!gsi_end_p (gsi));
-	  gimple *stmt = gsi_stmt (gsi);
-	  if (is_a <gresx *> (stmt))
+	if (EDGE_COUNT (bb->succs) == 0)
+	  {
+	    /* A stmt at the end of a block without any successors is
+	       either a resx or a noreturn call without a local
+	       handler.  Check that it's one of the desired
+	       checkpoints.  */
+	    if (flag_exceptions && is_a <gresx *> (stmt)
+		? (check_before_always_throwing_noreturn_calls
+		   || bb == bb_eh_cleanup)
+		: (!is_a <gcall *> (stmt)
+		   || !gimple_call_noreturn_p (stmt))
+		? (/* Catch cases in which successors would be
+		      expected.  */
+		   gcc_unreachable (), false)
+		: (!flag_exceptions
+		   || gimple_call_nothrow_p (as_a <gcall *> (stmt)))
+		? check_before_nothrow_noreturn_calls
+		: always_throwing_noreturn_call_p (stmt)
+		? check_before_always_throwing_noreturn_calls
+		: check_before_throwing_noreturn_calls)
+	      {
+		if (dump_file)
+		  {
+		    fprintf (dump_file,
+			     "Scheduling check before stmt"
+			     " in succ-less block %i:\n",
+			     bb->index);
+		    print_gimple_stmt (dump_file, stmt, 0);
+		  }
+
+		if (bitmap_set_bit (noreturn_blocks, bb->index))
+		  count_noreturn++;
+		else
+		  gcc_unreachable ();
+	      }
 	    continue;
-	}
+	  }
 
-      if (bitmap_set_bit (noreturn_blocks, bb->index))
+	/* If there are no exceptions, then any noreturn call must have
+	   zero successor edges.  Otherwise, check for blocks without
+	   non-EH successors, but skip those with resx stmts and edges
+	   (i.e., those other than that in bb_eh_cleanup), since those
+	   will go through bb_eh_cleanup, that will have been counted as
+	   noreturn above because it has no successors.  */
+	gcc_checking_assert (bb != bb_eh_cleanup
+			     || !check_at_escaping_exceptions);
+	if (flag_exceptions && is_a <gresx *> (stmt)
+	    ? check_before_always_throwing_noreturn_calls
+	    : (!is_a <gcall *> (stmt)
+	       || !gimple_call_noreturn_p (stmt))
+	    ? false
+	    : (!flag_exceptions
+	       || gimple_call_nothrow_p (as_a <gcall *> (stmt)))
+	    ? (/* Catch cases that should not have successors.  */
+	       gcc_unreachable (), check_before_nothrow_noreturn_calls)
+	    : always_throwing_noreturn_call_p (stmt)
+	    ? check_before_always_throwing_noreturn_calls
+	    : check_before_throwing_noreturn_calls)
+	  {
+	    gcc_checking_assert (single_succ_p (bb)
+				 && (single_succ_edge (bb)->flags & EDGE_EH));
+
+	    if (dump_file)
+	      {
+		fprintf (dump_file,
+			 "Scheduling check before stmt"
+			 " in EH-succ block %i:\n",
+			 bb->index);
+		print_gimple_stmt (dump_file, stmt, 0);
+	      }
+
+	    if (bitmap_set_bit (noreturn_blocks, bb->index))
+	      count_noreturn++;
+	    else
+	      gcc_unreachable ();
+	  }
+      }
+  else if (bb_eh_cleanup)
+    {
+      if (bitmap_set_bit (noreturn_blocks, bb_eh_cleanup->index))
 	count_noreturn++;
+      else
+	gcc_unreachable ();
     }
 
   gcc_checking_assert (!bb_eh_cleanup
@@ -869,12 +1061,29 @@ pass_harden_control_flow_redundancy::execute (function *fun)
      amount to a function that ends with an infinite loop.  */
   if (!count_noreturn
       && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-    return 0;
+    {
+      if (dump_file)
+	fprintf (dump_file,
+		 "Disabling CFR, no exit paths to check\n");
+
+      return 0;
+    }
 
   rt_bb_visited vstd (count_noreturn);
 
-  FOR_EACH_BB_FN (bb, fun)
-    vstd.visit (bb);
+  /* Visit blocks in index order, because building rtcfg depends on
+     that.  Blocks must be compact, which the cleanup_cfg requirement
+     ensures.  This would also enable FOR_EACH_BB_FN to be used to
+     iterate in index order, but bb_eh_cleanup block splits and
+     insertions changes that.  */
+  gcc_checking_assert (n_basic_blocks_for_fn (fun)
+		       == last_basic_block_for_fn (fun));
+  for (int i = NUM_FIXED_BLOCKS; i < n_basic_blocks_for_fn (fun); i++)
+    {
+      bb = BASIC_BLOCK_FOR_FN (fun, i);
+      gcc_checking_assert (bb->index == i);
+      vstd.visit (bb, bitmap_bit_p (noreturn_blocks, i));
+    }
 
   vstd.check (count_noreturn, noreturn_blocks);
 
diff --git a/gcc/testsuite/c-c++-common/harden-cfr-noret-never-O0.c b/gcc/testsuite/c-c++-common/harden-cfr-noret-never-O0.c
new file mode 100644
index 00000000000..a6992eb9f8e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/harden-cfr-noret-never-O0.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -O0 -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we don't insert checking before noreturn calls.  -O0 is tested
+   separately because h is not found to be noreturn without optimization.  */
+
+#include "torture/harden-cfr-noret.c"
+
+/* No out-of-line checks.  */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 0 "hardcfr" } } */
+/* Only one inline check at the end of f and of h2.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-never.c
new file mode 100644
index 00000000000..8bd2d13ac18
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-never.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we don't insert checking before noreturn calls.  -O0 is tested
+   separately because h is not found to be noreturn without optimization, which
+   affects codegen for h2, so h2 is omitted here at -O0.  */
+
+#if !__OPTIMIZE__
+# define OMIT_H2
+#endif
+
+#include "harden-cfr-noret.c"
+
+
+/* No out-of-line checks.  */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 0 "hardcfr" } } */
+/* Only one inline check at the end of f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-noexcept.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-noexcept.c
new file mode 100644
index 00000000000..a804a6cfe59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-noexcept.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fno-exceptions -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that -fno-exceptions makes for implicit nothrow in noreturn
+   handling.  */
+
+#define ATTR_NOTHROW_OPT
+
+#include "harden-cfr-noret.c"
+
+/* One out-of-line check before the noreturn call in f, and another at the end
+   of f.  */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* One inline check in h, before the noreturn call, and another in h2, before
+   or after the call, depending on noreturn detection.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-nothrow.c
new file mode 100644
index 00000000000..f390cfdbc59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert checking before nothrow noreturn calls.  */
+
+#include "harden-cfr-noret.c"
+
+/* One out-of-line check before the noreturn call in f, and another at the end
+   of f.  */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* One inline check in h, before the noreturn call, and another in h2, before
+   or after the call, depending on noreturn detection.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
new file mode 100644
index 00000000000..a58afd7944c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-noret.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert checking before all noreturn calls.  */
+
+#ifndef ATTR_NOTHROW_OPT /* Overridden in harden-cfr-noret-noexcept.  */
+#define ATTR_NOTHROW_OPT __attribute__ ((__nothrow__))
+#endif
+
+extern void __attribute__ ((__noreturn__)) ATTR_NOTHROW_OPT g(void);
+
+void f(int i) {
+  if (i)
+    /* Out-of-line checks here...  */
+    g ();
+  /* ... and here.  */
+}
+
+void __attribute__ ((__noinline__, __noclone__))
+h(void) {
+  /* Inline check here.  */
+  g ();
+}
+
+#ifndef OMIT_H2 /* from harden-cfr-noret-never.  */
+void h2(void) {
+  /* Inline check either here, whether because of noreturn or tail call...  */
+  h ();
+  /* ... or here, if not optimizing.  */
+}
+#endif
+
+/* One out-of-line check before the noreturn call in f, and another at the end
+   of f.  */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* One inline check in h, before the noreturn call, and another in h2, before
+   or after the call, depending on noreturn detection.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
new file mode 100644
index 00000000000..17ea79f7cfb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr -ffat-lto-objects -O0" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, and also checking before noreturn
+   calls.  h2 and h2b get an extra resx without ehcleanup.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 16 "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
new file mode 100644
index 00000000000..dad9693e1d2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-always-no-nothrow.C
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that C++ does NOT make for implicit nothrow in noreturn
+   handling.  */
+
+#include "harden-cfr-noret-no-nothrow.C"
+
+/* 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 "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 "__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
new file mode 100644
index 00000000000..33e1ae26f80
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-never-no-nothrow.C
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that C++ does NOT make for implicit nothrow in noreturn
+   handling.  Expected results for =never and =nothrow are the same,
+   since the functions are not nothrow.  */
+
+#include "harden-cfr-noret-no-nothrow.C"
+
+/* 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 "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 "__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
new file mode 100644
index 00000000000..b47d880ada2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-noret-no-nothrow.C
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that C++ does NOT make for implicit nothrow in noreturn
+   handling.  */
+
+#define ATTR_NOTHROW_OPT
+
+#if ! __OPTIMIZE__
+void __attribute__ ((__noreturn__)) h (void);
+#endif
+
+#include "../../c-c++-common/torture/harden-cfr-noret.c"
+
+/* 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 "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 "__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
new file mode 100644
index 00000000000..52ef7bc601a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -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
+   calls.  */
+
+#if ! __OPTIMIZE__
+/* Without optimization, functions with cleanups end up with an extra
+   resx that is not optimized out, so arrange to optimize them.  */
+void __attribute__ ((__optimize__ (1))) h2(void);
+void __attribute__ ((__optimize__ (1))) h2b(void);
+#endif
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 14 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* h, h2, h2b, and h4.  */
+/* { dg-final { scan-tree-dump-times "Bypassing" 4 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
new file mode 100644
index 00000000000..da7c9cf1033
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -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
+   don't have noreturn checking enabled.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 0 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 6 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
new file mode 100644
index 00000000000..2dbc67c34d9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -0,0 +1,65 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions.  */
+
+extern void g (void);
+extern void g2 (void);
+
+void f(int i) {
+  if (i)
+    g ();
+  /* Out-of-line checks here, and in the implicit handler.  */
+}
+
+void f2(int i) {
+  if (i)
+    g ();
+  else
+    g2 ();
+  /* Out-of-line checks here, and in the implicit handler.  */
+}
+
+void h(void) {
+  try {
+    g ();
+  } catch (...) {
+    throw;
+  }
+  /* Out-of-line checks here, and in the implicit handler.  */
+}
+
+struct needs_cleanup {
+  ~needs_cleanup();
+};
+
+void h2(void) {
+  needs_cleanup y; /* No check in the cleanup handler.  */
+  g();
+  /* Out-of-line checks here, and in the implicit handler.  */
+}
+
+extern void __attribute__ ((__nothrow__)) another_cleanup (void*);
+
+void h2b(void) {
+  int x __attribute__ ((cleanup (another_cleanup)));
+  g();
+  /* Out-of-line checks here, and in the implicit handler.  */
+}
+
+void h3(void) {
+  try {
+    throw 1;
+  } catch (...) {
+  }
+  /* Out-of-line checks here, and in the implicit handler.  */
+}
+
+void h4(void) {
+  /* Inline check before the __cxa_throw noreturn call.  */
+  throw 1;
+}
+
+/* { 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/gcc.dg/torture/harden-cfr-noret-no-nothrow.c b/gcc/testsuite/gcc.dg/torture/harden-cfr-noret-no-nothrow.c
new file mode 100644
index 00000000000..8e4ee1fab08
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/harden-cfr-noret-no-nothrow.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that C makes for implicit nothrow in noreturn handling.  */
+
+#define ATTR_NOTHROW_OPT
+
+#include "../../c-c++-common/torture/harden-cfr-noret.c"
+
+/* One out-of-line check before the noreturn call in f, and another at the end
+   of f.  */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 2 "hardcfr" } } */
+/* One inline check in h, before the noreturn call, and another in h2, before
+   or after the call, depending on noreturn detection.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 2 "hardcfr" } } */
diff --git a/libgcc/hardcfr.c b/libgcc/hardcfr.c
index 8ef29428111..55f6b995f2f 100644
--- a/libgcc/hardcfr.c
+++ b/libgcc/hardcfr.c
@@ -60,14 +60,25 @@ extern void __hardcfr_check (size_t blocks,
 			     vword const *visited,
 			     vword const *cfg);
 
+/* Compute the MASK for the bit representing BLOCK in WORDIDX's vword in a
+   visited blocks bit array.  */
+static inline void
+block2mask (size_t const block, vword *const mask, size_t *const wordidx)
+{
+  size_t wbits = __CHAR_BIT__ * sizeof (vword);
+  *wordidx = block / wbits;
+  *mask = (vword)1 << (block % wbits);
+}
 
 /* Check whether the bit corresponding to BLOCK is set in VISITED.  */
 static inline bool
 visited_p (size_t const block, vword const *const visited)
 {
-  size_t wbits = __CHAR_BIT__ * sizeof (vword);
-  vword w = visited[block / wbits];
-  return (w & ((vword)1 << (block % wbits))) != 0;
+  vword mask;
+  size_t wordidx;
+  block2mask (block, &mask, &wordidx);
+  vword w = visited[wordidx];
+  return (w & mask) != 0;
 }
 
 /* Read and consume a mask from **CFG_IT.  (Consume meaning advancing the
@@ -118,19 +129,19 @@ consume_seq (vword const **const cfg_it)
    we reach the terminator without finding any.  Consume the entire sequence
    otherwise, so that *CFG_IT points just past the terminator, which may be the
    beginning of the next sequence.  */
-static inline void
+static inline bool
 check_seq (vword const *const visited, vword const **const cfg_it)
 {
   vword mask;
   size_t wordidx;
 
   /* If the block was visited, check that at least one of the
-     preds was also visited.  */
+     preds/succs was also visited.  */
   do
     /* If we get to the end of the sequence without finding any
        match, something is amiss.  */
     if (!next_pair (cfg_it, &mask, &wordidx))
-      __builtin_trap ();
+      return false;
   /* Keep searching until we find a match, at which point the
      condition is satisfied.  */
   while (!test_mask (visited, mask, wordidx));
@@ -139,6 +150,94 @@ check_seq (vword const *const visited, vword const **const cfg_it)
      skipped the block, so as to position the iterator at the beginning of the
      next .  */
   consume_seq (cfg_it);
+
+  return true;
+}
+
+/* Print out the CFG with BLOCKS blocks, presumed to be associated with CALLER.
+   This is expected to be optimized out entirely, unless the verbose part of
+   __hardcfr_check_fail is enabled.  */
+static inline void
+__hardcfr_debug_cfg (size_t const blocks,
+		     void const *const caller,
+		     vword const *const cfg)
+{
+  __builtin_printf ("CFG at %p, for %p", cfg, caller);
+  vword const *cfg_it = cfg;
+  for (size_t i = 0; i < blocks; i++)
+    {
+      vword mask; size_t wordidx;
+      block2mask (i, &mask, &wordidx);
+      __builtin_printf ("\nblock %lu (%lu/0x%lx)\npreds: ",
+			(unsigned long)i,
+			(unsigned long)wordidx, (unsigned long)mask);
+      while (next_pair (&cfg_it, &mask, &wordidx))
+	__builtin_printf (" (%lu/0x%lx)",
+			  (unsigned long)wordidx, (unsigned long)mask);
+      __builtin_printf ("\nsuccs: ");
+      while (next_pair (&cfg_it, &mask, &wordidx))
+	__builtin_printf (" (%lu/0x%lx)",
+			  (unsigned long)wordidx, (unsigned long)mask);
+    }
+  __builtin_printf ("\n");
+}
+
+#ifndef ATTRIBUTE_UNUSED
+# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+/* This is called when an out-of-line hardcfr check fails.  All the arguments
+   are ignored, and it just traps, unless HARDCFR_VERBOSE_FAIL is enabled.  IF
+   it is, it prints the PART of the CFG, expected to have BLOCKS blocks, that
+   failed at CALLER's BLOCK, and the VISITED bitmap.  When the verbose mode is
+   enabled, it also forces __hardcfr_debug_cfg (above) to be compiled into an
+   out-of-line function, that could be called from a debugger.
+   */
+static inline void
+__hardcfr_check_fail (size_t const blocks ATTRIBUTE_UNUSED,
+		      vword const *const visited,
+		      vword const *const cfg ATTRIBUTE_UNUSED,
+		      size_t const block ATTRIBUTE_UNUSED,
+		      int const part ATTRIBUTE_UNUSED,
+		      void const *const caller ATTRIBUTE_UNUSED)
+{
+#if HARDCFR_VERBOSE_FAIL
+  static const char *parts[] = { "preds", "succs" };
+
+  vword mask; size_t wordidx;
+  block2mask (block, &mask, &wordidx);
+  __builtin_printf ("hardcfr fail at %p block %lu (%lu/0x%lx), expected %s:",
+		    caller, (unsigned long)block,
+		    (unsigned long)wordidx, (unsigned long)mask,
+		    parts[part]);
+
+  /* Skip data for previous blocks.  */
+  vword const *cfg_it = cfg;
+  for (size_t i = block; i--; )
+    {
+      consume_seq (&cfg_it);
+      consume_seq (&cfg_it);
+    }
+  for (size_t i = part; i--; )
+    consume_seq (&cfg_it);
+
+  while (next_pair (&cfg_it, &mask, &wordidx))
+    __builtin_printf (" (%lu/0x%lx)",
+		      (unsigned long)wordidx, (unsigned long)mask);
+
+  __builtin_printf ("\nvisited:");
+  block2mask (blocks, &mask, &wordidx);
+  for (size_t i = 0; i <= wordidx; i++)
+    __builtin_printf (" (%lu/0x%lx)",
+		      (unsigned long)i, (unsigned long)visited[i]);
+  __builtin_printf ("\n");
+
+  /* Reference __hardcfr_debug_cfg so that it's output out-of-line, so that it
+     can be called from a debugger.  */
+  if (!caller || caller == __hardcfr_debug_cfg)
+    return;
+#endif
+  __builtin_trap ();
 }
 
 /* Check that, for each of the BLOCKS basic blocks, if its bit is set in
@@ -168,9 +267,13 @@ __hardcfr_check (size_t const blocks,
       else
 	{
 	  /* Check predecessors.  */
-	  check_seq (visited, &cfg_it);
+	  if (!check_seq (visited, &cfg_it))
+	    __hardcfr_check_fail (blocks, visited, cfg, i, 0,
+				  __builtin_return_address (0));
 	  /* Check successors.  */
-	  check_seq (visited, &cfg_it);
+	  if (!check_seq (visited, &cfg_it))
+	    __hardcfr_check_fail (blocks, visited, cfg, i, 1,
+				  __builtin_return_address (0));
 	}
     }
 }

             reply	other threads:[~2022-08-24 17:18 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-08-24 17:18 Alexandre Oliva [this message]
  -- strict thread matches above, loose matches on Subject: below --
2022-08-24 22:59 Alexandre Oliva
2022-08-24 22:46 Alexandre Oliva
2022-08-24 19:39 Alexandre Oliva
2022-08-24 16:53 Alexandre Oliva
2022-08-11  7:28 Alexandre Oliva
2022-08-11  7:13 Alexandre Oliva
2022-08-11  7:07 Alexandre Oliva
2022-08-11  6:54 Alexandre Oliva
2022-08-11  6:43 Alexandre Oliva
2022-08-11  5:45 Alexandre Oliva
2022-08-11  5:22 Alexandre Oliva
2022-08-11  5:20 Alexandre Oliva
2022-08-11  5:17 Alexandre Oliva
2022-08-11  5:08 Alexandre Oliva
2022-08-11  4:24 Alexandre Oliva
2022-08-11  4:13 Alexandre Oliva
2022-08-11  2:50 Alexandre Oliva
2022-08-11  2:34 Alexandre Oliva
2022-08-11  2:24 Alexandre Oliva
2022-08-11  2:20 Alexandre Oliva
2022-08-11  1:57 Alexandre Oliva

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220824171856.4E6AE3858C33@sourceware.org \
    --to=aoliva@gcc.gnu.org \
    --cc=gcc-cvs@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).