public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-10-01  4:50 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-10-01  4:50 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:350e7daf3ed8df76fbf207670044d886d12ddd67

commit 350e7daf3ed8df76fbf207670044d886d12ddd67
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Fri Sep 30 23:20:35 2022 -0300

    hardcfr: mark expected-throw functions

Diff:
---
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/cp/except.cc                                    |  8 +++++---
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-always.c   | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-never.c    | 11 +++++++++++
 .../torture/harden-cfr-abrt-not-always.c            | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-nothrow.c  | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt.c          | 10 +++++++++-
 .../c-c++-common/torture/harden-cfr-always.c        | 13 +++++++++++++
 .../torture/harden-cfr-bret-not-always.c            | 14 ++++++++++++++
 .../c-c++-common/torture/harden-cfr-never.c         | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-not-always.c    | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-nothrow.c       | 13 +++++++++++++
 gcc/testsuite/c-c++-common/torture/harden-cfr.c     | 17 ++++++++++-------
 gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C   |  2 ++
 .../g++.dg/harden-cfr-throw-returning-O0.C          |  2 ++
 .../g++.dg/harden-cfr-throw-returning-enabled-O0.C  | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-always.C        |  7 -------
 .../g++.dg/torture/harden-cfr-throw-never.C         | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nothrow.C       | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  8 ++++++++
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 32 files changed, 255 insertions(+), 40 deletions(-)

