public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls
@ 2022-09-02 23:34 Alexandre Oliva
  0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Oliva @ 2022-09-02 23:34 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:6ae17fd3067c6741f9178e7a17da193190480608

commit 6ae17fd3067c6741f9178e7a17da193190480608
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Fri Sep 2 20:33:12 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  24 +-
 gcc/common.opt                                     |  29 ++
 gcc/doc/invoke.texi                                |  50 +-
 gcc/flag-types.h                                   |  10 +
 gcc/gimple-harden-control-flow.cc                  | 515 ++++++++++++++++++---
 .../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 ++
 .../c-c++-common/torture/harden-cfr-tail.c         |   2 +-
 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 +
 gcc/testsuite/gnat.dg/hardcfr.adb                  |   2 +-
 libgcc/hardcfr.c                                   | 119 ++++-
 21 files changed, 939 insertions(+), 86 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index f5fdc8e46b4..9d762e7c8cc 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,11 +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 returning.  Subprogram
-executions that complete by raising or propagating an exception bypass
-verification-and-return points.  A subprogram that can only complete
-by raising or propagating an exception may have instrumentation
-disabled altogether.
+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 661dcf2f485..455853301e3 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1795,6 +1795,35 @@ fharden-control-flow-redundancy
 Common Var(flag_harden_control_flow_redundancy) 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 3bec4b42437..c47cee28eaf 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -625,7 +625,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
@@ -16565,10 +16566,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, that they amount to an execution path that is
-consistent with the control flow graph, trapping otherwise.  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 d2e751060ff..3fae7548cab 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -157,6 +157,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 6b08846dbb1..6d03e24d38a 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -29,7 +29,12 @@ 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"
 #include "basic-block.h"
 #include "cfghooks.h"
 #include "cfgloop.h"
@@ -60,9 +65,7 @@ const pass_data pass_data_harden_control_flow_redundancy = {
   0,	    // properties_provided
   0,	    // properties_destroyed
   TODO_cleanup_cfg, // properties_start
-  TODO_update_ssa
-  | TODO_cleanup_cfg
-  | TODO_verify_il, // properties_finish
+  0,        // properties_finish
 };
 
 class pass_harden_control_flow_redundancy : public gimple_opt_pass
@@ -79,16 +82,6 @@ public:
     if (!flag_harden_control_flow_redundancy)
       return false;
 
-    /* We don't verify when an exception escapes, propagated or raised
-       by the function itself, so we're only concerned with edges to
-       the exit block.  If there aren't any, the function doesn't
-       return normally, so there won't be any checking point, so
-       there's no point in running the pass.  Should we add
-       verification at exception escapes, we should at least look at
-       !flag_exceptions here.  */
-    if (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-      return false;
-
     /* Functions that return more than once, like setjmp and vfork
        (that also gets this flag set), will start recording a path
        after the first return, and then may take another path when
@@ -117,8 +110,9 @@ public:
 	return false;
       }
 
-    if (param_hardcfr_max_blocks > 0
-	&& n_basic_blocks_for_fn (fun) - 2 > param_hardcfr_max_blocks)
+    if (fun->cfg && param_hardcfr_max_blocks > 0
+	&& (n_basic_blocks_for_fn (fun) - NUM_FIXED_BLOCKS
+	    > param_hardcfr_max_blocks))
       {
 	warning_at (DECL_SOURCE_LOCATION (fun->decl), 0,
 		    "%qD has more than %u blocks, the requested"
@@ -176,8 +170,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.  */
@@ -249,8 +243,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);
@@ -270,7 +263,7 @@ class rt_bb_visited
 
 public:
   /* Prepare to add control flow redundancy testing to CFUN.  */
-  rt_bb_visited ()
+  rt_bb_visited (int noreturn_blocks)
     : nblocks (n_basic_blocks_for_fn (cfun)),
       vword_type (NULL), ckseq (NULL), rtcfg (NULL)
   {
@@ -353,8 +346,9 @@ 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))
       {
 	/* Make sure vword_bits is wide enough for the representation
 	   of nblocks in rtcfg.  Compare with vword_bits << vword_bits,
@@ -379,12 +373,10 @@ public:
     gimple_seq_add_stmt (&ckseq, ckfail_init);
   }
 
-  /* 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 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)
   {
-    basic_block insbb = e->src;
-
     /* 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
@@ -401,43 +393,44 @@ public:
        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)
+    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;
+  }
+
+  /* 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)
+  {
+    if (!insert_exit_check (seq, e->src))
       gsi_insert_seq_on_edge_immediate (e, seq);
   }
 
   /* Add checking code on every exit edge, 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 ()
+  void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
   {
-    /* Insert initializers for visited at the entry.  */
-    gimple_seq iseq = NULL;
-
-    gcall *vinit = gimple_build_call (builtin_decl_explicit
-				      (BUILT_IN_MEMSET), 3,
-				      build1 (ADDR_EXPR,
-					      build_pointer_type
-					      (TREE_TYPE (visited)),
-					      visited),
-				      integer_zero_node,
-				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
-    gimple_seq_add_stmt (&iseq, vinit);
-
-    gsi_insert_seq_on_edge_immediate (single_succ_edge
-				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
-				      iseq);
-
     /* If we're using out-of-line checking, create and statically
        initialize the CFG checking representation, generate the
        checker call for the checking sequence, and insert it in all
@@ -506,20 +499,86 @@ public:
 	    gimple_seq seq = ckseq;
 	    /* Copy the sequence, unless we're dealing with the
 	       last edge (we're counting down to zero).  */
-	    if (i)
+	    if (i || count_noreturn)
+	      seq = gimple_seq_copy (seq);
+
+	    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));
+	  }
+
+	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);
 
-	    insert_exit_check (seq,
-			       EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i));
+	    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 ();
 	  }
+
+	gcc_checking_assert (count_noreturn == 0);
       }
     else
       {
 	/* 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.  */
@@ -540,8 +599,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));
@@ -564,6 +622,24 @@ public:
 	if (dom_info_available_p (CDI_DOMINATORS))
 	  set_immediate_dominator (CDI_DOMINATORS, trp, gimple_bb (last));
       }
+
+    /* Insert initializers for visited at the entry.  Do this after
+       other insertions, to avoid messing with block numbers.  */
+    gimple_seq iseq = NULL;
+
+    gcall *vinit = gimple_build_call (builtin_decl_explicit
+				      (BUILT_IN_MEMSET), 3,
+				      build1 (ADDR_EXPR,
+					      build_pointer_type
+					      (TREE_TYPE (visited)),
+					      visited),
+				      integer_zero_node,
+				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
+    gimple_seq_add_stmt (&iseq, vinit);
+
+    gsi_insert_seq_on_edge_immediate (single_succ_edge
+				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
+				      iseq);
   }
 
   /* Push onto RTCFG a (mask, index) pair to test for IBB when BB is
@@ -607,7 +683,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)
   {
@@ -633,7 +709,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);
@@ -653,10 +729,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
@@ -677,6 +756,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);
 
@@ -687,21 +768,323 @@ 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 *)
+pass_harden_control_flow_redundancy::execute (function *fun)
 {
-  rt_bb_visited vstd;
-
+  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;
-  FOR_EACH_BB_FN (bb, cfun)
-    vstd.visit (bb);
 
-  vstd.check ();
+  if (check_at_escaping_exceptions)
+    {
+      int lp_eh_cleanup = -1;
+
+      /* Record the preexisting blocks, to avoid visiting newly-created
+	 blocks.  */
+      auto_sbitmap to_visit (last_basic_block_for_fn (fun));
+      bitmap_clear (to_visit);
+
+      FOR_EACH_BB_FN (bb, fun)
+	bitmap_set_bit (to_visit, bb->index);
+
+      /* Scan the blocks for stmts with escaping exceptions, that
+	 wouldn't be denoted in the CFG, and associate them with an
+	 empty cleanup handler around the whole function.  Walk
+	 backwards, so that even when we split the block, */
+      sbitmap_iterator it;
+      unsigned i;
+      EXECUTE_IF_SET_IN_BITMAP (to_visit, 0, i, it)
+	{
+	  bb = BASIC_BLOCK_FOR_FN (fun, i);
+
+	  for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	       !gsi_end_p (gsi); gsi_prev (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (!stmt_could_throw_p (fun, stmt))
+		continue;
+
+	      /* If it must not throw, or if it already has a handler,
+		 we need not worry about it.  */
+	      if (lookup_stmt_eh_lp (stmt) != 0)
+		continue;
+
+	      /* 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)
+		{
+		  bb_eh_cleanup = create_empty_bb (bb);
+		  if (dom_info_available_p (CDI_DOMINATORS))
+		    set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+		  if (current_loops)
+		    add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+		  /* Make the new block an EH cleanup for the call.  */
+		  eh_region new_r = gen_eh_region_cleanup (NULL);
+		  eh_landing_pad lp = gen_eh_landing_pad (new_r);
+		  tree label = gimple_block_label (bb_eh_cleanup);
+		  lp->post_landing_pad = label;
+		  EH_LANDING_PAD_NR (label) = lp_eh_cleanup = lp->index;
+
+		  /* Just propagate the exception.
+		     We will later insert the verifier call.  */
+		  gimple_stmt_iterator ehgsi;
+		  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);
+		    }
+		}
+
+	      if (dump_file)
+		{
+		  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);
+	    }
+	}
+
+      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);
+	}
+    }
+
+  /* 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)
+      {
+	gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	if (gsi_end_p (gsi))
+	  continue;
+	gimple *stmt = gsi_stmt (gsi);
+
+	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;
+	  }
 
-  return 0;
+	/* 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
+		       || bitmap_bit_p (noreturn_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
+      && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
+    {
+      if (dump_file)
+	fprintf (dump_file,
+		 "Disabling CFR, no exit paths to check\n");
+
+      return 0;
+    }
+
+  rt_bb_visited vstd (count_noreturn);
+
+  /* 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);
+
+  return
+    TODO_update_ssa
+    | TODO_cleanup_cfg
+    | TODO_verify_il;
 }
 
 /* Instantiate a hardcfr pass.  */
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/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 40d76c5c163..09daa70fa3a 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.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-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
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/gcc/testsuite/gnat.dg/hardcfr.adb b/gcc/testsuite/gnat.dg/hardcfr.adb
index b578a913d75..abe1605c029 100644
--- a/gcc/testsuite/gnat.dg/hardcfr.adb
+++ b/gcc/testsuite/gnat.dg/hardcfr.adb
@@ -1,5 +1,5 @@
 --  { dg-do run }
