From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2140) id 7EA513858285; Fri, 9 Sep 2022 07:08:23 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7EA513858285 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1662707303; bh=zIvuEmfYVX+hW2FbhJ3b0n9rHahd1tzlgN0MYv2Q3XM=; h=From:To:Subject:Date:From; b=tfgAUC40rzNZ3F+p/B7kjtL3WymABANKUTMJu+eJwrGqW81FJ1iIg5G1JFimttCkQ hreIoZJHJM9lHV+j60TK2auG0RhCkPdmZMj3m/EG4gYaLjNkYx20ehxcQNp34+Yn4W Hz5/5S8/X07xZA5eidz74AchFisiFWEf8+cZvBu4= Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: Alexandre Oliva To: gcc-cvs@gcc.gnu.org Subject: [gcc(refs/users/aoliva/heads/testme)] hardcfr: mark expected-throw functions X-Act-Checkin: gcc X-Git-Author: Alexandre Oliva X-Git-Refname: refs/users/aoliva/heads/testme X-Git-Oldrev: b9db93e1ebce7eb838355723eb872c1c84327f16 X-Git-Newrev: 47534e005fce67306e587bedcbeeaab551e7b69d Message-Id: <20220909070823.7EA513858285@sourceware.org> Date: Fri, 9 Sep 2022 07:08:23 +0000 (GMT) List-Id: https://gcc.gnu.org/g:47534e005fce67306e587bedcbeeaab551e7b69d commit 47534e005fce67306e587bedcbeeaab551e7b69d Author: Alexandre Oliva 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; } 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 (stmt)) + return is_a (stmt); + + gcall *call = as_a (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) {