diff --git a/gcc/calls.cc b/gcc/calls.cc
index 6dd6f73e978..0c50d83b0ba 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -845,6 +845,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 11c1ec95c8f..0e1e65f6bed 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1805,7 +1805,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1816,14 +1816,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index fb85564a191..76922d56339 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5132,7 +5132,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index 048612de400..d939f63187b 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -652,12 +652,13 @@ build_throw (location_t loc, tree exp)
 	  tree args[3] = {ptr_type_node, ptr_type_node, cleanup_type};
 
 	  throw_fn = declare_library_fn_1 ("__cxa_throw",
-					   ECF_NORETURN | ECF_COLD,
+					   ECF_NORETURN | ECF_THROW | ECF_COLD,
 					   void_type_node, 3, args);
 	  if (flag_tm && throw_fn != error_mark_node)
 	    {
 	      tree itm_fn = declare_library_fn_1 ("_ITM_cxa_throw",
-						  ECF_NORETURN | ECF_COLD,
+						  ECF_NORETURN | ECF_THROW
+						  | ECF_COLD,
 						  void_type_node, 3, args);
 	      if (itm_fn != error_mark_node)
 		{
@@ -803,7 +804,8 @@ build_throw (location_t loc, tree exp)
       if (!rethrow_fn)
 	{
 	  rethrow_fn = declare_library_fn_1 ("__cxa_rethrow",
-					     ECF_NORETURN | ECF_COLD,
+					     ECF_NORETURN | ECF_THROW
+					     | ECF_COLD,
 					     void_type_node, 0, NULL);
 	  if (flag_tm && rethrow_fn != error_mark_node)
 	    apply_tm_attr (rethrow_fn, get_identifier ("transaction_pure"));
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e61dae228a0..edd1e691815 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16640,13 +16640,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16666,7 +16668,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 3fae7548cab..d6d3b0e7800 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index cd5b3cf69b2..106a50d9384 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1063,9 +1063,14 @@ public:
    Exception-raising functions, such as C++'s __cxa_throw,
    __cxa_rethrow, and Ada's */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 4d45311b45c..8b5bc72cb31 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1532,6 +1535,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 77ac1495c46..e3f3670ba3d 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3543,6 +3544,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c
new file mode 100644
index 00000000000..26c0f270716
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.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" } */
+
+/* Check the noreturn handling of a builtin call with always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
new file mode 100644
index 00000000000..a9eca9893bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with never.  */
+
+#include "harden-cfr-abrt.c"
+
+/* No out-of-line checking.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 0 "hardcfr" } } */
+/* Inline checking only before return in f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
new file mode 100644
index 00000000000..a91cc1726dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with not-always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
new file mode 100644
index 00000000000..24363bdfe57
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with =nothrow.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
index 4aada93ad94..1ed727317f1 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
@@ -1,11 +1,19 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+/* Check the noreturn handling of a builtin call.  */
+
 int f(int i) {
   if (!i)
     __builtin_abort ();
   return i;
 }
 
-/* No checking before the noreturn abort, so single inline check.  */
+int g() {
+  __builtin_abort ();
+}
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
new file mode 100644
index 00000000000..6e0767aad69
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
new file mode 100644
index 00000000000..a454f2b36fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that, even enabling checks before not-always-throwing noreturn calls
+   (leaving returning calls enabled), we get checks before __builtin_return
+   without duplication (__builtin_return is both noreturn and a returning
+   call).  */
+
+#include "harden-cfr-bret.c"
+
+/* Out-of-line checking, before both builtin_return and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
new file mode 100644
index 00000000000..7fe0bb4a663
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param).  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
+/* No checking for h (too many blocks) or main (no edges to exit block).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
new file mode 100644
index 00000000000..b9eda188201
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls that aren't expected to throw.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
new file mode 100644
index 00000000000..da54fc0b57a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   nothrow noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr.c b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
index 2f5946c0387..73824c66f50 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
@@ -1,6 +1,8 @@
 /* { dg-do run } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects" } */
 
+/* Check the instrumentation and the parameters.  */
+
 int
 f (int i, int j)
 {
@@ -64,18 +66,19 @@ h (unsigned i, int j) /* { dg-warning "has more than 9 blocks, the requested max
 }
 
 int
-main ()
+main (int argc, char *argv[])
 {
-  if (f (1, 2) != 2 || f (3, 2) != 6
-      || g (2, 5) != 25 || h (4, 3) != 33)
+  if (f (1, 2) != 2 || g (2, 5) != 25 || h (4, 3) != 33
+      || argc < 0)
     __builtin_abort ();
   /* Call exit, instead of returning, to avoid an edge to the exit block and
-     thus implicitly disable hardening of main.  */
+     thus implicitly disable hardening of main, when checking before noreturn
+     calls is disabled.  */
   __builtin_exit (0);
 }
 
 /* Inlined checking thus trap for f.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Out-of-line checking for g.  */
-/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
-/* No checking for h (too many blocks) or main (no edges to exit block).  */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
index 17ea79f7cfb..e3c109b89c5 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
@@ -5,6 +5,8 @@
    maybe-throwing functions, and also checking before noreturn
    calls.  h2 and h2b get an extra resx without ehcleanup.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 16 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
index f0338ccc361..207bdb7471a 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -4,6 +4,8 @@
 /* -fhardcfr-check-returning-calls gets implicitly disabled because,
    -at O0, -foptimize-sibling-calls has no effect.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
new file mode 100644
index 00000000000..b2df689c932
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fdump-tree-hardcfr -O0" } */
+
+/* Explicitly enable -fhardcfr-check-returning-calls -at O0.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* Same expectations as those in torture/harden-cfr-throw-returning.C.  */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 0286f6e6d3f..4d303e769ef 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -5,13 +5,6 @@
    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" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
new file mode 100644
index 00000000000..81c1b1abae6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "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
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..9723d92bfec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
new file mode 100644
index 00000000000..e1c2e8d73bb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..8e46b900cd2 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,6 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+#if ! __OPTIMIZE__ && ! defined NO_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
+
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
 
@@ -63,3 +70,4 @@ void h4(void) {
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 55807fe15c6..861830947c8 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index f8d24b5d4cf..f5909310421 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9617,6 +9617,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9822,7 +9826,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9831,7 +9836,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-10-25  2:51 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-10-25  2:51 UTC (permalink / raw)
  To: gcc-cvs

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

commit d9182f82105dc72b0d255630220267498672c5d5
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Oct 19 20:36:22 2022 -0300

    hardcfr: mark expected-throw functions
    
    Introduce support for marking functions expected to raise exceptions,
    and adjust Control Flow Redundancy hardening to add not-always mode
    for checking noreturn calls, now default, taking these marks into
    account.
    
    The marking is placed as an internal attribute, that will be exposed
    in a separate patch.
    
    
    for  gcc/ChangeLog
    
            * tree-core.h (ECF_THROW): New macro.
            * tree.cc (set_call_expr): Add expected_throw attribute when
            ECF_THROW is set.
            (build_common_builtin_node): Add ECF_THROW to
            __cxa_end_cleanup and _Unwind_Resume or _Unwind_SjLj_Resume.
            * calls.cc (flags_from_decl_or_type): Check for expected_throw
            attribute to set ECF_THROW.
            * gimple.cc (gimple_build_call_from_tree): Propagate ECF_THROW
            from decl flags to gimple call...
            (gimple_call_flags): ... and back.
            * gimple.h (GF_CALL_EXPECTED_THROW): New gf_mask flag.
            (gimple_call_set_expected_throw): New.
            (gimple_call_expected_throw_p): New.
            * common.opt (-fhardcfr-check-noreturn-calls=not-always): Add.
            (not-always): Enable in enum hardcfr_check_noreturn_calls.
            * doc/invoke.texi: Document it.
            * flag-types.h (hardcfr_noret): Enable HCFRNR_NOT_ALWAYS, move
            HCFRNR_UNSPECIFIED next to it to change the default.
            * gimple-harden-control-flow.cc
            (always_throwing_noreturn_call_p): Test for noreturn
            expected_throw calls.
    
    for  gcc/cp/ChangeLog
    
            * decl.cc (push_throw_library_fn): Mark with ECF_THROW.
            * except.cc (build_throw): Likewise __cxa_throw,
            _ITM_cxa_throw, __cxa_rethrow.
    
    for  gcc/testsuite/ChangeLog
    
            * c-c++-common/torture/harden-cfr-abrt-always.c: New.
            * c-c++-common/torture/harden-cfr-abrt-never.c: New.
            * c-c++-common/torture/harden-cfr-abrt-not-always.c: New.
            * c-c++-common/torture/harden-cfr-abrt-nothrow.c: New.
            * c-c++-common/torture/harden-cfr-abrt.c: Extend.
            * c-c++-common/torture/harden-cfr-always.c: New.
            * c-c++-common/torture/harden-cfr-bret-not-always.c: New.
            * c-c++-common/torture/harden-cfr-never.c: New.
            * c-c++-common/torture/harden-cfr-not-always.c: New.
            * c-c++-common/torture/harden-cfr-nothrow.c: New.
            * c-c++-common/torture/harden-cfr.c: Prevent full
            optimization of main.  Adjust for new default.
            * g++.dg/harden-cfr-throw-always-O0.C (NO_OPTIMIZE): Define.
            * g++.dg/harden-cfr-throw-returning-O0.C (NO_OPTIMIZE):
            Likewise.
            * g++.dg/harden-cfr-throw-returning-enabled-O0.C: New.
            * g++.dg/torture/harden-cfr-throw-always.C [!OPTIMIZE]:
            Move forced optimizations...
            * g++.dg/torture/harden-cfr-throw.C: ... here.
            * g++.dg/torture/harden-cfr-throw-never.C: New.
            * g++.dg/torture/harden-cfr-throw-not-always.C: New.
            * g++.dg/torture/harden-cfr-throw-nothrow.C: New.
            * g++.dg/torture/harden-cfr-throw-nocleanup.C: Adjust flags to
            retain old defaults.
            * g++.dg/torture/harden-cfr-throw-returning.C: Likewise.

Diff:
---
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/cp/except.cc                                    |  8 +++++---
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-always.c   | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-never.c    | 11 +++++++++++
 .../torture/harden-cfr-abrt-not-always.c            | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-nothrow.c  | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt.c          | 10 +++++++++-
 .../c-c++-common/torture/harden-cfr-always.c        | 13 +++++++++++++
 .../torture/harden-cfr-bret-not-always.c            | 14 ++++++++++++++
 .../c-c++-common/torture/harden-cfr-never.c         | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-not-always.c    | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-nothrow.c       | 13 +++++++++++++
 gcc/testsuite/c-c++-common/torture/harden-cfr.c     | 17 ++++++++++-------
 gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C   |  2 ++
 .../g++.dg/harden-cfr-throw-returning-O0.C          |  2 ++
 .../g++.dg/harden-cfr-throw-returning-enabled-O0.C  | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-always.C        |  7 -------
 .../g++.dg/torture/harden-cfr-throw-never.C         | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nothrow.C       | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  8 ++++++++
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 32 files changed, 255 insertions(+), 40 deletions(-)

diff --git a/gcc/calls.cc b/gcc/calls.cc
index 6dd6f73e978..0c50d83b0ba 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -845,6 +845,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 2f37d2fcbd3..e57646ce125 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1808,7 +1808,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1819,14 +1819,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 85b892cddf0..5d8f88d8419 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5144,7 +5144,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index 2677a9b7678..0131fbcf305 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -652,12 +652,13 @@ build_throw (location_t loc, tree exp)
 	  tree args[3] = {ptr_type_node, ptr_type_node, cleanup_type};
 
 	  throw_fn = declare_library_fn_1 ("__cxa_throw",
-					   ECF_NORETURN | ECF_COLD,
+					   ECF_NORETURN | ECF_THROW | ECF_COLD,
 					   void_type_node, 3, args);
 	  if (flag_tm && throw_fn != error_mark_node)
 	    {
 	      tree itm_fn = declare_library_fn_1 ("_ITM_cxa_throw",
-						  ECF_NORETURN | ECF_COLD,
+						  ECF_NORETURN | ECF_THROW
+						  | ECF_COLD,
 						  void_type_node, 3, args);
 	      if (itm_fn != error_mark_node)
 		{
@@ -788,7 +789,8 @@ build_throw (location_t loc, tree exp)
       if (!rethrow_fn)
 	{
 	  rethrow_fn = declare_library_fn_1 ("__cxa_rethrow",
-					     ECF_NORETURN | ECF_COLD,
+					     ECF_NORETURN | ECF_THROW
+					     | ECF_COLD,
 					     void_type_node, 0, NULL);
 	  if (flag_tm && rethrow_fn != error_mark_node)
 	    apply_tm_attr (rethrow_fn, get_identifier ("transaction_pure"));
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1ea9525c78d..24ff8e36721 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16687,13 +16687,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16713,7 +16715,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index b6d2294d2d1..268bf028ada 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 5c66762d448..53717a652ca 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1067,9 +1067,14 @@ public:
    unwinders could be detected here and handled differently from other
    noreturn functions.  */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 6c23dd77609..d96fd3a40e2 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1544,6 +1547,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index adbeb063186..86b683b4831 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3575,6 +3576,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c
new file mode 100644
index 00000000000..26c0f270716
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.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" } */
+
+/* Check the noreturn handling of a builtin call with always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
new file mode 100644
index 00000000000..a9eca9893bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with never.  */
+
+#include "harden-cfr-abrt.c"
+
+/* No out-of-line checking.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 0 "hardcfr" } } */
+/* Inline checking only before return in f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
new file mode 100644
index 00000000000..a91cc1726dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with not-always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
new file mode 100644
index 00000000000..24363bdfe57
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with =nothrow.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
index 4aada93ad94..1ed727317f1 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
@@ -1,11 +1,19 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+/* Check the noreturn handling of a builtin call.  */
+
 int f(int i) {
   if (!i)
     __builtin_abort ();
   return i;
 }
 
-/* No checking before the noreturn abort, so single inline check.  */
+int g() {
+  __builtin_abort ();
+}
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
new file mode 100644
index 00000000000..6e0767aad69
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
new file mode 100644
index 00000000000..a454f2b36fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that, even enabling checks before not-always-throwing noreturn calls
+   (leaving returning calls enabled), we get checks before __builtin_return
+   without duplication (__builtin_return is both noreturn and a returning
+   call).  */
+
+#include "harden-cfr-bret.c"
+
+/* Out-of-line checking, before both builtin_return and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
new file mode 100644
index 00000000000..7fe0bb4a663
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param).  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
+/* No checking for h (too many blocks) or main (no edges to exit block).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
new file mode 100644
index 00000000000..b9eda188201
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls that aren't expected to throw.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
new file mode 100644
index 00000000000..da54fc0b57a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   nothrow noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr.c b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
index 2f5946c0387..73824c66f50 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
@@ -1,6 +1,8 @@
 /* { dg-do run } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects" } */
 
+/* Check the instrumentation and the parameters.  */
+
 int
 f (int i, int j)
 {
@@ -64,18 +66,19 @@ h (unsigned i, int j) /* { dg-warning "has more than 9 blocks, the requested max
 }
 
 int
-main ()
+main (int argc, char *argv[])
 {
-  if (f (1, 2) != 2 || f (3, 2) != 6
-      || g (2, 5) != 25 || h (4, 3) != 33)
+  if (f (1, 2) != 2 || g (2, 5) != 25 || h (4, 3) != 33
+      || argc < 0)
     __builtin_abort ();
   /* Call exit, instead of returning, to avoid an edge to the exit block and
-     thus implicitly disable hardening of main.  */
+     thus implicitly disable hardening of main, when checking before noreturn
+     calls is disabled.  */
   __builtin_exit (0);
 }
 
 /* Inlined checking thus trap for f.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Out-of-line checking for g.  */
-/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
-/* No checking for h (too many blocks) or main (no edges to exit block).  */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
index 17ea79f7cfb..e3c109b89c5 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
@@ -5,6 +5,8 @@
    maybe-throwing functions, and also checking before noreturn
    calls.  h2 and h2b get an extra resx without ehcleanup.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 16 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
index f0338ccc361..207bdb7471a 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -4,6 +4,8 @@
 /* -fhardcfr-check-returning-calls gets implicitly disabled because,
    -at O0, -foptimize-sibling-calls has no effect.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
new file mode 100644
index 00000000000..b2df689c932
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fdump-tree-hardcfr -O0" } */
+
+/* Explicitly enable -fhardcfr-check-returning-calls -at O0.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* Same expectations as those in torture/harden-cfr-throw-returning.C.  */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 0286f6e6d3f..4d303e769ef 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -5,13 +5,6 @@
    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" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
new file mode 100644
index 00000000000..81c1b1abae6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "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
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..9723d92bfec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
new file mode 100644
index 00000000000..e1c2e8d73bb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..8e46b900cd2 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,6 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+#if ! __OPTIMIZE__ && ! defined NO_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
+
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
 
@@ -63,3 +70,4 @@ void h4(void) {
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 80b886cc3e4..91e7c1af8ac 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 81a6ceaf181..bfbf5729981 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9637,6 +9637,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9842,7 +9846,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9851,7 +9856,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-10-20 22:31 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-10-20 22:31 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:9cd39226478f04a0c47767b81c4b9ef6c6e30bb0

commit 9cd39226478f04a0c47767b81c4b9ef6c6e30bb0
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Oct 19 20:36:22 2022 -0300

    hardcfr: mark expected-throw functions
    
    Introduce support for marking functions expected to raise exceptions,
    and adjust Control Flow Redundancy hardening to add not-always mode
    for checking noreturn calls, now default, taking these marks into
    account.
    
    The marking is placed as an internal attribute, that will be exposed
    in a separate patch.
    
    
    for  gcc/ChangeLog
    
            * tree-core.h (ECF_THROW): New macro.
            * tree.cc (set_call_expr): Add expected_throw attribute when
            ECF_THROW is set.
            (build_common_builtin_node): Add ECF_THROW to
            __cxa_end_cleanup and _Unwind_Resume or _Unwind_SjLj_Resume.
            * calls.cc (flags_from_decl_or_type): Check for expected_throw
            attribute to set ECF_THROW.
            * gimple.cc (gimple_build_call_from_tree): Propagate ECF_THROW
            from decl flags to gimple call...
            (gimple_call_flags): ... and back.
            * gimple.h (GF_CALL_EXPECTED_THROW): New gf_mask flag.
            (gimple_call_set_expected_throw): New.
            (gimple_call_expected_throw_p): New.
            * common.opt (-fhardcfr-check-noreturn-calls=not-always): Add.
            (not-always): Enable in enum hardcfr_check_noreturn_calls.
            * doc/invoke.texi: Document it.
            * flag-types.h (hardcfr_noret): Enable HCFRNR_NOT_ALWAYS, move
            HCFRNR_UNSPECIFIED next to it to change the default.
            * gimple-harden-control-flow.cc
            (always_throwing_noreturn_call_p): Test for noreturn
            expected_throw calls.
    
    for  gcc/cp/ChangeLog
    
            * decl.cc (push_throw_library_fn): Mark with ECF_THROW.
            * except.cc (build_throw): Likewise __cxa_throw,
            _ITM_cxa_throw, __cxa_rethrow.
    
    for  gcc/testsuite/ChangeLog
    
            * c-c++-common/torture/harden-cfr-abrt-always.c: New.
            * c-c++-common/torture/harden-cfr-abrt-never.c: New.
            * c-c++-common/torture/harden-cfr-abrt-not-always.c: New.
            * c-c++-common/torture/harden-cfr-abrt-nothrow.c: New.
            * c-c++-common/torture/harden-cfr-abrt.c: Extend.
            * c-c++-common/torture/harden-cfr-always.c: New.
            * c-c++-common/torture/harden-cfr-bret-not-always.c: New.
            * c-c++-common/torture/harden-cfr-never.c: New.
            * c-c++-common/torture/harden-cfr-not-always.c: New.
            * c-c++-common/torture/harden-cfr-nothrow.c: New.
            * c-c++-common/torture/harden-cfr.c: Prevent full
            optimization of main.  Adjust for new default.
            * g++.dg/harden-cfr-throw-always-O0.C (NO_OPTIMIZE): Define.
            * g++.dg/harden-cfr-throw-returning-O0.C (NO_OPTIMIZE):
            Likewise.
            * g++.dg/harden-cfr-throw-returning-enabled-O0.C: New.
            * g++.dg/torture/harden-cfr-throw-always.C [!OPTIMIZE]:
            Move forced optimizations...
            * g++.dg/torture/harden-cfr-throw.C: ... here.
            * g++.dg/torture/harden-cfr-throw-never.C: New.
            * g++.dg/torture/harden-cfr-throw-not-always.C: New.
            * g++.dg/torture/harden-cfr-throw-nothrow.C: New.
            * g++.dg/torture/harden-cfr-throw-nocleanup.C: Adjust flags to
            retain old defaults.
            * g++.dg/torture/harden-cfr-throw-returning.C: Likewise.

Diff:
---
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/cp/except.cc                                    |  8 +++++---
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-always.c   | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-never.c    | 11 +++++++++++
 .../torture/harden-cfr-abrt-not-always.c            | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-nothrow.c  | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt.c          | 10 +++++++++-
 .../c-c++-common/torture/harden-cfr-always.c        | 13 +++++++++++++
 .../torture/harden-cfr-bret-not-always.c            | 14 ++++++++++++++
 .../c-c++-common/torture/harden-cfr-never.c         | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-not-always.c    | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-nothrow.c       | 13 +++++++++++++
 gcc/testsuite/c-c++-common/torture/harden-cfr.c     | 17 ++++++++++-------
 gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C   |  2 ++
 .../g++.dg/harden-cfr-throw-returning-O0.C          |  2 ++
 .../g++.dg/harden-cfr-throw-returning-enabled-O0.C  | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-always.C        |  7 -------
 .../g++.dg/torture/harden-cfr-throw-never.C         | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nothrow.C       | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  8 ++++++++
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 32 files changed, 255 insertions(+), 40 deletions(-)

diff --git a/gcc/calls.cc b/gcc/calls.cc
index 6dd6f73e978..0c50d83b0ba 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -845,6 +845,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 2f37d2fcbd3..e57646ce125 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1808,7 +1808,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1819,14 +1819,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 85b892cddf0..5d8f88d8419 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5144,7 +5144,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index 2677a9b7678..0131fbcf305 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -652,12 +652,13 @@ build_throw (location_t loc, tree exp)
 	  tree args[3] = {ptr_type_node, ptr_type_node, cleanup_type};
 
 	  throw_fn = declare_library_fn_1 ("__cxa_throw",
-					   ECF_NORETURN | ECF_COLD,
+					   ECF_NORETURN | ECF_THROW | ECF_COLD,
 					   void_type_node, 3, args);
 	  if (flag_tm && throw_fn != error_mark_node)
 	    {
 	      tree itm_fn = declare_library_fn_1 ("_ITM_cxa_throw",
-						  ECF_NORETURN | ECF_COLD,
+						  ECF_NORETURN | ECF_THROW
+						  | ECF_COLD,
 						  void_type_node, 3, args);
 	      if (itm_fn != error_mark_node)
 		{
@@ -788,7 +789,8 @@ build_throw (location_t loc, tree exp)
       if (!rethrow_fn)
 	{
 	  rethrow_fn = declare_library_fn_1 ("__cxa_rethrow",
-					     ECF_NORETURN | ECF_COLD,
+					     ECF_NORETURN | ECF_THROW
+					     | ECF_COLD,
 					     void_type_node, 0, NULL);
 	  if (flag_tm && rethrow_fn != error_mark_node)
 	    apply_tm_attr (rethrow_fn, get_identifier ("transaction_pure"));
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1ea9525c78d..24ff8e36721 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16687,13 +16687,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16713,7 +16715,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 3fae7548cab..d6d3b0e7800 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index fe322f021ee..1ea47615cd4 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1066,9 +1066,14 @@ public:
    unwinders could be detected here and handled differently from other
    noreturn functions.  */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 6c23dd77609..d96fd3a40e2 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1544,6 +1547,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index adbeb063186..86b683b4831 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3575,6 +3576,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c
new file mode 100644
index 00000000000..26c0f270716
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.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" } */
+
+/* Check the noreturn handling of a builtin call with always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
new file mode 100644
index 00000000000..a9eca9893bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with never.  */
+
+#include "harden-cfr-abrt.c"
+
+/* No out-of-line checking.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 0 "hardcfr" } } */
+/* Inline checking only before return in f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
new file mode 100644
index 00000000000..a91cc1726dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with not-always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
new file mode 100644
index 00000000000..24363bdfe57
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with =nothrow.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
index 4aada93ad94..1ed727317f1 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
@@ -1,11 +1,19 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+/* Check the noreturn handling of a builtin call.  */
+
 int f(int i) {
   if (!i)
     __builtin_abort ();
   return i;
 }
 
-/* No checking before the noreturn abort, so single inline check.  */
+int g() {
+  __builtin_abort ();
+}
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
new file mode 100644
index 00000000000..6e0767aad69
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
new file mode 100644
index 00000000000..a454f2b36fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that, even enabling checks before not-always-throwing noreturn calls
+   (leaving returning calls enabled), we get checks before __builtin_return
+   without duplication (__builtin_return is both noreturn and a returning
+   call).  */
+
+#include "harden-cfr-bret.c"
+
+/* Out-of-line checking, before both builtin_return and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
new file mode 100644
index 00000000000..7fe0bb4a663
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param).  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
+/* No checking for h (too many blocks) or main (no edges to exit block).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
new file mode 100644
index 00000000000..b9eda188201
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls that aren't expected to throw.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
new file mode 100644
index 00000000000..da54fc0b57a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   nothrow noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr.c b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
index 2f5946c0387..73824c66f50 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
@@ -1,6 +1,8 @@
 /* { dg-do run } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects" } */
 
+/* Check the instrumentation and the parameters.  */
+
 int
 f (int i, int j)
 {
@@ -64,18 +66,19 @@ h (unsigned i, int j) /* { dg-warning "has more than 9 blocks, the requested max
 }
 
 int
-main ()
+main (int argc, char *argv[])
 {
-  if (f (1, 2) != 2 || f (3, 2) != 6
-      || g (2, 5) != 25 || h (4, 3) != 33)
+  if (f (1, 2) != 2 || g (2, 5) != 25 || h (4, 3) != 33
+      || argc < 0)
     __builtin_abort ();
   /* Call exit, instead of returning, to avoid an edge to the exit block and
-     thus implicitly disable hardening of main.  */
+     thus implicitly disable hardening of main, when checking before noreturn
+     calls is disabled.  */
   __builtin_exit (0);
 }
 
 /* Inlined checking thus trap for f.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Out-of-line checking for g.  */
-/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
-/* No checking for h (too many blocks) or main (no edges to exit block).  */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
index 17ea79f7cfb..e3c109b89c5 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
@@ -5,6 +5,8 @@
    maybe-throwing functions, and also checking before noreturn
    calls.  h2 and h2b get an extra resx without ehcleanup.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 16 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
index f0338ccc361..207bdb7471a 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -4,6 +4,8 @@
 /* -fhardcfr-check-returning-calls gets implicitly disabled because,
    -at O0, -foptimize-sibling-calls has no effect.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
new file mode 100644
index 00000000000..b2df689c932
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fdump-tree-hardcfr -O0" } */
+
+/* Explicitly enable -fhardcfr-check-returning-calls -at O0.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* Same expectations as those in torture/harden-cfr-throw-returning.C.  */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 0286f6e6d3f..4d303e769ef 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -5,13 +5,6 @@
    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" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
new file mode 100644
index 00000000000..81c1b1abae6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "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
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..9723d92bfec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
new file mode 100644
index 00000000000..e1c2e8d73bb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..8e46b900cd2 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,6 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+#if ! __OPTIMIZE__ && ! defined NO_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
+
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
 
@@ -63,3 +70,4 @@ void h4(void) {
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 80b886cc3e4..91e7c1af8ac 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 81a6ceaf181..bfbf5729981 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9637,6 +9637,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9842,7 +9846,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9851,7 +9856,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-10-20  4:09 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-10-20  4:09 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:20c7519c2eef3f4ea541f0c2d0d8b98e13b21bf8

commit 20c7519c2eef3f4ea541f0c2d0d8b98e13b21bf8
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Oct 19 20:36:22 2022 -0300

    hardcfr: mark expected-throw functions

Diff:
---
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/cp/except.cc                                    |  8 +++++---
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-always.c   | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-never.c    | 11 +++++++++++
 .../torture/harden-cfr-abrt-not-always.c            | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-nothrow.c  | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt.c          | 10 +++++++++-
 .../c-c++-common/torture/harden-cfr-always.c        | 13 +++++++++++++
 .../torture/harden-cfr-bret-not-always.c            | 14 ++++++++++++++
 .../c-c++-common/torture/harden-cfr-never.c         | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-not-always.c    | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-nothrow.c       | 13 +++++++++++++
 gcc/testsuite/c-c++-common/torture/harden-cfr.c     | 17 ++++++++++-------
 gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C   |  2 ++
 .../g++.dg/harden-cfr-throw-returning-O0.C          |  2 ++
 .../g++.dg/harden-cfr-throw-returning-enabled-O0.C  | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-always.C        |  7 -------
 .../g++.dg/torture/harden-cfr-throw-never.C         | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nothrow.C       | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  8 ++++++++
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 32 files changed, 255 insertions(+), 40 deletions(-)

diff --git a/gcc/calls.cc b/gcc/calls.cc
index 6dd6f73e978..0c50d83b0ba 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -845,6 +845,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 2f37d2fcbd3..e57646ce125 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1808,7 +1808,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1819,14 +1819,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 85b892cddf0..5d8f88d8419 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5144,7 +5144,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index 2677a9b7678..0131fbcf305 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -652,12 +652,13 @@ build_throw (location_t loc, tree exp)
 	  tree args[3] = {ptr_type_node, ptr_type_node, cleanup_type};
 
 	  throw_fn = declare_library_fn_1 ("__cxa_throw",
-					   ECF_NORETURN | ECF_COLD,
+					   ECF_NORETURN | ECF_THROW | ECF_COLD,
 					   void_type_node, 3, args);
 	  if (flag_tm && throw_fn != error_mark_node)
 	    {
 	      tree itm_fn = declare_library_fn_1 ("_ITM_cxa_throw",
-						  ECF_NORETURN | ECF_COLD,
+						  ECF_NORETURN | ECF_THROW
+						  | ECF_COLD,
 						  void_type_node, 3, args);
 	      if (itm_fn != error_mark_node)
 		{
@@ -788,7 +789,8 @@ build_throw (location_t loc, tree exp)
       if (!rethrow_fn)
 	{
 	  rethrow_fn = declare_library_fn_1 ("__cxa_rethrow",
-					     ECF_NORETURN | ECF_COLD,
+					     ECF_NORETURN | ECF_THROW
+					     | ECF_COLD,
 					     void_type_node, 0, NULL);
 	  if (flag_tm && rethrow_fn != error_mark_node)
 	    apply_tm_attr (rethrow_fn, get_identifier ("transaction_pure"));
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1ea9525c78d..24ff8e36721 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16687,13 +16687,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16713,7 +16715,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 3fae7548cab..d6d3b0e7800 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index fe322f021ee..1ea47615cd4 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1066,9 +1066,14 @@ public:
    unwinders could be detected here and handled differently from other
    noreturn functions.  */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 6c23dd77609..d96fd3a40e2 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1544,6 +1547,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index adbeb063186..86b683b4831 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3575,6 +3576,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c
new file mode 100644
index 00000000000..26c0f270716
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.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" } */
+
+/* Check the noreturn handling of a builtin call with always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
new file mode 100644
index 00000000000..a9eca9893bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with never.  */
+
+#include "harden-cfr-abrt.c"
+
+/* No out-of-line checking.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 0 "hardcfr" } } */
+/* Inline checking only before return in f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
new file mode 100644
index 00000000000..a91cc1726dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with not-always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
new file mode 100644
index 00000000000..24363bdfe57
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with =nothrow.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
index 4aada93ad94..1ed727317f1 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
@@ -1,11 +1,19 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+/* Check the noreturn handling of a builtin call.  */
+
 int f(int i) {
   if (!i)
     __builtin_abort ();
   return i;
 }
 
-/* No checking before the noreturn abort, so single inline check.  */
+int g() {
+  __builtin_abort ();
+}
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
new file mode 100644
index 00000000000..6e0767aad69
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
new file mode 100644
index 00000000000..a454f2b36fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that, even enabling checks before not-always-throwing noreturn calls
+   (leaving returning calls enabled), we get checks before __builtin_return
+   without duplication (__builtin_return is both noreturn and a returning
+   call).  */
+
+#include "harden-cfr-bret.c"
+
+/* Out-of-line checking, before both builtin_return and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
new file mode 100644
index 00000000000..7fe0bb4a663
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param).  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
+/* No checking for h (too many blocks) or main (no edges to exit block).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
new file mode 100644
index 00000000000..b9eda188201
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls that aren't expected to throw.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
new file mode 100644
index 00000000000..da54fc0b57a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   nothrow noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr.c b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
index 2f5946c0387..73824c66f50 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
@@ -1,6 +1,8 @@
 /* { dg-do run } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects" } */
 
+/* Check the instrumentation and the parameters.  */
+
 int
 f (int i, int j)
 {
@@ -64,18 +66,19 @@ h (unsigned i, int j) /* { dg-warning "has more than 9 blocks, the requested max
 }
 
 int
-main ()
+main (int argc, char *argv[])
 {
-  if (f (1, 2) != 2 || f (3, 2) != 6
-      || g (2, 5) != 25 || h (4, 3) != 33)
+  if (f (1, 2) != 2 || g (2, 5) != 25 || h (4, 3) != 33
+      || argc < 0)
     __builtin_abort ();
   /* Call exit, instead of returning, to avoid an edge to the exit block and
-     thus implicitly disable hardening of main.  */
+     thus implicitly disable hardening of main, when checking before noreturn
+     calls is disabled.  */
   __builtin_exit (0);
 }
 
 /* Inlined checking thus trap for f.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Out-of-line checking for g.  */
-/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
-/* No checking for h (too many blocks) or main (no edges to exit block).  */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
index 17ea79f7cfb..e3c109b89c5 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
@@ -5,6 +5,8 @@
    maybe-throwing functions, and also checking before noreturn
    calls.  h2 and h2b get an extra resx without ehcleanup.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 16 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
index f0338ccc361..207bdb7471a 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -4,6 +4,8 @@
 /* -fhardcfr-check-returning-calls gets implicitly disabled because,
    -at O0, -foptimize-sibling-calls has no effect.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
new file mode 100644
index 00000000000..b2df689c932
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fdump-tree-hardcfr -O0" } */
+
+/* Explicitly enable -fhardcfr-check-returning-calls -at O0.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* Same expectations as those in torture/harden-cfr-throw-returning.C.  */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 0286f6e6d3f..4d303e769ef 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -5,13 +5,6 @@
    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" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
new file mode 100644
index 00000000000..81c1b1abae6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "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
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..9723d92bfec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
new file mode 100644
index 00000000000..e1c2e8d73bb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..8e46b900cd2 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,6 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+#if ! __OPTIMIZE__ && ! defined NO_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
+
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
 
@@ -63,3 +70,4 @@ void h4(void) {
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 80b886cc3e4..91e7c1af8ac 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 81a6ceaf181..bfbf5729981 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9637,6 +9637,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9842,7 +9846,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9851,7 +9856,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-10-06 10:31 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-10-06 10:31 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:4f3a3ac0d0af80b206f2ef99fad02cd67a7a01d4

commit 4f3a3ac0d0af80b206f2ef99fad02cd67a7a01d4
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Oct 6 04:43:29 2022 -0300

    hardcfr: mark expected-throw functions

Diff:
---
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/cp/except.cc                                    |  8 +++++---
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-always.c   | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-never.c    | 11 +++++++++++
 .../torture/harden-cfr-abrt-not-always.c            | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-nothrow.c  | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt.c          | 10 +++++++++-
 .../c-c++-common/torture/harden-cfr-always.c        | 13 +++++++++++++
 .../torture/harden-cfr-bret-not-always.c            | 14 ++++++++++++++
 .../c-c++-common/torture/harden-cfr-never.c         | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-not-always.c    | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-nothrow.c       | 13 +++++++++++++
 gcc/testsuite/c-c++-common/torture/harden-cfr.c     | 17 ++++++++++-------
 gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C   |  2 ++
 .../g++.dg/harden-cfr-throw-returning-O0.C          |  2 ++
 .../g++.dg/harden-cfr-throw-returning-enabled-O0.C  | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-always.C        |  7 -------
 .../g++.dg/torture/harden-cfr-throw-never.C         | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nothrow.C       | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  8 ++++++++
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 32 files changed, 255 insertions(+), 40 deletions(-)

diff --git a/gcc/calls.cc b/gcc/calls.cc
index 6dd6f73e978..0c50d83b0ba 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -845,6 +845,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 11c1ec95c8f..0e1e65f6bed 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1805,7 +1805,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1816,14 +1816,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 07148b9fd4e..fe547f72d5f 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5145,7 +5145,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index 048612de400..d939f63187b 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -652,12 +652,13 @@ build_throw (location_t loc, tree exp)
 	  tree args[3] = {ptr_type_node, ptr_type_node, cleanup_type};
 
 	  throw_fn = declare_library_fn_1 ("__cxa_throw",
-					   ECF_NORETURN | ECF_COLD,
+					   ECF_NORETURN | ECF_THROW | ECF_COLD,
 					   void_type_node, 3, args);
 	  if (flag_tm && throw_fn != error_mark_node)
 	    {
 	      tree itm_fn = declare_library_fn_1 ("_ITM_cxa_throw",
-						  ECF_NORETURN | ECF_COLD,
+						  ECF_NORETURN | ECF_THROW
+						  | ECF_COLD,
 						  void_type_node, 3, args);
 	      if (itm_fn != error_mark_node)
 		{
@@ -803,7 +804,8 @@ build_throw (location_t loc, tree exp)
       if (!rethrow_fn)
 	{
 	  rethrow_fn = declare_library_fn_1 ("__cxa_rethrow",
-					     ECF_NORETURN | ECF_COLD,
+					     ECF_NORETURN | ECF_THROW
+					     | ECF_COLD,
 					     void_type_node, 0, NULL);
 	  if (flag_tm && rethrow_fn != error_mark_node)
 	    apply_tm_attr (rethrow_fn, get_identifier ("transaction_pure"));
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ff2b5bce5cf..ccb432d5903 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16644,13 +16644,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16670,7 +16672,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 3fae7548cab..d6d3b0e7800 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index cd5b3cf69b2..106a50d9384 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1063,9 +1063,14 @@ public:
    Exception-raising functions, such as C++'s __cxa_throw,
    __cxa_rethrow, and Ada's */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 4d45311b45c..8b5bc72cb31 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1532,6 +1535,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 77ac1495c46..e3f3670ba3d 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3543,6 +3544,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c
new file mode 100644
index 00000000000..26c0f270716
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.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" } */
+
+/* Check the noreturn handling of a builtin call with always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
new file mode 100644
index 00000000000..a9eca9893bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with never.  */
+
+#include "harden-cfr-abrt.c"
+
+/* No out-of-line checking.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 0 "hardcfr" } } */
+/* Inline checking only before return in f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
new file mode 100644
index 00000000000..a91cc1726dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with not-always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
new file mode 100644
index 00000000000..24363bdfe57
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with =nothrow.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
index 4aada93ad94..1ed727317f1 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
@@ -1,11 +1,19 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+/* Check the noreturn handling of a builtin call.  */
+
 int f(int i) {
   if (!i)
     __builtin_abort ();
   return i;
 }
 
-/* No checking before the noreturn abort, so single inline check.  */
+int g() {
+  __builtin_abort ();
+}
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
new file mode 100644
index 00000000000..6e0767aad69
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
new file mode 100644
index 00000000000..a454f2b36fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that, even enabling checks before not-always-throwing noreturn calls
+   (leaving returning calls enabled), we get checks before __builtin_return
+   without duplication (__builtin_return is both noreturn and a returning
+   call).  */
+
+#include "harden-cfr-bret.c"
+
+/* Out-of-line checking, before both builtin_return and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
new file mode 100644
index 00000000000..7fe0bb4a663
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param).  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
+/* No checking for h (too many blocks) or main (no edges to exit block).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
new file mode 100644
index 00000000000..b9eda188201
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls that aren't expected to throw.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
new file mode 100644
index 00000000000..da54fc0b57a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   nothrow noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr.c b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
index 2f5946c0387..73824c66f50 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
@@ -1,6 +1,8 @@
 /* { dg-do run } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects" } */
 
+/* Check the instrumentation and the parameters.  */
+
 int
 f (int i, int j)
 {
@@ -64,18 +66,19 @@ h (unsigned i, int j) /* { dg-warning "has more than 9 blocks, the requested max
 }
 
 int
-main ()
+main (int argc, char *argv[])
 {
-  if (f (1, 2) != 2 || f (3, 2) != 6
-      || g (2, 5) != 25 || h (4, 3) != 33)
+  if (f (1, 2) != 2 || g (2, 5) != 25 || h (4, 3) != 33
+      || argc < 0)
     __builtin_abort ();
   /* Call exit, instead of returning, to avoid an edge to the exit block and
-     thus implicitly disable hardening of main.  */
+     thus implicitly disable hardening of main, when checking before noreturn
+     calls is disabled.  */
   __builtin_exit (0);
 }
 
 /* Inlined checking thus trap for f.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Out-of-line checking for g.  */
-/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
-/* No checking for h (too many blocks) or main (no edges to exit block).  */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
index 17ea79f7cfb..e3c109b89c5 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
@@ -5,6 +5,8 @@
    maybe-throwing functions, and also checking before noreturn
    calls.  h2 and h2b get an extra resx without ehcleanup.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 16 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
index f0338ccc361..207bdb7471a 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -4,6 +4,8 @@
 /* -fhardcfr-check-returning-calls gets implicitly disabled because,
    -at O0, -foptimize-sibling-calls has no effect.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
new file mode 100644
index 00000000000..b2df689c932
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fdump-tree-hardcfr -O0" } */
+
+/* Explicitly enable -fhardcfr-check-returning-calls -at O0.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* Same expectations as those in torture/harden-cfr-throw-returning.C.  */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 0286f6e6d3f..4d303e769ef 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -5,13 +5,6 @@
    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" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
new file mode 100644
index 00000000000..81c1b1abae6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "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
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..9723d92bfec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
new file mode 100644
index 00000000000..e1c2e8d73bb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..8e46b900cd2 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,6 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+#if ! __OPTIMIZE__ && ! defined NO_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
+
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
 
@@ -63,3 +70,4 @@ void h4(void) {
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 55807fe15c6..861830947c8 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index f8d24b5d4cf..f5909310421 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9617,6 +9617,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9822,7 +9826,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9831,7 +9836,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-09-10  4:38 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-09-10  4:38 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:3199d12a540ff8bd6f94d5560014004b8cbaf06f

commit 3199d12a540ff8bd6f94d5560014004b8cbaf06f
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Fri Sep 9 01:44:34 2022 -0300

    hardcfr: mark expected-throw functions

Diff:
---
 gcc/ada/doc/gnat_rm/security_hardening_features.rst | 17 +++++++++--------
 gcc/ada/gcc-interface/trans.cc                      |  3 +++
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/cp/except.cc                                    |  8 +++++---
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-always.c   | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-never.c    | 11 +++++++++++
 .../torture/harden-cfr-abrt-not-always.c            | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-nothrow.c  | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt.c          | 10 +++++++++-
 .../c-c++-common/torture/harden-cfr-always.c        | 13 +++++++++++++
 .../torture/harden-cfr-bret-not-always.c            | 14 ++++++++++++++
 .../c-c++-common/torture/harden-cfr-never.c         | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-not-always.c    | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-nothrow.c       | 13 +++++++++++++
 gcc/testsuite/c-c++-common/torture/harden-cfr.c     | 17 ++++++++++-------
 gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C   |  2 ++
 .../g++.dg/harden-cfr-throw-returning-O0.C          |  2 ++
 .../g++.dg/harden-cfr-throw-returning-enabled-O0.C  | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-always.C        |  7 -------
 .../g++.dg/torture/harden-cfr-throw-never.C         | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nothrow.C       | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  8 ++++++++
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 34 files changed, 267 insertions(+), 48 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 4dfda486795..225dedd3541 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -367,17 +367,18 @@ gets modified as follows:
    end;
 
 
-Verification may also be performed before No_Return calls, whether
-only nothrow ones, with
-:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
-:switch:`-fhardcfr-check-noreturn-calls=always`.  The default is
-:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
-disables checking before No_Return calls.
+Verification may also be performed before No_Return calls, whether all
+of them, with :switch:`-fhardcfr-check-noreturn-calls=always`; all but
+internal subprograms involved in exception-raising or -reraising, with
+:switch:`-fhardcfr-check-noreturn-calls=not-always` (default); only
+nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`;
+or none, with :switch:`-fhardcfr-check-noreturn-calls=never`.
 
 When a No_Return call returns control to its caller through an
 exception, verification may have already been performed before the
-call, if :switch:`-fhardcfr-check-noreturn-calls=always` is in effect.
-The compiler arranges for already-checked No_Return calls without a
+call, if :switch:`-fhardcfr-check-noreturn-calls=always` or
+:switch:`-fhardcfr-check-noreturn-calls=not-always` is in effect.  The
+compiler arranges for already-checked No_Return calls without a
 preexisting handler to bypass the implicitly-added cleanup handler and
 thus the redundant check, but a local exception or cleanup handler, if
 present, will modify the set of visited blocks, and checking will take
diff --git a/gcc/ada/gcc-interface/trans.cc b/gcc/ada/gcc-interface/trans.cc
index f2e0cb2299b..64edbccc787 100644
--- a/gcc/ada/gcc-interface/trans.cc
+++ b/gcc/ada/gcc-interface/trans.cc
@@ -514,6 +514,7 @@ gigi (Node_Id gnat_root,
 			   ftype, NULL_TREE,
 			   is_default, true, true, true, false, false, NULL,
 			   Empty);
+  set_call_expr_flags (reraise_zcx_decl, ECF_NORETURN | ECF_THROW);
 
   /* Dummy objects to materialize "others" and "all others" in the exception
      tables.  These are exported by a-exexpr-gcc.adb, so see this unit for
@@ -553,6 +554,7 @@ gigi (Node_Id gnat_root,
 	  (get_identifier ("__gnat_last_chance_handler"), NULL_TREE, ftype,
 	   NULL_TREE, is_default, true, true, true, false, false, NULL,
 	   Empty);
+      set_call_expr_flags (decl, ECF_NORETURN | ECF_THROW);
       for (i = 0; i < (int) ARRAY_SIZE (gnat_raise_decls); i++)
 	gnat_raise_decls[i] = decl;
     }
@@ -716,6 +718,7 @@ build_raise_check (int check, enum exception_info_kind kind)
     = create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
 			   NULL_TREE, is_default, true, true, true, false,
 			   false, NULL, Empty);
+  set_call_expr_flags (result, ECF_NORETURN | ECF_THROW);
 
   return result;
 }
diff --git a/gcc/calls.cc b/gcc/calls.cc
index bc96aff38f0..6f841163aaa 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -844,6 +844,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 983cb4db7c6..afdd6dc76cc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1805,7 +1805,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1816,14 +1816,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 4665a29a24d..49f1e927155 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5132,7 +5132,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index da0a65c613d..a52b0ae5a1c 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -652,12 +652,13 @@ build_throw (location_t loc, tree exp)
 	  tree args[3] = {ptr_type_node, ptr_type_node, cleanup_type};
 
 	  throw_fn = declare_library_fn_1 ("__cxa_throw",
-					   ECF_NORETURN | ECF_COLD,
+					   ECF_NORETURN | ECF_THROW | ECF_COLD,
 					   void_type_node, 3, args);
 	  if (flag_tm && throw_fn != error_mark_node)
 	    {
 	      tree itm_fn = declare_library_fn_1 ("_ITM_cxa_throw",
-						  ECF_NORETURN | ECF_COLD,
+						  ECF_NORETURN | ECF_THROW
+						  | ECF_COLD,
 						  void_type_node, 3, args);
 	      if (itm_fn != error_mark_node)
 		{
@@ -803,7 +804,8 @@ build_throw (location_t loc, tree exp)
       if (!rethrow_fn)
 	{
 	  rethrow_fn = declare_library_fn_1 ("__cxa_rethrow",
-					     ECF_NORETURN | ECF_COLD,
+					     ECF_NORETURN | ECF_THROW
+					     | ECF_COLD,
 					     void_type_node, 0, NULL);
 	  if (flag_tm && rethrow_fn != error_mark_node)
 	    apply_tm_attr (rethrow_fn, get_identifier ("transaction_pure"));
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index f838d11bdb9..2b3beaf58be 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16617,13 +16617,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16643,7 +16645,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 3fae7548cab..d6d3b0e7800 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index cd5b3cf69b2..106a50d9384 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1063,9 +1063,14 @@ public:
    Exception-raising functions, such as C++'s __cxa_throw,
    __cxa_rethrow, and Ada's */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 4d45311b45c..8b5bc72cb31 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1532,6 +1535,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 77ac1495c46..e3f3670ba3d 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3543,6 +3544,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c
new file mode 100644
index 00000000000..26c0f270716
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.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" } */
+
+/* Check the noreturn handling of a builtin call with always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
new file mode 100644
index 00000000000..a9eca9893bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with never.  */
+
+#include "harden-cfr-abrt.c"
+
+/* No out-of-line checking.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 0 "hardcfr" } } */
+/* Inline checking only before return in f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
new file mode 100644
index 00000000000..a91cc1726dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with not-always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
new file mode 100644
index 00000000000..24363bdfe57
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with =nothrow.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
index 4aada93ad94..1ed727317f1 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
@@ -1,11 +1,19 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+/* Check the noreturn handling of a builtin call.  */
+
 int f(int i) {
   if (!i)
     __builtin_abort ();
   return i;
 }
 
-/* No checking before the noreturn abort, so single inline check.  */
+int g() {
+  __builtin_abort ();
+}
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
new file mode 100644
index 00000000000..6e0767aad69
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
new file mode 100644
index 00000000000..a454f2b36fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that, even enabling checks before not-always-throwing noreturn calls
+   (leaving returning calls enabled), we get checks before __builtin_return
+   without duplication (__builtin_return is both noreturn and a returning
+   call).  */
+
+#include "harden-cfr-bret.c"
+
+/* Out-of-line checking, before both builtin_return and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
new file mode 100644
index 00000000000..7fe0bb4a663
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param).  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
+/* No checking for h (too many blocks) or main (no edges to exit block).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
new file mode 100644
index 00000000000..b9eda188201
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls that aren't expected to throw.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
new file mode 100644
index 00000000000..da54fc0b57a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   nothrow noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr.c b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
index 2f5946c0387..73824c66f50 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
@@ -1,6 +1,8 @@
 /* { dg-do run } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects" } */
 
+/* Check the instrumentation and the parameters.  */
+
 int
 f (int i, int j)
 {
@@ -64,18 +66,19 @@ h (unsigned i, int j) /* { dg-warning "has more than 9 blocks, the requested max
 }
 
 int
-main ()
+main (int argc, char *argv[])
 {
-  if (f (1, 2) != 2 || f (3, 2) != 6
-      || g (2, 5) != 25 || h (4, 3) != 33)
+  if (f (1, 2) != 2 || g (2, 5) != 25 || h (4, 3) != 33
+      || argc < 0)
     __builtin_abort ();
   /* Call exit, instead of returning, to avoid an edge to the exit block and
-     thus implicitly disable hardening of main.  */
+     thus implicitly disable hardening of main, when checking before noreturn
+     calls is disabled.  */
   __builtin_exit (0);
 }
 
 /* Inlined checking thus trap for f.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Out-of-line checking for g.  */
-/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
-/* No checking for h (too many blocks) or main (no edges to exit block).  */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
index 17ea79f7cfb..e3c109b89c5 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-always-O0.C
@@ -5,6 +5,8 @@
    maybe-throwing functions, and also checking before noreturn
    calls.  h2 and h2b get an extra resx without ehcleanup.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 16 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
index f0338ccc361..207bdb7471a 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -4,6 +4,8 @@
 /* -fhardcfr-check-returning-calls gets implicitly disabled because,
    -at O0, -foptimize-sibling-calls has no effect.  */
 
+#define NO_OPTIMIZE
+
 #include "torture/harden-cfr-throw.C"
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
new file mode 100644
index 00000000000..b2df689c932
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fdump-tree-hardcfr -O0" } */
+
+/* Explicitly enable -fhardcfr-check-returning-calls -at O0.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* Same expectations as those in torture/harden-cfr-throw-returning.C.  */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 0286f6e6d3f..4d303e769ef 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -5,13 +5,6 @@
    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" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
new file mode 100644
index 00000000000..81c1b1abae6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "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
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..9723d92bfec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
new file mode 100644
index 00000000000..e1c2e8d73bb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..8e46b900cd2 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,6 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+#if ! __OPTIMIZE__ && ! defined NO_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
+
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
 
@@ -63,3 +70,4 @@ void h4(void) {
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 80c2bcb333d..869e6c4673e 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 2f488e4467c..9c49a1b1570 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9614,6 +9614,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9819,7 +9823,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9828,7 +9833,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-09-10  3:04 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-09-10  3:04 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:0a5440f33a66716cdde3fa19b40f7678de751d89

commit 0a5440f33a66716cdde3fa19b40f7678de751d89
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Fri Sep 9 01:44:34 2022 -0300

    hardcfr: mark expected-throw functions

Diff:
---
 gcc/ada/doc/gnat_rm/security_hardening_features.rst | 17 +++++++++--------
 gcc/ada/gcc-interface/trans.cc                      |  3 +++
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/cp/except.cc                                    |  8 +++++---
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-always.c   | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-never.c    | 11 +++++++++++
 .../torture/harden-cfr-abrt-not-always.c            | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt-nothrow.c  | 11 +++++++++++
 .../c-c++-common/torture/harden-cfr-abrt.c          | 10 +++++++++-
 .../c-c++-common/torture/harden-cfr-always.c        | 13 +++++++++++++
 .../torture/harden-cfr-bret-not-always.c            | 14 ++++++++++++++
 .../c-c++-common/torture/harden-cfr-never.c         | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-not-always.c    | 13 +++++++++++++
 .../c-c++-common/torture/harden-cfr-nothrow.c       | 13 +++++++++++++
 gcc/testsuite/c-c++-common/torture/harden-cfr.c     | 17 ++++++++++-------
 .../g++.dg/harden-cfr-throw-returning-O0.C          |  2 +-
 .../g++.dg/harden-cfr-throw-returning-enabled-O0.C  | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-always.C        |  7 -------
 .../g++.dg/torture/harden-cfr-throw-never.C         | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 12 ++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nothrow.C       | 11 +++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  8 ++++++++
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 33 files changed, 264 insertions(+), 49 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 4dfda486795..225dedd3541 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -367,17 +367,18 @@ gets modified as follows:
    end;
 
 
-Verification may also be performed before No_Return calls, whether
-only nothrow ones, with
-:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
-:switch:`-fhardcfr-check-noreturn-calls=always`.  The default is
-:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
-disables checking before No_Return calls.
+Verification may also be performed before No_Return calls, whether all
+of them, with :switch:`-fhardcfr-check-noreturn-calls=always`; all but
+internal subprograms involved in exception-raising or -reraising, with
+:switch:`-fhardcfr-check-noreturn-calls=not-always` (default); only
+nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`;
+or none, with :switch:`-fhardcfr-check-noreturn-calls=never`.
 
 When a No_Return call returns control to its caller through an
 exception, verification may have already been performed before the
-call, if :switch:`-fhardcfr-check-noreturn-calls=always` is in effect.
-The compiler arranges for already-checked No_Return calls without a
+call, if :switch:`-fhardcfr-check-noreturn-calls=always` or
+:switch:`-fhardcfr-check-noreturn-calls=not-always` is in effect.  The
+compiler arranges for already-checked No_Return calls without a
 preexisting handler to bypass the implicitly-added cleanup handler and
 thus the redundant check, but a local exception or cleanup handler, if
 present, will modify the set of visited blocks, and checking will take
diff --git a/gcc/ada/gcc-interface/trans.cc b/gcc/ada/gcc-interface/trans.cc
index f2e0cb2299b..64edbccc787 100644
--- a/gcc/ada/gcc-interface/trans.cc
+++ b/gcc/ada/gcc-interface/trans.cc
@@ -514,6 +514,7 @@ gigi (Node_Id gnat_root,
 			   ftype, NULL_TREE,
 			   is_default, true, true, true, false, false, NULL,
 			   Empty);
+  set_call_expr_flags (reraise_zcx_decl, ECF_NORETURN | ECF_THROW);
 
   /* Dummy objects to materialize "others" and "all others" in the exception
      tables.  These are exported by a-exexpr-gcc.adb, so see this unit for
@@ -553,6 +554,7 @@ gigi (Node_Id gnat_root,
 	  (get_identifier ("__gnat_last_chance_handler"), NULL_TREE, ftype,
 	   NULL_TREE, is_default, true, true, true, false, false, NULL,
 	   Empty);
+      set_call_expr_flags (decl, ECF_NORETURN | ECF_THROW);
       for (i = 0; i < (int) ARRAY_SIZE (gnat_raise_decls); i++)
 	gnat_raise_decls[i] = decl;
     }
@@ -716,6 +718,7 @@ build_raise_check (int check, enum exception_info_kind kind)
     = create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
 			   NULL_TREE, is_default, true, true, true, false,
 			   false, NULL, Empty);
+  set_call_expr_flags (result, ECF_NORETURN | ECF_THROW);
 
   return result;
 }
diff --git a/gcc/calls.cc b/gcc/calls.cc
index bc96aff38f0..6f841163aaa 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -844,6 +844,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 983cb4db7c6..afdd6dc76cc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1805,7 +1805,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1816,14 +1816,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 4665a29a24d..49f1e927155 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5132,7 +5132,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
index da0a65c613d..a52b0ae5a1c 100644
--- a/gcc/cp/except.cc
+++ b/gcc/cp/except.cc
@@ -652,12 +652,13 @@ build_throw (location_t loc, tree exp)
 	  tree args[3] = {ptr_type_node, ptr_type_node, cleanup_type};
 
 	  throw_fn = declare_library_fn_1 ("__cxa_throw",
-					   ECF_NORETURN | ECF_COLD,
+					   ECF_NORETURN | ECF_THROW | ECF_COLD,
 					   void_type_node, 3, args);
 	  if (flag_tm && throw_fn != error_mark_node)
 	    {
 	      tree itm_fn = declare_library_fn_1 ("_ITM_cxa_throw",
-						  ECF_NORETURN | ECF_COLD,
+						  ECF_NORETURN | ECF_THROW
+						  | ECF_COLD,
 						  void_type_node, 3, args);
 	      if (itm_fn != error_mark_node)
 		{
@@ -803,7 +804,8 @@ build_throw (location_t loc, tree exp)
       if (!rethrow_fn)
 	{
 	  rethrow_fn = declare_library_fn_1 ("__cxa_rethrow",
-					     ECF_NORETURN | ECF_COLD,
+					     ECF_NORETURN | ECF_THROW
+					     | ECF_COLD,
 					     void_type_node, 0, NULL);
 	  if (flag_tm && rethrow_fn != error_mark_node)
 	    apply_tm_attr (rethrow_fn, get_identifier ("transaction_pure"));
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index f838d11bdb9..2b3beaf58be 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16617,13 +16617,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16643,7 +16645,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 3fae7548cab..d6d3b0e7800 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index cd5b3cf69b2..106a50d9384 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1063,9 +1063,14 @@ public:
    Exception-raising functions, such as C++'s __cxa_throw,
    __cxa_rethrow, and Ada's */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 4d45311b45c..8b5bc72cb31 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1532,6 +1535,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 77ac1495c46..e3f3670ba3d 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3543,6 +3544,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.c
new file mode 100644
index 00000000000..26c0f270716
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-always.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" } */
+
+/* Check the noreturn handling of a builtin call with always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
new file mode 100644
index 00000000000..a9eca9893bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-never.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with never.  */
+
+#include "harden-cfr-abrt.c"
+
+/* No out-of-line checking.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 0 "hardcfr" } } */
+/* Inline checking only before return in f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
new file mode 100644
index 00000000000..a91cc1726dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-not-always.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with not-always.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
new file mode 100644
index 00000000000..24363bdfe57
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt-nothrow.c
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check the noreturn handling of a builtin call with =nothrow.  */
+
+#include "harden-cfr-abrt.c"
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_abort in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
index 4aada93ad94..1ed727317f1 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-abrt.c
@@ -1,11 +1,19 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+/* Check the noreturn handling of a builtin call.  */
+
 int f(int i) {
   if (!i)
     __builtin_abort ();
   return i;
 }
 
-/* No checking before the noreturn abort, so single inline check.  */
+int g() {
+  __builtin_abort ();
+}
+
+/* Out-of-line checking, before both builtin_abort and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
new file mode 100644
index 00000000000..6e0767aad69
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
new file mode 100644
index 00000000000..a454f2b36fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-bret-not-always.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that, even enabling checks before not-always-throwing noreturn calls
+   (leaving returning calls enabled), we get checks before __builtin_return
+   without duplication (__builtin_return is both noreturn and a returning
+   call).  */
+
+#include "harden-cfr-bret.c"
+
+/* Out-of-line checking, before both builtin_return and return in f.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 2 "hardcfr" } } */
+/* Inline checking before builtin_return in g.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
new file mode 100644
index 00000000000..7fe0bb4a663
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-never.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param).  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
+/* No checking for h (too many blocks) or main (no edges to exit block).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
new file mode 100644
index 00000000000..b9eda188201
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-not-always.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=not-always -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters with checking before
+   all noreturn calls that aren't expected to throw.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
new file mode 100644
index 00000000000..da54fc0b57a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr-nothrow.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects -w" } */
+
+/* Check the instrumentation and the parameters without checking before
+   nothrow noreturn calls.  */
+
+#include "harden-cfr.c"
+
+/* Inlined checking thus trap for f.  */
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/c-c++-common/torture/harden-cfr.c b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
index 2f5946c0387..73824c66f50 100644
--- a/gcc/testsuite/c-c++-common/torture/harden-cfr.c
+++ b/gcc/testsuite/c-c++-common/torture/harden-cfr.c
@@ -1,6 +1,8 @@
 /* { dg-do run } */
 /* { dg-options "-fharden-control-flow-redundancy -fdump-tree-hardcfr --param hardcfr-max-blocks=9 --param hardcfr-max-inline-blocks=5 -ffat-lto-objects" } */
 
+/* Check the instrumentation and the parameters.  */
+
 int
 f (int i, int j)
 {
@@ -64,18 +66,19 @@ h (unsigned i, int j) /* { dg-warning "has more than 9 blocks, the requested max
 }
 
 int
-main ()
+main (int argc, char *argv[])
 {
-  if (f (1, 2) != 2 || f (3, 2) != 6
-      || g (2, 5) != 25 || h (4, 3) != 33)
+  if (f (1, 2) != 2 || g (2, 5) != 25 || h (4, 3) != 33
+      || argc < 0)
     __builtin_abort ();
   /* Call exit, instead of returning, to avoid an edge to the exit block and
-     thus implicitly disable hardening of main.  */
+     thus implicitly disable hardening of main, when checking before noreturn
+     calls is disabled.  */
   __builtin_exit (0);
 }
 
 /* Inlined checking thus trap for f.  */
 /* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "hardcfr" } } */
-/* Out-of-line checking for g.  */
-/* { dg-final { scan-tree-dump-times "__hardcfr_check" 1 "hardcfr" } } */
-/* No checking for h (too many blocks) or main (no edges to exit block).  */
+/* Out-of-line checking for g (param), and before both noreturn calls in main.  */
+/* { dg-final { scan-tree-dump-times "__hardcfr_check" 3 "hardcfr" } } */
+/* No checking for h (too many blocks).  */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
index f0338ccc361..f0f3be124cc 100644
--- a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-O0.C
@@ -6,5 +6,5 @@
 
 #include "torture/harden-cfr-throw.C"
 
-/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 14 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
new file mode 100644
index 00000000000..b2df689c932
--- /dev/null
+++ b/gcc/testsuite/g++.dg/harden-cfr-throw-returning-enabled-O0.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-returning-calls -fdump-tree-hardcfr -O0" } */
+
+/* Explicitly enable -fhardcfr-check-returning-calls -at O0.  */
+
+#include "torture/harden-cfr-throw.C"
+
+/* Same expectations as those in torture/harden-cfr-throw-returning.C.  */
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 10 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 2 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
index 0286f6e6d3f..4d303e769ef 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-always.C
@@ -5,13 +5,6 @@
    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" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
new file mode 100644
index 00000000000..81c1b1abae6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-never.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=never -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "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
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..9723d92bfec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
new file mode 100644
index 00000000000..e1c2e8d73bb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nothrow.C
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=nothrow -fdump-tree-hardcfr -ffat-lto-objects" } */
+
+/* Check that we insert cleanups for checking around the bodies of
+   maybe-throwing functions, without checking before noreturn
+   calls.  */
+
+#include "harden-cfr-throw.C"
+
+/* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..0eb274b3209 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,6 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
+#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
+
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
 
@@ -63,3 +70,4 @@ void h4(void) {
 
 /* { dg-final { scan-tree-dump-times "hardcfr_check" 12 "hardcfr" } } */
 /* { dg-final { scan-tree-dump-times "builtin_trap" 1 "hardcfr" } } */
+/* { dg-final { scan-tree-dump-times "Bypassing" 0 "hardcfr" } } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 80c2bcb333d..869e6c4673e 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 2f488e4467c..9c49a1b1570 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9614,6 +9614,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9819,7 +9823,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9828,7 +9833,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

* [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions
@ 2022-09-09  7:08 Alexandre Oliva
  0 siblings, 0 replies; 8+ messages in thread
From: Alexandre Oliva @ 2022-09-09  7:08 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:47534e005fce67306e587bedcbeeaab551e7b69d

commit 47534e005fce67306e587bedcbeeaab551e7b69d
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Fri Sep 9 01:44:34 2022 -0300

    hardcfr: mark expected-throw functions

Diff:
---
 gcc/ada/doc/gnat_rm/security_hardening_features.rst | 17 +++++++++--------
 gcc/ada/gcc-interface/trans.cc                      |  3 +++
 gcc/calls.cc                                        |  3 +++
 gcc/common.opt                                      | 12 +++---------
 gcc/cp/decl.cc                                      |  3 ++-
 gcc/doc/invoke.texi                                 | 12 ++++++++----
 gcc/flag-types.h                                    |  4 ++--
 gcc/gimple-harden-control-flow.cc                   |  9 +++++++--
 gcc/gimple.cc                                       |  5 +++++
 gcc/gimple.h                                        | 21 +++++++++++++++++++++
 .../g++.dg/torture/harden-cfr-throw-nocleanup.C     |  2 +-
 .../g++.dg/torture/harden-cfr-throw-not-always.C    | 20 ++++++++++++++++++++
 .../g++.dg/torture/harden-cfr-throw-returning.C     |  2 +-
 gcc/testsuite/g++.dg/torture/harden-cfr-throw.C     |  2 +-
 gcc/tree-core.h                                     |  3 +++
 gcc/tree.cc                                         |  9 +++++++--
 16 files changed, 96 insertions(+), 31 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 4dfda486795..225dedd3541 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -367,17 +367,18 @@ gets modified as follows:
    end;
 
 
-Verification may also be performed before No_Return calls, whether
-only nothrow ones, with
-:switch:`-fhardcfr-check-noreturn-calls=nothrow`, or all of them, with
-:switch:`-fhardcfr-check-noreturn-calls=always`.  The default is
-:switch:`-fhardcfr-check-noreturn-calls=never` for this feature, that
-disables checking before No_Return calls.
+Verification may also be performed before No_Return calls, whether all
+of them, with :switch:`-fhardcfr-check-noreturn-calls=always`; all but
+internal subprograms involved in exception-raising or -reraising, with
+:switch:`-fhardcfr-check-noreturn-calls=not-always` (default); only
+nothrow ones, with :switch:`-fhardcfr-check-noreturn-calls=nothrow`;
+or none, with :switch:`-fhardcfr-check-noreturn-calls=never`.
 
 When a No_Return call returns control to its caller through an
 exception, verification may have already been performed before the
-call, if :switch:`-fhardcfr-check-noreturn-calls=always` is in effect.
-The compiler arranges for already-checked No_Return calls without a
+call, if :switch:`-fhardcfr-check-noreturn-calls=always` or
+:switch:`-fhardcfr-check-noreturn-calls=not-always` is in effect.  The
+compiler arranges for already-checked No_Return calls without a
 preexisting handler to bypass the implicitly-added cleanup handler and
 thus the redundant check, but a local exception or cleanup handler, if
 present, will modify the set of visited blocks, and checking will take
diff --git a/gcc/ada/gcc-interface/trans.cc b/gcc/ada/gcc-interface/trans.cc
index f2e0cb2299b..64edbccc787 100644
--- a/gcc/ada/gcc-interface/trans.cc
+++ b/gcc/ada/gcc-interface/trans.cc
@@ -514,6 +514,7 @@ gigi (Node_Id gnat_root,
 			   ftype, NULL_TREE,
 			   is_default, true, true, true, false, false, NULL,
 			   Empty);
+  set_call_expr_flags (reraise_zcx_decl, ECF_NORETURN | ECF_THROW);
 
   /* Dummy objects to materialize "others" and "all others" in the exception
      tables.  These are exported by a-exexpr-gcc.adb, so see this unit for
@@ -553,6 +554,7 @@ gigi (Node_Id gnat_root,
 	  (get_identifier ("__gnat_last_chance_handler"), NULL_TREE, ftype,
 	   NULL_TREE, is_default, true, true, true, false, false, NULL,
 	   Empty);
+      set_call_expr_flags (decl, ECF_NORETURN | ECF_THROW);
       for (i = 0; i < (int) ARRAY_SIZE (gnat_raise_decls); i++)
 	gnat_raise_decls[i] = decl;
     }
@@ -716,6 +718,7 @@ build_raise_check (int check, enum exception_info_kind kind)
     = create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
 			   NULL_TREE, is_default, true, true, true, false,
 			   false, NULL, Empty);
+  set_call_expr_flags (result, ECF_NORETURN | ECF_THROW);
 
   return result;
 }
diff --git a/gcc/calls.cc b/gcc/calls.cc
index bc96aff38f0..6f841163aaa 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -844,6 +844,9 @@ flags_from_decl_or_type (const_tree exp)
 	    flags |= ECF_TM_PURE;
 	}
 
+      if (lookup_attribute ("expected_throw", DECL_ATTRIBUTES (exp)))
+	flags |= ECF_THROW;
+
       flags = special_function_p (exp, flags);
     }
   else if (TYPE_P (exp))
diff --git a/gcc/common.opt b/gcc/common.opt
index 983cb4db7c6..afdd6dc76cc 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1805,7 +1805,7 @@ 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.
+-fhardcfr-check-noreturn-calls=[always|not-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)
@@ -1816,14 +1816,8 @@ 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(not-always) Value(HCFRNR_NOT_ALWAYS)
 
 EnumValue
 Enum(hardcfr_check_noreturn_calls) String(always) Value(HCFRNR_ALWAYS)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 4665a29a24d..49f1e927155 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -5132,7 +5132,8 @@ push_cp_library_fn (enum tree_code operator_code, tree type,
 tree
 push_throw_library_fn (tree name, tree type)
 {
-  tree fn = push_library_fn (name, type, NULL_TREE, ECF_NORETURN | ECF_COLD);
+  tree fn = push_library_fn (name, type, NULL_TREE,
+			     ECF_NORETURN | ECF_THROW | ECF_COLD);
   return fn;
 }
 \f
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index f838d11bdb9..2b3beaf58be 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16617,13 +16617,15 @@ are enabled (see @option{-foptimize-sibling-calls}), but it can be
 enabled (or disabled, using its negated form) explicitly, regardless of
 the optimizations.
 
-@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}nothrow@r{|}never@r{]}
+@item -fhardcfr-check-noreturn-calls=@r{[}always@r{|}not-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).
+aren't expected to return control to the caller through an exception
+(@option{not-always}, the default), those that may not return control to
+the caller through an exception either (@option{nothrow}), or none of
+them (@option{never}).
 
 Checking before a @code{noreturn} function that may return control to
 the caller through an exception may cause checking to be performed more
@@ -16643,7 +16645,9 @@ 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.
+made @option{not-always} the default setting for this option: it
+excludes from the @code{noreturn} treatment only internal functions used
+to (re)raise exceptions, that are not affected by these optimizations.
 
 @item -fstack-protector
 @opindex fstack-protector
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 3fae7548cab..d6d3b0e7800 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -162,9 +162,9 @@ enum hardcfr_noret
 {
   HCFRNR_NEVER,
   HCFRNR_NOTHROW,
-  HCFRNR_NOT_ALWAYS, /* Reserved for future use.  */
+  HCFRNR_NOT_ALWAYS,
+  HCFRNR_UNSPECIFIED,
   HCFRNR_ALWAYS,
-  HCFRNR_UNSPECIFIED = -1
 };
 
 /* The live patching level.  */
diff --git a/gcc/gimple-harden-control-flow.cc b/gcc/gimple-harden-control-flow.cc
index 1c93bf622e8..1b7e0228197 100644
--- a/gcc/gimple-harden-control-flow.cc
+++ b/gcc/gimple-harden-control-flow.cc
@@ -1063,9 +1063,14 @@ public:
    Exception-raising functions, such as C++'s __cxa_throw,
    __cxa_rethrow, and Ada's */
 static bool
-always_throwing_noreturn_call_p (gimple *)
+always_throwing_noreturn_call_p (gimple *stmt)
 {
-  return false;
+  if (!is_a <gcall *> (stmt))
+    return is_a <gresx *> (stmt);
+
+  gcall *call = as_a <gcall *> (stmt);
+  return (gimple_call_noreturn_p (call)
+	  && gimple_call_expected_throw_p (call));
 }
 
 /* Control flow redundancy hardening: record the execution path, and
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index 4d45311b45c..8b5bc72cb31 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -399,6 +399,9 @@ gimple_build_call_from_tree (tree t, tree fnptrtype)
     gimple_call_set_from_thunk (call, CALL_FROM_THUNK_P (t));
   gimple_call_set_va_arg_pack (call, CALL_EXPR_VA_ARG_PACK (t));
   gimple_call_set_nothrow (call, TREE_NOTHROW (t));
+  if (fndecl)
+    gimple_call_set_expected_throw (call,
+				    flags_from_decl_or_type (fndecl) & ECF_THROW);
   gimple_call_set_by_descriptor (call, CALL_EXPR_BY_DESCRIPTOR (t));
   copy_warning (call, t);
 
@@ -1532,6 +1535,8 @@ gimple_call_flags (const gimple *stmt)
 
   if (stmt->subcode & GF_CALL_NOTHROW)
     flags |= ECF_NOTHROW;
+  if (stmt->subcode & GF_CALL_EXPECTED_THROW)
+    flags |= ECF_THROW;
 
   if (stmt->subcode & GF_CALL_BY_DESCRIPTOR)
     flags |= ECF_BY_DESCRIPTOR;
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 77ac1495c46..e3f3670ba3d 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -150,6 +150,7 @@ enum gf_mask {
     GF_CALL_BY_DESCRIPTOR	= 1 << 10,
     GF_CALL_NOCF_CHECK		= 1 << 11,
     GF_CALL_FROM_NEW_OR_DELETE	= 1 << 12,
+    GF_CALL_EXPECTED_THROW	= 1 << 13,
     GF_OMP_PARALLEL_COMBINED	= 1 << 0,
     GF_OMP_TASK_TASKLOOP	= 1 << 0,
     GF_OMP_TASK_TASKWAIT	= 1 << 1,
@@ -3543,6 +3544,26 @@ gimple_call_nothrow_p (gcall *s)
   return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
 }
 
+/* If NOTHROW_P is true, GIMPLE_CALL S is a call that is known to not throw
+   even if the called function can throw in other cases.  */
+
+static inline void
+gimple_call_set_expected_throw (gcall *s, bool expected_throw_p)
+{
+  if (expected_throw_p)
+    s->subcode |= GF_CALL_EXPECTED_THROW;
+  else
+    s->subcode &= ~GF_CALL_EXPECTED_THROW;
+}
+
+/* Return true if S is a nothrow call.  */
+
+static inline bool
+gimple_call_expected_throw_p (gcall *s)
+{
+  return (gimple_call_flags (s) & ECF_THROW) != 0;
+}
+
 /* If FOR_VAR is true, GIMPLE_CALL S is a call to builtin_alloca that
    is known to be emitted for VLA objects.  Those are wrapped by
    stack_save/stack_restore calls and hence can't lead to unbounded
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
index 885b0b236af..9f359363d17 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-nocleanup.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-exceptions  -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we do not insert cleanups for checking around the bodies
    of maybe-throwing functions.  h4 doesn't get any checks, because we
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
new file mode 100644
index 00000000000..2ec091fec7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-not-always.C
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fhardcfr-check-noreturn-calls=not-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" } } */
+/* h4.  */
+/* { dg-final { scan-tree-dump-times "Bypassing" 1 "hardcfr" } } */
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
index 32def637255..37e4551d096 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw-returning.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -foptimize-sibling-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  These results depend on checking before
diff --git a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
index 992fbdad381..743c3a13fca 100644
--- a/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
+++ b/gcc/testsuite/g++.dg/torture/harden-cfr-throw.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fharden-control-flow-redundancy -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
+/* { dg-options "-fharden-control-flow-redundancy -fhardcfr-check-noreturn-calls=never -fno-hardcfr-check-returning-calls -fdump-tree-hardcfr -ffat-lto-objects" } */
 
 /* Check that we insert cleanups for checking around the bodies of
    maybe-throwing functions.  */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 80c2bcb333d..869e6c4673e 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -96,6 +96,9 @@ struct die_struct;
 /* Nonzero if this is a cold function.  */
 #define ECF_COLD		  (1 << 15)
 
+/* Nonzero if this is a function expected to end with an exception.  */
+#define ECF_THROW		  (1 << 16)
+
 /* Call argument flags.  */
 
 /* Nonzero if the argument is not used by the function.  */
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 2f488e4467c..9c49a1b1570 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -9614,6 +9614,10 @@ set_call_expr_flags (tree decl, int flags)
 		   DECL_ATTRIBUTES (decl));
   if ((flags & ECF_TM_PURE) && flag_tm)
     apply_tm_attr (decl, get_identifier ("transaction_pure"));
+  if ((flags & ECF_THROW))
+    DECL_ATTRIBUTES (decl)
+      = tree_cons (get_identifier ("expected_throw"),
+		   NULL, DECL_ATTRIBUTES (decl));
   /* Looping const or pure is implied by noreturn.
      There is currently no way to declare looping const or looping pure alone.  */
   gcc_assert (!(flags & ECF_LOOPING_CONST_OR_PURE)
@@ -9819,7 +9823,8 @@ build_common_builtin_nodes (void)
       ftype = build_function_type_list (void_type_node, NULL_TREE);
       local_define_builtin ("__builtin_cxa_end_cleanup", ftype,
 			    BUILT_IN_CXA_END_CLEANUP,
-			    "__cxa_end_cleanup", ECF_NORETURN | ECF_LEAF);
+			    "__cxa_end_cleanup",
+			    ECF_NORETURN | ECF_THROW | ECF_LEAF);
     }
 
   ftype = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE);
@@ -9828,7 +9833,7 @@ build_common_builtin_nodes (void)
 			((targetm_common.except_unwind_info (&global_options)
 			  == UI_SJLJ)
 			 ? "_Unwind_SjLj_Resume" : "_Unwind_Resume"),
-			ECF_NORETURN);
+			ECF_NORETURN | ECF_THROW);
 
   if (builtin_decl_explicit (BUILT_IN_RETURN_ADDRESS) == NULL_TREE)
     {

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

end of thread, other threads:[~2022-10-25  2:51 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-01  4:50 [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions Alexandre Oliva
  -- strict thread matches above, loose matches on Subject: below --
2022-10-25  2:51 Alexandre Oliva
2022-10-20 22:31 Alexandre Oliva
2022-10-20  4:09 Alexandre Oliva
2022-10-06 10:31 Alexandre Oliva
2022-09-10  4:38 Alexandre Oliva
2022-09-10  3:04 Alexandre Oliva
2022-09-09  7:08 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).