---  { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param=hardcfr-max-blocks=22 --param=hardcfr-max-inline-blocks=12 -O0" }
+--  { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr --param=hardcfr-max-blocks=22 --param=hardcfr-max-inline-blocks=12 -O0" }
 
 procedure HardCFR is
    function F (I, J : Integer) return Integer is
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));
 	}
     }
 }

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls
@ 2022-08-27  2:55 Alexandre Oliva
  0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Oliva @ 2022-08-27  2:55 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:dcfcc38a9c68fc2f966340fdb73fa647e2206782

commit dcfcc38a9c68fc2f966340fdb73fa647e2206782
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Aug 24 19:57:59 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  24 +-
 gcc/common.opt                                     |  29 ++
 gcc/doc/invoke.texi                                |  50 +-
 gcc/flag-types.h                                   |  10 +
 gcc/gimple-harden-control-flow.cc                  | 515 ++++++++++++++++++---
 .../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 ++
 .../c-c++-common/torture/harden-cfr-tail.c         |   2 +-
 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 +
 gcc/testsuite/gnat.dg/hardcfr.adb                  |   2 +-
 libgcc/hardcfr.c                                   | 119 ++++-
 21 files changed, 939 insertions(+), 86 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index f5fdc8e46b4..9d762e7c8cc 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -263,11 +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 returning.  Subprogram
-executions that complete by raising or propagating an exception bypass
-verification-and-return points.  A subprogram that can only complete
-by raising or propagating an exception may have instrumentation
-disabled altogether.
+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 e347251f713..57f01330fcf 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1801,6 +1801,35 @@ fharden-control-flow-redundancy
 Common Var(flag_harden_control_flow_redundancy) 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 df626a0ea39..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,10 +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, that they amount to an execution path that is
-consistent with the control flow graph, trapping otherwise.  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..e06b01243bc 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 6b08846dbb1..6d03e24d38a 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -29,7 +29,12 @@ 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"
 #include "basic-block.h"
 #include "cfghooks.h"
 #include "cfgloop.h"
@@ -60,9 +65,7 @@ const pass_data pass_data_harden_control_flow_redundancy = {
   0,	    // properties_provided
   0,	    // properties_destroyed
   TODO_cleanup_cfg, // properties_start
-  TODO_update_ssa
-  | TODO_cleanup_cfg
-  | TODO_verify_il, // properties_finish
+  0,        // properties_finish
 };
 
 class pass_harden_control_flow_redundancy : public gimple_opt_pass
@@ -79,16 +82,6 @@ public:
     if (!flag_harden_control_flow_redundancy)
       return false;
 
-    /* We don't verify when an exception escapes, propagated or raised
-       by the function itself, so we're only concerned with edges to
-       the exit block.  If there aren't any, the function doesn't
-       return normally, so there won't be any checking point, so
-       there's no point in running the pass.  Should we add
-       verification at exception escapes, we should at least look at
-       !flag_exceptions here.  */
-    if (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-      return false;
-
     /* Functions that return more than once, like setjmp and vfork
        (that also gets this flag set), will start recording a path
        after the first return, and then may take another path when
@@ -117,8 +110,9 @@ public:
 	return false;
       }
 
-    if (param_hardcfr_max_blocks > 0
-	&& n_basic_blocks_for_fn (fun) - 2 > param_hardcfr_max_blocks)
+    if (fun->cfg && param_hardcfr_max_blocks > 0
+	&& (n_basic_blocks_for_fn (fun) - NUM_FIXED_BLOCKS
+	    > param_hardcfr_max_blocks))
       {
 	warning_at (DECL_SOURCE_LOCATION (fun->decl), 0,
 		    "%qD has more than %u blocks, the requested"
@@ -176,8 +170,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.  */
@@ -249,8 +243,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);
@@ -270,7 +263,7 @@ class rt_bb_visited
 
 public:
   /* Prepare to add control flow redundancy testing to CFUN.  */
-  rt_bb_visited ()
+  rt_bb_visited (int noreturn_blocks)
     : nblocks (n_basic_blocks_for_fn (cfun)),
       vword_type (NULL), ckseq (NULL), rtcfg (NULL)
   {
@@ -353,8 +346,9 @@ 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))
       {
 	/* Make sure vword_bits is wide enough for the representation
 	   of nblocks in rtcfg.  Compare with vword_bits << vword_bits,
@@ -379,12 +373,10 @@ public:
     gimple_seq_add_stmt (&ckseq, ckfail_init);
   }
 
-  /* 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 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)
   {
-    basic_block insbb = e->src;
-
     /* 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
@@ -401,43 +393,44 @@ public:
        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)
+    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;
+  }
+
+  /* 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)
+  {
+    if (!insert_exit_check (seq, e->src))
       gsi_insert_seq_on_edge_immediate (e, seq);
   }
 
   /* Add checking code on every exit edge, 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 ()
+  void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
   {
-    /* Insert initializers for visited at the entry.  */
-    gimple_seq iseq = NULL;
-
-    gcall *vinit = gimple_build_call (builtin_decl_explicit
-				      (BUILT_IN_MEMSET), 3,
-				      build1 (ADDR_EXPR,
-					      build_pointer_type
-					      (TREE_TYPE (visited)),
-					      visited),
-				      integer_zero_node,
-				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
-    gimple_seq_add_stmt (&iseq, vinit);
-
-    gsi_insert_seq_on_edge_immediate (single_succ_edge
-				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
-				      iseq);
-
     /* If we're using out-of-line checking, create and statically
        initialize the CFG checking representation, generate the
        checker call for the checking sequence, and insert it in all
@@ -506,20 +499,86 @@ public:
 	    gimple_seq seq = ckseq;
 	    /* Copy the sequence, unless we're dealing with the
 	       last edge (we're counting down to zero).  */
-	    if (i)
+	    if (i || count_noreturn)
+	      seq = gimple_seq_copy (seq);
+
+	    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));
+	  }
+
+	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);
 
-	    insert_exit_check (seq,
-			       EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i));
+	    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 ();
 	  }
+
+	gcc_checking_assert (count_noreturn == 0);
       }
     else
       {
 	/* 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.  */
@@ -540,8 +599,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));
@@ -564,6 +622,24 @@ public:
 	if (dom_info_available_p (CDI_DOMINATORS))
 	  set_immediate_dominator (CDI_DOMINATORS, trp, gimple_bb (last));
       }
+
+    /* Insert initializers for visited at the entry.  Do this after
+       other insertions, to avoid messing with block numbers.  */
+    gimple_seq iseq = NULL;
+
+    gcall *vinit = gimple_build_call (builtin_decl_explicit
+				      (BUILT_IN_MEMSET), 3,
+				      build1 (ADDR_EXPR,
+					      build_pointer_type
+					      (TREE_TYPE (visited)),
+					      visited),
+				      integer_zero_node,
+				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
+    gimple_seq_add_stmt (&iseq, vinit);
+
+    gsi_insert_seq_on_edge_immediate (single_succ_edge
+				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
+				      iseq);
   }
 
   /* Push onto RTCFG a (mask, index) pair to test for IBB when BB is
@@ -607,7 +683,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)
   {
@@ -633,7 +709,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);
@@ -653,10 +729,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
@@ -677,6 +756,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);
 
@@ -687,21 +768,323 @@ 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 *)
+pass_harden_control_flow_redundancy::execute (function *fun)
 {
-  rt_bb_visited vstd;
-
+  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;
-  FOR_EACH_BB_FN (bb, cfun)
-    vstd.visit (bb);
 
-  vstd.check ();
+  if (check_at_escaping_exceptions)
+    {
+      int lp_eh_cleanup = -1;
+
+      /* Record the preexisting blocks, to avoid visiting newly-created
+	 blocks.  */
+      auto_sbitmap to_visit (last_basic_block_for_fn (fun));
+      bitmap_clear (to_visit);
+
+      FOR_EACH_BB_FN (bb, fun)
+	bitmap_set_bit (to_visit, bb->index);
+
+      /* Scan the blocks for stmts with escaping exceptions, that
+	 wouldn't be denoted in the CFG, and associate them with an
+	 empty cleanup handler around the whole function.  Walk
+	 backwards, so that even when we split the block, */
+      sbitmap_iterator it;
+      unsigned i;
+      EXECUTE_IF_SET_IN_BITMAP (to_visit, 0, i, it)
+	{
+	  bb = BASIC_BLOCK_FOR_FN (fun, i);
+
+	  for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	       !gsi_end_p (gsi); gsi_prev (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (!stmt_could_throw_p (fun, stmt))
+		continue;
+
+	      /* If it must not throw, or if it already has a handler,
+		 we need not worry about it.  */
+	      if (lookup_stmt_eh_lp (stmt) != 0)
+		continue;
+
+	      /* 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)
+		{
+		  bb_eh_cleanup = create_empty_bb (bb);
+		  if (dom_info_available_p (CDI_DOMINATORS))
+		    set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+		  if (current_loops)
+		    add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+		  /* Make the new block an EH cleanup for the call.  */
+		  eh_region new_r = gen_eh_region_cleanup (NULL);
+		  eh_landing_pad lp = gen_eh_landing_pad (new_r);
+		  tree label = gimple_block_label (bb_eh_cleanup);
+		  lp->post_landing_pad = label;
+		  EH_LANDING_PAD_NR (label) = lp_eh_cleanup = lp->index;
+
+		  /* Just propagate the exception.
+		     We will later insert the verifier call.  */
+		  gimple_stmt_iterator ehgsi;
+		  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);
+		    }
+		}
+
+	      if (dump_file)
+		{
+		  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);
+	    }
+	}
+
+      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);
+	}
+    }
+
+  /* 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)
+      {
+	gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	if (gsi_end_p (gsi))
+	  continue;
+	gimple *stmt = gsi_stmt (gsi);
+
+	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;
+	  }
 
-  return 0;
+	/* 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
+		       || bitmap_bit_p (noreturn_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
+      && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
+    {
+      if (dump_file)
+	fprintf (dump_file,
+		 "Disabling CFR, no exit paths to check\n");
+
+      return 0;
+    }
+
+  rt_bb_visited vstd (count_noreturn);
+
+  /* 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);
+
+  return
+    TODO_update_ssa
+    | TODO_cleanup_cfg
+    | TODO_verify_il;
 }
 
 /* Instantiate a hardcfr pass.  */
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/c-c++-common/torture/harden-cfr-tail.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
index 40d76c5c163..09daa70fa3a 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-tail.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-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
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/gcc/testsuite/gnat.dg/hardcfr.adb b/gcc/testsuite/gnat.dg/hardcfr.adb
index b578a913d75..abe1605c029 100644
--- a/gcc/testsuite/gnat.dg/hardcfr.adb
+++ b/gcc/testsuite/gnat.dg/hardcfr.adb
@@ -1,5 +1,5 @@
 --  { dg-do run }
---  { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param=hardcfr-max-blocks=22 --param=hardcfr-max-inline-blocks=12 -O0" }
+--  { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions -fdump-tree-hardcfr --param=hardcfr-max-blocks=22 --param=hardcfr-max-inline-blocks=12 -O0" }
 
 procedure HardCFR is
    function F (I, J : Integer) return Integer is
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));
 	}
     }
 }

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls
@ 2022-08-24 22:59 Alexandre Oliva
  0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Oliva @ 2022-08-24 22:59 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:c7cc3fa861a35b4f67a6842143d817367c548c56

commit c7cc3fa861a35b4f67a6842143d817367c548c56
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Aug 24 19:57:59 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls

Diff:
---
 gcc/doc/invoke.texi               |   7 +-
 gcc/gimple-harden-control-flow.cc | 270 +++++++++++++++++++++++++++++++-------
 2 files changed, 226 insertions(+), 51 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index df626a0ea39..9c8f2eecb6c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16556,9 +16556,10 @@ 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, that they amount to an execution path that is
-consistent with the control flow graph, trapping otherwise.  Tuning
-options @option{--param hardcfr-max-blocks} and @option{--param
+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.
 
 @item -fstack-protector
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6b08846dbb1..00c84f6ada1 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -30,6 +30,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa.h"
 #include "gimple-iterator.h"
 #include "tree-cfg.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "sbitmap.h"
 #include "basic-block.h"
 #include "cfghooks.h"
 #include "cfgloop.h"
@@ -60,9 +63,7 @@ const pass_data pass_data_harden_control_flow_redundancy = {
   0,	    // properties_provided
   0,	    // properties_destroyed
   TODO_cleanup_cfg, // properties_start
-  TODO_update_ssa
-  | TODO_cleanup_cfg
-  | TODO_verify_il, // properties_finish
+  0,        // properties_finish
 };
 
 class pass_harden_control_flow_redundancy : public gimple_opt_pass
@@ -79,16 +80,6 @@ public:
     if (!flag_harden_control_flow_redundancy)
       return false;
 
-    /* We don't verify when an exception escapes, propagated or raised
-       by the function itself, so we're only concerned with edges to
-       the exit block.  If there aren't any, the function doesn't
-       return normally, so there won't be any checking point, so
-       there's no point in running the pass.  Should we add
-       verification at exception escapes, we should at least look at
-       !flag_exceptions here.  */
-    if (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-      return false;
-
     /* Functions that return more than once, like setjmp and vfork
        (that also gets this flag set), will start recording a path
        after the first return, and then may take another path when
@@ -270,7 +261,7 @@ class rt_bb_visited
 
 public:
   /* Prepare to add control flow redundancy testing to CFUN.  */
-  rt_bb_visited ()
+  rt_bb_visited (int noreturn_blocks)
     : nblocks (n_basic_blocks_for_fn (cfun)),
       vword_type (NULL), ckseq (NULL), rtcfg (NULL)
   {
@@ -354,7 +345,9 @@ public:
     gimple_seq_add_stmt (&ckseq, detach);
 
     if (nblocks - 2 > blknum (param_hardcfr_max_inline_blocks)
-	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun)))
+	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun))
+	|| (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
+	    + noreturn_blocks > 1))
       {
 	/* Make sure vword_bits is wide enough for the representation
 	   of nblocks in rtcfg.  Compare with vword_bits << vword_bits,
@@ -379,12 +372,10 @@ public:
     gimple_seq_add_stmt (&ckseq, ckfail_init);
   }
 
-  /* 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 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)
   {
-    basic_block insbb = e->src;
-
     /* 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
@@ -401,43 +392,44 @@ public:
        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)
+    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;
+  }
+
+  /* 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)
+  {
+    if (!insert_exit_check (seq, e->src))
       gsi_insert_seq_on_edge_immediate (e, seq);
   }
 
   /* Add checking code on every exit edge, 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 ()
+  void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
   {
-    /* Insert initializers for visited at the entry.  */
-    gimple_seq iseq = NULL;
-
-    gcall *vinit = gimple_build_call (builtin_decl_explicit
-				      (BUILT_IN_MEMSET), 3,
-				      build1 (ADDR_EXPR,
-					      build_pointer_type
-					      (TREE_TYPE (visited)),
-					      visited),
-				      integer_zero_node,
-				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
-    gimple_seq_add_stmt (&iseq, vinit);
-
-    gsi_insert_seq_on_edge_immediate (single_succ_edge
-				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
-				      iseq);
-
     /* If we're using out-of-line checking, create and statically
        initialize the CFG checking representation, generate the
        checker call for the checking sequence, and insert it in all
@@ -506,12 +498,32 @@ public:
 	    gimple_seq seq = ckseq;
 	    /* Copy the sequence, unless we're dealing with the
 	       last edge (we're counting down to zero).  */
-	    if (i)
+	    if (i || count_noreturn)
+	      seq = gimple_seq_copy (seq);
+
+	    edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+
+	    insert_exit_check (seq, e);
+
+	    gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+	  }
+
+	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);
 
-	    insert_exit_check (seq,
-			       EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i));
+	    if (!insert_exit_check (seq, bb))
+	      gcc_unreachable ();
 	  }
+
+	gcc_checking_assert (count_noreturn == 0);
       }
     else
       {
@@ -564,6 +576,24 @@ public:
 	if (dom_info_available_p (CDI_DOMINATORS))
 	  set_immediate_dominator (CDI_DOMINATORS, trp, gimple_bb (last));
       }
+
+    /* Insert initializers for visited at the entry.  Do this after
+       other insertions, to avoid messing with block numbers.  */
+    gimple_seq iseq = NULL;
+
+    gcall *vinit = gimple_build_call (builtin_decl_explicit
+				      (BUILT_IN_MEMSET), 3,
+				      build1 (ADDR_EXPR,
+					      build_pointer_type
+					      (TREE_TYPE (visited)),
+					      visited),
+				      integer_zero_node,
+				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
+    gimple_seq_add_stmt (&iseq, vinit);
+
+    gsi_insert_seq_on_edge_immediate (single_succ_edge
+				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
+				      iseq);
   }
 
   /* Push onto RTCFG a (mask, index) pair to test for IBB when BB is
@@ -691,17 +721,161 @@ public:
    verify at exit that an expect path was taken.  */
 
 unsigned int
-pass_harden_control_flow_redundancy::execute (function *)
+pass_harden_control_flow_redundancy::execute (function *fun)
 {
-  rt_bb_visited vstd;
-
+  basic_block bb_eh_cleanup = NULL;
   basic_block bb;
-  FOR_EACH_BB_FN (bb, cfun)
+
+  if (flag_exceptions)
+    {
+      int lp_eh_cleanup = -1;
+
+      /* Record the preexisting blocks, to avoid visiting newly-created
+	 blocks.  */
+      auto_sbitmap to_visit (last_basic_block_for_fn (fun));
+      bitmap_clear (to_visit);
+
+      FOR_EACH_BB_FN (bb, fun)
+	bitmap_set_bit (to_visit, bb->index);
+
+      /* Scan the blocks for stmts with escaping exceptions, that
+	 wouldn't be denoted in the CFG, and associate them with an
+	 empty cleanup handler around the whole function.  Walk
+	 backwards, so that even when we split the block, */
+      sbitmap_iterator it;
+      unsigned i;
+      EXECUTE_IF_SET_IN_BITMAP (to_visit, 0, i, it)
+	{
+	  bb = BASIC_BLOCK_FOR_FN (fun, i);
+
+	  for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	       !gsi_end_p (gsi); gsi_prev (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (!gimple_could_trap_p (stmt))
+		continue;
+
+	      /* If it must not throw, or if it already has a handler,
+		 we need not worry about it.  */
+	      if (lookup_stmt_eh_lp (stmt) != 0)
+		continue;
+
+	      if (!stmt_ends_bb_p (stmt))
+		split_block (bb, stmt);
+
+	      if (!bb_eh_cleanup)
+		{
+		  bb_eh_cleanup = create_empty_bb (bb);
+		  if (dom_info_available_p (CDI_DOMINATORS))
+		    set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+		  if (current_loops)
+		    add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+		  /* Make the new block an EH cleanup for the call.  */
+		  eh_region new_r = gen_eh_region_cleanup (NULL);
+		  eh_landing_pad lp = gen_eh_landing_pad (new_r);
+		  tree label = gimple_block_label (bb_eh_cleanup);
+		  lp->post_landing_pad = label;
+		  EH_LANDING_PAD_NR (label) = lp_eh_cleanup = lp->index;
+
+		  /* Just propagate the exception.
+		     We will later insert the verifier call.  */
+		  gimple_stmt_iterator ehgsi;
+		  ehgsi = gsi_after_labels (bb_eh_cleanup);
+		  gresx *resx = gimple_build_resx (new_r->index);
+		  gsi_insert_before (&ehgsi, resx, GSI_SAME_STMT);
+		}
+	      else
+		{
+		  // Update immedite dominator and loop?
+		}
+
+	      add_stmt_to_eh_lp (stmt, lp_eh_cleanup);
+	      /* Finally, wire the EH cleanup block into the CFG.  */
+	      make_eh_edges (stmt);		}
+	}
+    }
+
+  /* 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);
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      if (EDGE_COUNT (bb->succs) == 0)
+	{
+	  if (bitmap_set_bit (noreturn_blocks, bb->index))
+	    count_noreturn++;
+	  continue;
+	}
+
+      /* 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))
+	    continue;
+	}
+
+      if (bitmap_set_bit (noreturn_blocks, bb->index))
+	count_noreturn++;
+    }
+
+  gcc_checking_assert (!bb_eh_cleanup
+		       || bitmap_bit_p (noreturn_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
+      && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
+    return 0;
+
+  rt_bb_visited vstd (count_noreturn);
+
+  FOR_EACH_BB_FN (bb, fun)
     vstd.visit (bb);
 
-  vstd.check ();
+  vstd.check (count_noreturn, noreturn_blocks);
 
-  return 0;
+  return
+    TODO_update_ssa
+    | TODO_cleanup_cfg
+    | TODO_verify_il;
 }
 
 /* Instantiate a hardcfr pass.  */

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls
@ 2022-08-24 22:45 Alexandre Oliva
  0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Oliva @ 2022-08-24 22:45 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:3b796f3030192c8f59fb7bd3983e1d46ea503343

commit 3b796f3030192c8f59fb7bd3983e1d46ea503343
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Aug 24 13:36:59 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls

Diff:
---
 gcc/doc/invoke.texi               |   7 +-
 gcc/gimple-harden-control-flow.cc | 270 +++++++++++++++++++++++++++++++-------
 2 files changed, 226 insertions(+), 51 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index df626a0ea39..9c8f2eecb6c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16556,9 +16556,10 @@ 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, that they amount to an execution path that is
-consistent with the control flow graph, trapping otherwise.  Tuning
-options @option{--param hardcfr-max-blocks} and @option{--param
+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.
 
 @item -fstack-protector
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6b08846dbb1..00c84f6ada1 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -30,6 +30,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa.h"
 #include "gimple-iterator.h"
 #include "tree-cfg.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "sbitmap.h"
 #include "basic-block.h"
 #include "cfghooks.h"
 #include "cfgloop.h"
@@ -60,9 +63,7 @@ const pass_data pass_data_harden_control_flow_redundancy = {
   0,	    // properties_provided
   0,	    // properties_destroyed
   TODO_cleanup_cfg, // properties_start
-  TODO_update_ssa
-  | TODO_cleanup_cfg
-  | TODO_verify_il, // properties_finish
+  0,        // properties_finish
 };
 
 class pass_harden_control_flow_redundancy : public gimple_opt_pass
@@ -79,16 +80,6 @@ public:
     if (!flag_harden_control_flow_redundancy)
       return false;
 
-    /* We don't verify when an exception escapes, propagated or raised
-       by the function itself, so we're only concerned with edges to
-       the exit block.  If there aren't any, the function doesn't
-       return normally, so there won't be any checking point, so
-       there's no point in running the pass.  Should we add
-       verification at exception escapes, we should at least look at
-       !flag_exceptions here.  */
-    if (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-      return false;
-
     /* Functions that return more than once, like setjmp and vfork
        (that also gets this flag set), will start recording a path
        after the first return, and then may take another path when
@@ -270,7 +261,7 @@ class rt_bb_visited
 
 public:
   /* Prepare to add control flow redundancy testing to CFUN.  */
-  rt_bb_visited ()
+  rt_bb_visited (int noreturn_blocks)
     : nblocks (n_basic_blocks_for_fn (cfun)),
       vword_type (NULL), ckseq (NULL), rtcfg (NULL)
   {
@@ -354,7 +345,9 @@ public:
     gimple_seq_add_stmt (&ckseq, detach);
 
     if (nblocks - 2 > blknum (param_hardcfr_max_inline_blocks)
-	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun)))
+	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun))
+	|| (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
+	    + noreturn_blocks > 1))
       {
 	/* Make sure vword_bits is wide enough for the representation
 	   of nblocks in rtcfg.  Compare with vword_bits << vword_bits,
@@ -379,12 +372,10 @@ public:
     gimple_seq_add_stmt (&ckseq, ckfail_init);
   }
 
-  /* 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 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)
   {
-    basic_block insbb = e->src;
-
     /* 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
@@ -401,43 +392,44 @@ public:
        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)
+    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;
+  }
+
+  /* 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)
+  {
+    if (!insert_exit_check (seq, e->src))
       gsi_insert_seq_on_edge_immediate (e, seq);
   }
 
   /* Add checking code on every exit edge, 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 ()
+  void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
   {
-    /* Insert initializers for visited at the entry.  */
-    gimple_seq iseq = NULL;
-
-    gcall *vinit = gimple_build_call (builtin_decl_explicit
-				      (BUILT_IN_MEMSET), 3,
-				      build1 (ADDR_EXPR,
-					      build_pointer_type
-					      (TREE_TYPE (visited)),
-					      visited),
-				      integer_zero_node,
-				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
-    gimple_seq_add_stmt (&iseq, vinit);
-
-    gsi_insert_seq_on_edge_immediate (single_succ_edge
-				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
-				      iseq);
-
     /* If we're using out-of-line checking, create and statically
        initialize the CFG checking representation, generate the
        checker call for the checking sequence, and insert it in all
@@ -506,12 +498,32 @@ public:
 	    gimple_seq seq = ckseq;
 	    /* Copy the sequence, unless we're dealing with the
 	       last edge (we're counting down to zero).  */
-	    if (i)
+	    if (i || count_noreturn)
+	      seq = gimple_seq_copy (seq);
+
+	    edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+
+	    insert_exit_check (seq, e);
+
+	    gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+	  }
+
+	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);
 
-	    insert_exit_check (seq,
-			       EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i));
+	    if (!insert_exit_check (seq, bb))
+	      gcc_unreachable ();
 	  }
+
+	gcc_checking_assert (count_noreturn == 0);
       }
     else
       {
@@ -564,6 +576,24 @@ public:
 	if (dom_info_available_p (CDI_DOMINATORS))
 	  set_immediate_dominator (CDI_DOMINATORS, trp, gimple_bb (last));
       }
+
+    /* Insert initializers for visited at the entry.  Do this after
+       other insertions, to avoid messing with block numbers.  */
+    gimple_seq iseq = NULL;
+
+    gcall *vinit = gimple_build_call (builtin_decl_explicit
+				      (BUILT_IN_MEMSET), 3,
+				      build1 (ADDR_EXPR,
+					      build_pointer_type
+					      (TREE_TYPE (visited)),
+					      visited),
+				      integer_zero_node,
+				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
+    gimple_seq_add_stmt (&iseq, vinit);
+
+    gsi_insert_seq_on_edge_immediate (single_succ_edge
+				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
+				      iseq);
   }
 
   /* Push onto RTCFG a (mask, index) pair to test for IBB when BB is
@@ -691,17 +721,161 @@ public:
    verify at exit that an expect path was taken.  */
 
 unsigned int
-pass_harden_control_flow_redundancy::execute (function *)
+pass_harden_control_flow_redundancy::execute (function *fun)
 {
-  rt_bb_visited vstd;
-
+  basic_block bb_eh_cleanup = NULL;
   basic_block bb;
-  FOR_EACH_BB_FN (bb, cfun)
+
+  if (flag_exceptions)
+    {
+      int lp_eh_cleanup = -1;
+
+      /* Record the preexisting blocks, to avoid visiting newly-created
+	 blocks.  */
+      auto_sbitmap to_visit (last_basic_block_for_fn (fun));
+      bitmap_clear (to_visit);
+
+      FOR_EACH_BB_FN (bb, fun)
+	bitmap_set_bit (to_visit, bb->index);
+
+      /* Scan the blocks for stmts with escaping exceptions, that
+	 wouldn't be denoted in the CFG, and associate them with an
+	 empty cleanup handler around the whole function.  Walk
+	 backwards, so that even when we split the block, */
+      sbitmap_iterator it;
+      unsigned i;
+      EXECUTE_IF_SET_IN_BITMAP (to_visit, 0, i, it)
+	{
+	  bb = BASIC_BLOCK_FOR_FN (fun, i);
+
+	  for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	       !gsi_end_p (gsi); gsi_prev (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (!gimple_could_trap_p (stmt))
+		continue;
+
+	      /* If it must not throw, or if it already has a handler,
+		 we need not worry about it.  */
+	      if (lookup_stmt_eh_lp (stmt) != 0)
+		continue;
+
+	      if (!stmt_ends_bb_p (stmt))
+		split_block (bb, stmt);
+
+	      if (!bb_eh_cleanup)
+		{
+		  bb_eh_cleanup = create_empty_bb (bb);
+		  if (dom_info_available_p (CDI_DOMINATORS))
+		    set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+		  if (current_loops)
+		    add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+		  /* Make the new block an EH cleanup for the call.  */
+		  eh_region new_r = gen_eh_region_cleanup (NULL);
+		  eh_landing_pad lp = gen_eh_landing_pad (new_r);
+		  tree label = gimple_block_label (bb_eh_cleanup);
+		  lp->post_landing_pad = label;
+		  EH_LANDING_PAD_NR (label) = lp_eh_cleanup = lp->index;
+
+		  /* Just propagate the exception.
+		     We will later insert the verifier call.  */
+		  gimple_stmt_iterator ehgsi;
+		  ehgsi = gsi_after_labels (bb_eh_cleanup);
+		  gresx *resx = gimple_build_resx (new_r->index);
+		  gsi_insert_before (&ehgsi, resx, GSI_SAME_STMT);
+		}
+	      else
+		{
+		  // Update immedite dominator and loop?
+		}
+
+	      add_stmt_to_eh_lp (stmt, lp_eh_cleanup);
+	      /* Finally, wire the EH cleanup block into the CFG.  */
+	      make_eh_edges (stmt);		}
+	}
+    }
+
+  /* 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);
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      if (EDGE_COUNT (bb->succs) == 0)
+	{
+	  if (bitmap_set_bit (noreturn_blocks, bb->index))
+	    count_noreturn++;
+	  continue;
+	}
+
+      /* 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))
+	    continue;
+	}
+
+      if (bitmap_set_bit (noreturn_blocks, bb->index))
+	count_noreturn++;
+    }
+
+  gcc_checking_assert (!bb_eh_cleanup
+		       || bitmap_bit_p (noreturn_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
+      && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
+    return 0;
+
+  rt_bb_visited vstd (count_noreturn);
+
+  FOR_EACH_BB_FN (bb, fun)
     vstd.visit (bb);
 
-  vstd.check ();
+  vstd.check (count_noreturn, noreturn_blocks);
 
-  return 0;
+  return
+    TODO_update_ssa
+    | TODO_cleanup_cfg
+    | TODO_verify_il;
 }
 
 /* Instantiate a hardcfr pass.  */

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls
@ 2022-08-24 19:39 Alexandre Oliva
  0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Oliva @ 2022-08-24 19:39 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:976f11002f8d6142f12fe3e5755d79aba554fe6d

commit 976f11002f8d6142f12fe3e5755d79aba554fe6d
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Aug 24 13:36:59 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls

Diff:
---
 gcc/doc/invoke.texi               |   7 +-
 gcc/gimple-harden-control-flow.cc | 270 +++++++++++++++++++++++++++++++-------
 2 files changed, 226 insertions(+), 51 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index df626a0ea39..9c8f2eecb6c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16556,9 +16556,10 @@ 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, that they amount to an execution path that is
-consistent with the control flow graph, trapping otherwise.  Tuning
-options @option{--param hardcfr-max-blocks} and @option{--param
+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.
 
 @item -fstack-protector
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 6b08846dbb1..00c84f6ada1 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -30,6 +30,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa.h"
 #include "gimple-iterator.h"
 #include "tree-cfg.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "sbitmap.h"
 #include "basic-block.h"
 #include "cfghooks.h"
 #include "cfgloop.h"
@@ -60,9 +63,7 @@ const pass_data pass_data_harden_control_flow_redundancy = {
   0,	    // properties_provided
   0,	    // properties_destroyed
   TODO_cleanup_cfg, // properties_start
-  TODO_update_ssa
-  | TODO_cleanup_cfg
-  | TODO_verify_il, // properties_finish
+  0,        // properties_finish
 };
 
 class pass_harden_control_flow_redundancy : public gimple_opt_pass
@@ -79,16 +80,6 @@ public:
     if (!flag_harden_control_flow_redundancy)
       return false;
 
-    /* We don't verify when an exception escapes, propagated or raised
-       by the function itself, so we're only concerned with edges to
-       the exit block.  If there aren't any, the function doesn't
-       return normally, so there won't be any checking point, so
-       there's no point in running the pass.  Should we add
-       verification at exception escapes, we should at least look at
-       !flag_exceptions here.  */
-    if (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-      return false;
-
     /* Functions that return more than once, like setjmp and vfork
        (that also gets this flag set), will start recording a path
        after the first return, and then may take another path when
@@ -270,7 +261,7 @@ class rt_bb_visited
 
 public:
   /* Prepare to add control flow redundancy testing to CFUN.  */
-  rt_bb_visited ()
+  rt_bb_visited (int noreturn_blocks)
     : nblocks (n_basic_blocks_for_fn (cfun)),
       vword_type (NULL), ckseq (NULL), rtcfg (NULL)
   {
@@ -354,7 +345,9 @@ public:
     gimple_seq_add_stmt (&ckseq, detach);
 
     if (nblocks - 2 > blknum (param_hardcfr_max_inline_blocks)
-	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun)))
+	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun))
+	|| (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
+	    + noreturn_blocks > 1))
       {
 	/* Make sure vword_bits is wide enough for the representation
 	   of nblocks in rtcfg.  Compare with vword_bits << vword_bits,
@@ -379,12 +372,10 @@ public:
     gimple_seq_add_stmt (&ckseq, ckfail_init);
   }
 
-  /* 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 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)
   {
-    basic_block insbb = e->src;
-
     /* 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
@@ -401,43 +392,44 @@ public:
        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)
+    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;
+  }
+
+  /* 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)
+  {
+    if (!insert_exit_check (seq, e->src))
       gsi_insert_seq_on_edge_immediate (e, seq);
   }
 
   /* Add checking code on every exit edge, 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 ()
+  void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
   {
-    /* Insert initializers for visited at the entry.  */
-    gimple_seq iseq = NULL;
-
-    gcall *vinit = gimple_build_call (builtin_decl_explicit
-				      (BUILT_IN_MEMSET), 3,
-				      build1 (ADDR_EXPR,
-					      build_pointer_type
-					      (TREE_TYPE (visited)),
-					      visited),
-				      integer_zero_node,
-				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
-    gimple_seq_add_stmt (&iseq, vinit);
-
-    gsi_insert_seq_on_edge_immediate (single_succ_edge
-				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
-				      iseq);
-
     /* If we're using out-of-line checking, create and statically
        initialize the CFG checking representation, generate the
        checker call for the checking sequence, and insert it in all
@@ -506,12 +498,32 @@ public:
 	    gimple_seq seq = ckseq;
 	    /* Copy the sequence, unless we're dealing with the
 	       last edge (we're counting down to zero).  */
-	    if (i)
+	    if (i || count_noreturn)
+	      seq = gimple_seq_copy (seq);
+
+	    edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+
+	    insert_exit_check (seq, e);
+
+	    gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+	  }
+
+	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);
 
-	    insert_exit_check (seq,
-			       EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i));
+	    if (!insert_exit_check (seq, bb))
+	      gcc_unreachable ();
 	  }
+
+	gcc_checking_assert (count_noreturn == 0);
       }
     else
       {
@@ -564,6 +576,24 @@ public:
 	if (dom_info_available_p (CDI_DOMINATORS))
 	  set_immediate_dominator (CDI_DOMINATORS, trp, gimple_bb (last));
       }
+
+    /* Insert initializers for visited at the entry.  Do this after
+       other insertions, to avoid messing with block numbers.  */
+    gimple_seq iseq = NULL;
+
+    gcall *vinit = gimple_build_call (builtin_decl_explicit
+				      (BUILT_IN_MEMSET), 3,
+				      build1 (ADDR_EXPR,
+					      build_pointer_type
+					      (TREE_TYPE (visited)),
+					      visited),
+				      integer_zero_node,
+				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
+    gimple_seq_add_stmt (&iseq, vinit);
+
+    gsi_insert_seq_on_edge_immediate (single_succ_edge
+				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
+				      iseq);
   }
 
   /* Push onto RTCFG a (mask, index) pair to test for IBB when BB is
@@ -691,17 +721,161 @@ public:
    verify at exit that an expect path was taken.  */
 
 unsigned int
-pass_harden_control_flow_redundancy::execute (function *)
+pass_harden_control_flow_redundancy::execute (function *fun)
 {
-  rt_bb_visited vstd;
-
+  basic_block bb_eh_cleanup = NULL;
   basic_block bb;
-  FOR_EACH_BB_FN (bb, cfun)
+
+  if (flag_exceptions)
+    {
+      int lp_eh_cleanup = -1;
+
+      /* Record the preexisting blocks, to avoid visiting newly-created
+	 blocks.  */
+      auto_sbitmap to_visit (last_basic_block_for_fn (fun));
+      bitmap_clear (to_visit);
+
+      FOR_EACH_BB_FN (bb, fun)
+	bitmap_set_bit (to_visit, bb->index);
+
+      /* Scan the blocks for stmts with escaping exceptions, that
+	 wouldn't be denoted in the CFG, and associate them with an
+	 empty cleanup handler around the whole function.  Walk
+	 backwards, so that even when we split the block, */
+      sbitmap_iterator it;
+      unsigned i;
+      EXECUTE_IF_SET_IN_BITMAP (to_visit, 0, i, it)
+	{
+	  bb = BASIC_BLOCK_FOR_FN (fun, i);
+
+	  for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	       !gsi_end_p (gsi); gsi_prev (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (!gimple_could_trap_p (stmt))
+		continue;
+
+	      /* If it must not throw, or if it already has a handler,
+		 we need not worry about it.  */
+	      if (lookup_stmt_eh_lp (stmt) != 0)
+		continue;
+
+	      if (!stmt_ends_bb_p (stmt))
+		split_block (bb, stmt);
+
+	      if (!bb_eh_cleanup)
+		{
+		  bb_eh_cleanup = create_empty_bb (bb);
+		  if (dom_info_available_p (CDI_DOMINATORS))
+		    set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+		  if (current_loops)
+		    add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+		  /* Make the new block an EH cleanup for the call.  */
+		  eh_region new_r = gen_eh_region_cleanup (NULL);
+		  eh_landing_pad lp = gen_eh_landing_pad (new_r);
+		  tree label = gimple_block_label (bb_eh_cleanup);
+		  lp->post_landing_pad = label;
+		  EH_LANDING_PAD_NR (label) = lp_eh_cleanup = lp->index;
+
+		  /* Just propagate the exception.
+		     We will later insert the verifier call.  */
+		  gimple_stmt_iterator ehgsi;
+		  ehgsi = gsi_after_labels (bb_eh_cleanup);
+		  gresx *resx = gimple_build_resx (new_r->index);
+		  gsi_insert_before (&ehgsi, resx, GSI_SAME_STMT);
+		}
+	      else
+		{
+		  // Update immedite dominator and loop?
+		}
+
+	      add_stmt_to_eh_lp (stmt, lp_eh_cleanup);
+	      /* Finally, wire the EH cleanup block into the CFG.  */
+	      make_eh_edges (stmt);		}
+	}
+    }
+
+  /* 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);
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      if (EDGE_COUNT (bb->succs) == 0)
+	{
+	  if (bitmap_set_bit (noreturn_blocks, bb->index))
+	    count_noreturn++;
+	  continue;
+	}
+
+      /* 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))
+	    continue;
+	}
+
+      if (bitmap_set_bit (noreturn_blocks, bb->index))
+	count_noreturn++;
+    }
+
+  gcc_checking_assert (!bb_eh_cleanup
+		       || bitmap_bit_p (noreturn_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
+      && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
+    return 0;
+
+  rt_bb_visited vstd (count_noreturn);
+
+  FOR_EACH_BB_FN (bb, fun)
     vstd.visit (bb);
 
-  vstd.check ();
+  vstd.check (count_noreturn, noreturn_blocks);
 
-  return 0;
+  return
+    TODO_update_ssa
+    | TODO_cleanup_cfg
+    | TODO_verify_il;
 }
 
 /* Instantiate a hardcfr pass.  */

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls
@ 2022-08-24 16:52 Alexandre Oliva
  0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Oliva @ 2022-08-24 16:52 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:88092167547fe32bb3ac36239d8643cd63ea8f9c

commit 88092167547fe32bb3ac36239d8643cd63ea8f9c
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Aug 24 13:36:59 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls

Diff:
---
 gcc/doc/invoke.texi               |   7 +-
 gcc/gimple-harden-control-flow.cc | 270 +++++++++++++++++++++++++++++++-------
 2 files changed, 226 insertions(+), 51 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index df626a0ea39..9c8f2eecb6c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16556,9 +16556,10 @@ 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, that they amount to an execution path that is
-consistent with the control flow graph, trapping otherwise.  Tuning
-options @option{--param hardcfr-max-blocks} and @option{--param
+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.
 
 @item -fstack-protector
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 8932d548a20..5066a43fe6c 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -30,6 +30,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa.h"
 #include "gimple-iterator.h"
 #include "tree-cfg.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "sbitmap.h"
 #include "basic-block.h"
 #include "cfghooks.h"
 #include "cfgloop.h"
@@ -60,9 +63,7 @@ const pass_data pass_data_harden_control_flow_redundancy = {
   0,	    // properties_provided
   0,	    // properties_destroyed
   TODO_cleanup_cfg, // properties_start
-  TODO_update_ssa
-  | TODO_cleanup_cfg
-  | TODO_verify_il, // properties_finish
+  0,        // properties_finish
 };
 
 class pass_harden_control_flow_redundancy : public gimple_opt_pass
@@ -79,16 +80,6 @@ public:
     if (!flag_harden_control_flow_redundancy)
       return false;
 
-    /* We don't verify when an exception escapes, propagated or raised
-       by the function itself, so we're only concerned with edges to
-       the exit block.  If there aren't any, the function doesn't
-       return normally, so there won't be any checking point, so
-       there's no point in running the pass.  Should we add
-       verification at exception escapes, we should at least look at
-       !flag_exceptions here.  */
-    if (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-      return false;
-
     /* Functions that return more than once, like setjmp and vfork
        (that also gets this flag set), will start recording a path
        after the first return, and then may take another path when
@@ -276,7 +267,7 @@ class rt_bb_visited
 
 public:
   /* Prepare to add control flow redundancy testing to CFUN.  */
-  rt_bb_visited ()
+  rt_bb_visited (int noreturn_blocks)
     : nblocks (n_basic_blocks_for_fn (cfun)),
       vword_type (NULL), ckseq (NULL), rtcfg (NULL)
   {
@@ -360,7 +351,9 @@ public:
     gimple_seq_add_stmt (&ckseq, detach);
 
     if (nblocks - 2 > blknum (param_hardcfr_max_inline_blocks)
-	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun)))
+	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun))
+	|| (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
+	    + noreturn_blocks > 1))
       {
 	/* Make sure vword_bits is wide enough for the representation
 	   of nblocks in rtcfg.  Compare with vword_bits << vword_bits,
@@ -385,12 +378,10 @@ public:
     gimple_seq_add_stmt (&ckseq, ckfail_init);
   }
 
-  /* 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 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)
   {
-    basic_block insbb = e->src;
-
     /* 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
@@ -407,43 +398,44 @@ public:
        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)
+    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;
+  }
+
+  /* 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)
+  {
+    if (!insert_exit_check (seq, e->src))
       gsi_insert_seq_on_edge_immediate (e, seq);
   }
 
   /* Add checking code on every exit edge, 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 ()
+  void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
   {
-    /* Insert initializers for visited at the entry.  */
-    gimple_seq iseq = NULL;
-
-    gcall *vinit = gimple_build_call (builtin_decl_explicit
-				      (BUILT_IN_MEMSET), 3,
-				      build1 (ADDR_EXPR,
-					      build_pointer_type
-					      (TREE_TYPE (visited)),
-					      visited),
-				      integer_zero_node,
-				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
-    gimple_seq_add_stmt (&iseq, vinit);
-
-    gsi_insert_seq_on_edge_immediate (single_succ_edge
-				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
-				      iseq);
-
     /* If we're using out-of-line checking, create and statically
        initialize the CFG checking representation, generate the
        checker call for the checking sequence, and insert it in all
@@ -512,12 +504,32 @@ public:
 	    gimple_seq seq = ckseq;
 	    /* Copy the sequence, unless we're dealing with the
 	       last edge (we're counting down to zero).  */
-	    if (i)
+	    if (i || count_noreturn)
+	      seq = gimple_seq_copy (seq);
+
+	    edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+
+	    insert_exit_check (seq, e);
+
+	    gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+	  }
+
+	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);
 
-	    insert_exit_check (seq,
-			       EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i));
+	    if (!insert_exit_check (seq, bb))
+	      gcc_unreachable ();
 	  }
+
+	gcc_checking_assert (count_noreturn == 0);
       }
     else
       {
@@ -570,6 +582,24 @@ public:
 	if (dom_info_available_p (CDI_DOMINATORS))
 	  set_immediate_dominator (CDI_DOMINATORS, trp, gimple_bb (last));
       }
+
+    /* Insert initializers for visited at the entry.  Do this after
+       other insertions, to avoid messing with block numbers.  */
+    gimple_seq iseq = NULL;
+
+    gcall *vinit = gimple_build_call (builtin_decl_explicit
+				      (BUILT_IN_MEMSET), 3,
+				      build1 (ADDR_EXPR,
+					      build_pointer_type
+					      (TREE_TYPE (visited)),
+					      visited),
+				      integer_zero_node,
+				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
+    gimple_seq_add_stmt (&iseq, vinit);
+
+    gsi_insert_seq_on_edge_immediate (single_succ_edge
+				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
+				      iseq);
   }
 
   /* Push onto RTCFG a (mask, index) pair to test for IBB when BB is
@@ -697,17 +727,161 @@ public:
    verify at exit that an expect path was taken.  */
 
 unsigned int
-pass_harden_control_flow_redundancy::execute (function *)
+pass_harden_control_flow_redundancy::execute (function *fun)
 {
-  rt_bb_visited vstd;
-
+  basic_block bb_eh_cleanup = NULL;
   basic_block bb;
-  FOR_EACH_BB_FN (bb, cfun)
+
+  if (flag_exceptions)
+    {
+      int lp_eh_cleanup = -1;
+
+      /* Record the preexisting blocks, to avoid visiting newly-created
+	 blocks.  */
+      auto_sbitmap to_visit (last_basic_block_for_fn (fun));
+      bitmap_clear (to_visit);
+
+      FOR_EACH_BB_FN (bb, fun)
+	bitmap_set_bit (to_visit, bb->index);
+
+      /* Scan the blocks for stmts with escaping exceptions, that
+	 wouldn't be denoted in the CFG, and associate them with an
+	 empty cleanup handler around the whole function.  Walk
+	 backwards, so that even when we split the block, */
+      sbitmap_iterator it;
+      unsigned i;
+      EXECUTE_IF_SET_IN_BITMAP (to_visit, 0, i, it)
+	{
+	  bb = BASIC_BLOCK_FOR_FN (fun, i);
+
+	  for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	       !gsi_end_p (gsi); gsi_prev (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (!gimple_could_trap_p (stmt))
+		continue;
+
+	      /* If it must not throw, or if it already has a handler,
+		 we need not worry about it.  */
+	      if (lookup_stmt_eh_lp (stmt) != 0)
+		continue;
+
+	      if (!stmt_ends_bb_p (stmt))
+		split_block (bb, stmt);
+
+	      if (!bb_eh_cleanup)
+		{
+		  bb_eh_cleanup = create_empty_bb (bb);
+		  if (dom_info_available_p (CDI_DOMINATORS))
+		    set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+		  if (current_loops)
+		    add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+		  /* Make the new block an EH cleanup for the call.  */
+		  eh_region new_r = gen_eh_region_cleanup (NULL);
+		  eh_landing_pad lp = gen_eh_landing_pad (new_r);
+		  tree label = gimple_block_label (bb_eh_cleanup);
+		  lp->post_landing_pad = label;
+		  EH_LANDING_PAD_NR (label) = lp_eh_cleanup = lp->index;
+
+		  /* Just propagate the exception.
+		     We will later insert the verifier call.  */
+		  gimple_stmt_iterator ehgsi;
+		  ehgsi = gsi_after_labels (bb_eh_cleanup);
+		  gresx *resx = gimple_build_resx (new_r->index);
+		  gsi_insert_before (&ehgsi, resx, GSI_SAME_STMT);
+		}
+	      else
+		{
+		  // Update immedite dominator and loop?
+		}
+
+	      add_stmt_to_eh_lp (stmt, lp_eh_cleanup);
+	      /* Finally, wire the EH cleanup block into the CFG.  */
+	      make_eh_edges (stmt);		}
+	}
+    }
+
+  /* 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);
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      if (EDGE_COUNT (bb->succs) == 0)
+	{
+	  if (bitmap_set_bit (noreturn_blocks, bb->index))
+	    count_noreturn++;
+	  continue;
+	}
+
+      /* 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))
+	    continue;
+	}
+
+      if (bitmap_set_bit (noreturn_blocks, bb->index))
+	count_noreturn++;
+    }
+
+  gcc_checking_assert (!bb_eh_cleanup
+		       || bitmap_bit_p (noreturn_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
+      && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
+    return 0;
+
+  rt_bb_visited vstd (count_noreturn);
+
+  FOR_EACH_BB_FN (bb, fun)
     vstd.visit (bb);
 
-  vstd.check ();
+  vstd.check (count_noreturn, noreturn_blocks);
 
-  return 0;
+  return
+    TODO_update_ssa
+    | TODO_cleanup_cfg
+    | TODO_verify_il;
 }
 
 /* Instantiate a hardcfr pass.  */

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls
@ 2022-08-10 23:51 Alexandre Oliva
  0 siblings, 0 replies; 7+ messages in thread
From: Alexandre Oliva @ 2022-08-10 23:51 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:55ff57d87260178ba62e888b102f117995543d8b

commit 55ff57d87260178ba62e888b102f117995543d8b
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Aug 10 20:40:17 2022 -0300

    hardcfr: add checking at exceptions and noreturn calls

Diff:
---
 gcc/doc/invoke.texi               |   7 +-
 gcc/gimple-harden-control-flow.cc | 270 +++++++++++++++++++++++++++++++-------
 2 files changed, 226 insertions(+), 51 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e3bf391c716..1fa212f220b 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16441,9 +16441,10 @@ 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, that they amount to an execution path that is
-consistent with the control flow graph, trapping otherwise.  Tuning
-options @option{--param hardcfr-max-blocks} and @option{--param
+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.
 
 @item -fstack-protector
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 8932d548a20..5066a43fe6c 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -30,6 +30,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa.h"
 #include "gimple-iterator.h"
 #include "tree-cfg.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "sbitmap.h"
 #include "basic-block.h"
 #include "cfghooks.h"
 #include "cfgloop.h"
@@ -60,9 +63,7 @@ const pass_data pass_data_harden_control_flow_redundancy = {
   0,	    // properties_provided
   0,	    // properties_destroyed
   TODO_cleanup_cfg, // properties_start
-  TODO_update_ssa
-  | TODO_cleanup_cfg
-  | TODO_verify_il, // properties_finish
+  0,        // properties_finish
 };
 
 class pass_harden_control_flow_redundancy : public gimple_opt_pass
@@ -79,16 +80,6 @@ public:
     if (!flag_harden_control_flow_redundancy)
       return false;
 
-    /* We don't verify when an exception escapes, propagated or raised
-       by the function itself, so we're only concerned with edges to
-       the exit block.  If there aren't any, the function doesn't
-       return normally, so there won't be any checking point, so
-       there's no point in running the pass.  Should we add
-       verification at exception escapes, we should at least look at
-       !flag_exceptions here.  */
-    if (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
-      return false;
-
     /* Functions that return more than once, like setjmp and vfork
        (that also gets this flag set), will start recording a path
        after the first return, and then may take another path when
@@ -276,7 +267,7 @@ class rt_bb_visited
 
 public:
   /* Prepare to add control flow redundancy testing to CFUN.  */
-  rt_bb_visited ()
+  rt_bb_visited (int noreturn_blocks)
     : nblocks (n_basic_blocks_for_fn (cfun)),
       vword_type (NULL), ckseq (NULL), rtcfg (NULL)
   {
@@ -360,7 +351,9 @@ public:
     gimple_seq_add_stmt (&ckseq, detach);
 
     if (nblocks - 2 > blknum (param_hardcfr_max_inline_blocks)
-	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun)))
+	|| !single_pred_p (EXIT_BLOCK_PTR_FOR_FN (cfun))
+	|| (EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
+	    + noreturn_blocks > 1))
       {
 	/* Make sure vword_bits is wide enough for the representation
 	   of nblocks in rtcfg.  Compare with vword_bits << vword_bits,
@@ -385,12 +378,10 @@ public:
     gimple_seq_add_stmt (&ckseq, ckfail_init);
   }
 
-  /* 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 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)
   {
-    basic_block insbb = e->src;
-
     /* 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
@@ -407,43 +398,44 @@ public:
        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)
+    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;
+  }
+
+  /* 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)
+  {
+    if (!insert_exit_check (seq, e->src))
       gsi_insert_seq_on_edge_immediate (e, seq);
   }
 
   /* Add checking code on every exit edge, 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 ()
+  void check (int count_noreturn, auto_sbitmap const &noreturn_blocks)
   {
-    /* Insert initializers for visited at the entry.  */
-    gimple_seq iseq = NULL;
-
-    gcall *vinit = gimple_build_call (builtin_decl_explicit
-				      (BUILT_IN_MEMSET), 3,
-				      build1 (ADDR_EXPR,
-					      build_pointer_type
-					      (TREE_TYPE (visited)),
-					      visited),
-				      integer_zero_node,
-				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
-    gimple_seq_add_stmt (&iseq, vinit);
-
-    gsi_insert_seq_on_edge_immediate (single_succ_edge
-				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
-				      iseq);
-
     /* If we're using out-of-line checking, create and statically
        initialize the CFG checking representation, generate the
        checker call for the checking sequence, and insert it in all
@@ -512,12 +504,32 @@ public:
 	    gimple_seq seq = ckseq;
 	    /* Copy the sequence, unless we're dealing with the
 	       last edge (we're counting down to zero).  */
-	    if (i)
+	    if (i || count_noreturn)
+	      seq = gimple_seq_copy (seq);
+
+	    edge e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i);
+
+	    insert_exit_check (seq, e);
+
+	    gcc_checking_assert (!bitmap_bit_p (noreturn_blocks, e->src->index));
+	  }
+
+	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);
 
-	    insert_exit_check (seq,
-			       EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), i));
+	    if (!insert_exit_check (seq, bb))
+	      gcc_unreachable ();
 	  }
+
+	gcc_checking_assert (count_noreturn == 0);
       }
     else
       {
@@ -570,6 +582,24 @@ public:
 	if (dom_info_available_p (CDI_DOMINATORS))
 	  set_immediate_dominator (CDI_DOMINATORS, trp, gimple_bb (last));
       }
+
+    /* Insert initializers for visited at the entry.  Do this after
+       other insertions, to avoid messing with block numbers.  */
+    gimple_seq iseq = NULL;
+
+    gcall *vinit = gimple_build_call (builtin_decl_explicit
+				      (BUILT_IN_MEMSET), 3,
+				      build1 (ADDR_EXPR,
+					      build_pointer_type
+					      (TREE_TYPE (visited)),
+					      visited),
+				      integer_zero_node,
+				      TYPE_SIZE_UNIT (TREE_TYPE (visited)));
+    gimple_seq_add_stmt (&iseq, vinit);
+
+    gsi_insert_seq_on_edge_immediate (single_succ_edge
+				      (ENTRY_BLOCK_PTR_FOR_FN (cfun)),
+				      iseq);
   }
 
   /* Push onto RTCFG a (mask, index) pair to test for IBB when BB is
@@ -697,17 +727,161 @@ public:
    verify at exit that an expect path was taken.  */
 
 unsigned int
-pass_harden_control_flow_redundancy::execute (function *)
+pass_harden_control_flow_redundancy::execute (function *fun)
 {
-  rt_bb_visited vstd;
-
+  basic_block bb_eh_cleanup = NULL;
   basic_block bb;
-  FOR_EACH_BB_FN (bb, cfun)
+
+  if (flag_exceptions)
+    {
+      int lp_eh_cleanup = -1;
+
+      /* Record the preexisting blocks, to avoid visiting newly-created
+	 blocks.  */
+      auto_sbitmap to_visit (last_basic_block_for_fn (fun));
+      bitmap_clear (to_visit);
+
+      FOR_EACH_BB_FN (bb, fun)
+	bitmap_set_bit (to_visit, bb->index);
+
+      /* Scan the blocks for stmts with escaping exceptions, that
+	 wouldn't be denoted in the CFG, and associate them with an
+	 empty cleanup handler around the whole function.  Walk
+	 backwards, so that even when we split the block, */
+      sbitmap_iterator it;
+      unsigned i;
+      EXECUTE_IF_SET_IN_BITMAP (to_visit, 0, i, it)
+	{
+	  bb = BASIC_BLOCK_FOR_FN (fun, i);
+
+	  for (gimple_stmt_iterator gsi = gsi_last_bb (bb);
+	       !gsi_end_p (gsi); gsi_prev (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (!gimple_could_trap_p (stmt))
+		continue;
+
+	      /* If it must not throw, or if it already has a handler,
+		 we need not worry about it.  */
+	      if (lookup_stmt_eh_lp (stmt) != 0)
+		continue;
+
+	      if (!stmt_ends_bb_p (stmt))
+		split_block (bb, stmt);
+
+	      if (!bb_eh_cleanup)
+		{
+		  bb_eh_cleanup = create_empty_bb (bb);
+		  if (dom_info_available_p (CDI_DOMINATORS))
+		    set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+		  if (current_loops)
+		    add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+		  /* Make the new block an EH cleanup for the call.  */
+		  eh_region new_r = gen_eh_region_cleanup (NULL);
+		  eh_landing_pad lp = gen_eh_landing_pad (new_r);
+		  tree label = gimple_block_label (bb_eh_cleanup);
+		  lp->post_landing_pad = label;
+		  EH_LANDING_PAD_NR (label) = lp_eh_cleanup = lp->index;
+
+		  /* Just propagate the exception.
+		     We will later insert the verifier call.  */
+		  gimple_stmt_iterator ehgsi;
+		  ehgsi = gsi_after_labels (bb_eh_cleanup);
+		  gresx *resx = gimple_build_resx (new_r->index);
+		  gsi_insert_before (&ehgsi, resx, GSI_SAME_STMT);
+		}
+	      else
+		{
+		  // Update immedite dominator and loop?
+		}
+
+	      add_stmt_to_eh_lp (stmt, lp_eh_cleanup);
+	      /* Finally, wire the EH cleanup block into the CFG.  */
+	      make_eh_edges (stmt);		}
+	}
+    }
+
+  /* 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);
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      if (EDGE_COUNT (bb->succs) == 0)
+	{
+	  if (bitmap_set_bit (noreturn_blocks, bb->index))
+	    count_noreturn++;
+	  continue;
+	}
+
+      /* 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))
+	    continue;
+	}
+
+      if (bitmap_set_bit (noreturn_blocks, bb->index))
+	count_noreturn++;
+    }
+
+  gcc_checking_assert (!bb_eh_cleanup
+		       || bitmap_bit_p (noreturn_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
+      && EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (fun)->preds) == 0)
+    return 0;
+
+  rt_bb_visited vstd (count_noreturn);
+
+  FOR_EACH_BB_FN (bb, fun)
     vstd.visit (bb);
 
-  vstd.check ();
+  vstd.check (count_noreturn, noreturn_blocks);
 
-  return 0;
+  return
+    TODO_update_ssa
+    | TODO_cleanup_cfg
+    | TODO_verify_il;
 }
 
 /* Instantiate a hardcfr pass.  */


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2022-09-02 23:34 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-02 23:34 [gcc(refs/users/aoliva/heads/testme)] hardcfr: add checking at exceptions and noreturn calls Alexandre Oliva
  -- strict thread matches above, loose matches on Subject: below --
2022-08-27  2:55 Alexandre Oliva
2022-08-24 22:59 Alexandre Oliva
2022-08-24 22:45 Alexandre Oliva
2022-08-24 19:39 Alexandre Oliva
2022-08-24 16:52 Alexandre Oliva
2022-08-10 23:51 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).