Skip to content

Commit f03f787

Browse files
authored
Merge pull request #497 from github/lcartey/exceptions-exclude-noexcept
Exceptions: do not rethrow from functions with incompatible exception specifications.
2 parents 0a23725 + 793f9a7 commit f03f787

File tree

7 files changed

+137
-14
lines changed

7 files changed

+137
-14
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
* Exceptions are no longer propagated from calls to `noexcept` functions, or calls functions with dynamic exception specifications where the exception is not permitted. This is consistent with the default behaviour specified in `[expect.spec]` which indicates that `std::terminate` is called. This has the following impact:
2+
- `A15-4-2`, `ERR55-CPP` - reduce false positives for `noexcept` functions which call other `noexcept` function which may throw.
3+
- `A15-2-2` - reduce false positives for constructors which call `noexcept` functions.
4+
- `A15-4-5` - reduce false positives for checked exceptions that are thrown from `noexcept` functions called by the original function.
5+
- `DCL57-CPP` - do not report exceptions thrown from `noexcept` functions called by deallocation functions or destructors.
6+
- `A15-5-1`, `M15-3-1` - do not report exceptions thrown from `noexcept` functions called by special functions.
Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
edges
22
| test.cpp:5:3:5:20 | throw ... [ExceptionA] | test.cpp:4:6:4:15 | test_throw [ExceptionA] |
3-
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:11:3:11:8 | call to throwA [ExceptionA] |
4-
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:15:3:15:8 | call to throwA [ExceptionA] |
3+
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:9:25:9:30 | call to throwA [ExceptionA] |
4+
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:10:42:10:47 | call to throwA [ExceptionA] |
5+
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:13:3:13:8 | call to throwA [ExceptionA] |
6+
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:17:3:17:8 | call to throwA [ExceptionA] |
57
| test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:8:6:8:11 | throwA [ExceptionA] |
6-
| test.cpp:11:3:11:8 | call to throwA [ExceptionA] | test.cpp:10:6:10:24 | test_indirect_throw [ExceptionA] |
7-
| test.cpp:15:3:15:8 | call to throwA [ExceptionA] | test.cpp:14:6:14:26 | test_indirect_throw_2 [ExceptionA] |
8+
| test.cpp:9:6:9:19 | indirectThrowA [ExceptionA] | test.cpp:34:3:34:16 | call to indirectThrowA [ExceptionA] |
9+
| test.cpp:9:25:9:30 | call to throwA [ExceptionA] | test.cpp:9:6:9:19 | indirectThrowA [ExceptionA] |
10+
| test.cpp:10:42:10:47 | call to throwA [ExceptionA] | test.cpp:10:6:10:27 | noexceptIndirectThrowA [ExceptionA] |
11+
| test.cpp:13:3:13:8 | call to throwA [ExceptionA] | test.cpp:12:6:12:24 | test_indirect_throw [ExceptionA] |
12+
| test.cpp:17:3:17:8 | call to throwA [ExceptionA] | test.cpp:16:6:16:26 | test_indirect_throw_2 [ExceptionA] |
13+
| test.cpp:34:3:34:16 | call to indirectThrowA [ExceptionA] | test.cpp:33:6:33:26 | test_indirect_throw_6 [ExceptionA] |
814
#select
915
| test.cpp:4:6:4:15 | test_throw | test.cpp:5:3:5:20 | throw ... [ExceptionA] | test.cpp:4:6:4:15 | test_throw [ExceptionA] | Function test_throw is declared noexcept(true) but can throw exceptions of type ExceptionA. |
10-
| test.cpp:10:6:10:24 | test_indirect_throw | test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:10:6:10:24 | test_indirect_throw [ExceptionA] | Function test_indirect_throw is declared noexcept(true) but can throw exceptions of type ExceptionA. |
11-
| test.cpp:14:6:14:26 | test_indirect_throw_2 | test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:14:6:14:26 | test_indirect_throw_2 [ExceptionA] | Function test_indirect_throw_2 is declared noexcept(true) but can throw exceptions of type ExceptionA. |
16+
| test.cpp:10:6:10:27 | noexceptIndirectThrowA | test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:10:6:10:27 | noexceptIndirectThrowA [ExceptionA] | Function noexceptIndirectThrowA is declared noexcept(true) but can throw exceptions of type ExceptionA. |
17+
| test.cpp:12:6:12:24 | test_indirect_throw | test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:12:6:12:24 | test_indirect_throw [ExceptionA] | Function test_indirect_throw is declared noexcept(true) but can throw exceptions of type ExceptionA. |
18+
| test.cpp:16:6:16:26 | test_indirect_throw_2 | test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:16:6:16:26 | test_indirect_throw_2 [ExceptionA] | Function test_indirect_throw_2 is declared noexcept(true) but can throw exceptions of type ExceptionA. |
19+
| test.cpp:33:6:33:26 | test_indirect_throw_6 | test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:33:6:33:26 | test_indirect_throw_6 [ExceptionA] | Function test_indirect_throw_6 is declared noexcept(true) but can throw exceptions of type ExceptionA. |

cpp/autosar/test/rules/A15-4-2/test.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ void test_throw() noexcept(true) {
66
}
77

88
void throwA() { throw ExceptionA(); }
9+
void indirectThrowA() { throwA(); }
10+
void noexceptIndirectThrowA() noexcept { throwA(); } // NON_COMPLIANT
911

1012
void test_indirect_throw() noexcept(true) {
1113
throwA(); // NON_COMPLIANT - function marked as noexcept(true)
@@ -21,4 +23,13 @@ void test_indirect_throw_3() noexcept(false) {
2123

2224
void test_indirect_throw_4() {
2325
throwA(); // COMPLIANT - function marked as noexcept(false)
26+
}
27+
28+
void test_indirect_throw_5() noexcept {
29+
noexceptIndirectThrowA(); // COMPLIANT - noexceptIndirectThrowA would call
30+
// std::terminate() if ExceptionA is thrown
31+
}
32+
33+
void test_indirect_throw_6() noexcept {
34+
indirectThrowA(); // NON_COMPLIANT
2435
}
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
11
edges
2+
| test_dynamic_specification.cpp:9:6:9:22 | throw_logic_error [logic_error] | test_dynamic_specification.cpp:34:3:34:19 | call to throw_logic_error [logic_error] |
3+
| test_dynamic_specification.cpp:9:6:9:22 | throw_logic_error [logic_error] | test_dynamic_specification.cpp:38:3:38:19 | call to throw_logic_error [logic_error] |
4+
| test_dynamic_specification.cpp:9:6:9:22 | throw_logic_error [logic_error] | test_dynamic_specification.cpp:43:3:43:19 | call to throw_logic_error [logic_error] |
5+
| test_dynamic_specification.cpp:9:28:9:58 | throw ... [logic_error] | test_dynamic_specification.cpp:9:6:9:22 | throw_logic_error [logic_error] |
26
| test_dynamic_specification.cpp:22:3:22:24 | throw ... [exception] | test_dynamic_specification.cpp:20:6:20:49 | test_simple_exception_spec_covered_inherited [exception] |
37
| test_dynamic_specification.cpp:29:3:29:24 | throw ... [exception] | test_dynamic_specification.cpp:28:6:28:30 | test_no_throw_contravened [exception] |
4-
| test_no_except.cpp:8:3:8:14 | throw ... [char *] | test_no_except.cpp:7:6:7:23 | test_noexcept_true [char *] |
8+
| test_dynamic_specification.cpp:33:6:33:31 | indirect_throw_logic_error [logic_error] | test_dynamic_specification.cpp:48:3:48:28 | call to indirect_throw_logic_error [logic_error] |
9+
| test_dynamic_specification.cpp:34:3:34:19 | call to throw_logic_error [logic_error] | test_dynamic_specification.cpp:33:6:33:31 | indirect_throw_logic_error [logic_error] |
10+
| test_dynamic_specification.cpp:38:3:38:19 | call to throw_logic_error [logic_error] | test_dynamic_specification.cpp:37:6:37:46 | indirect_throw_logic_error_but_terminates [logic_error] |
11+
| test_dynamic_specification.cpp:43:3:43:19 | call to throw_logic_error [logic_error] | test_dynamic_specification.cpp:41:6:41:48 | indirect_throw_logic_error_but_terminates_2 [logic_error] |
12+
| test_dynamic_specification.cpp:48:3:48:28 | call to indirect_throw_logic_error [logic_error] | test_dynamic_specification.cpp:47:6:47:25 | test_indirect_throws [logic_error] |
13+
| test_no_except.cpp:4:3:4:20 | throw ... [ExceptionA] | test_no_except.cpp:3:6:3:15 | test_throw [ExceptionA] |
14+
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:8:25:8:30 | call to throwA [ExceptionA] |
15+
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:9:42:9:47 | call to throwA [ExceptionA] |
16+
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:12:3:12:8 | call to throwA [ExceptionA] |
17+
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:16:3:16:8 | call to throwA [ExceptionA] |
18+
| test_no_except.cpp:7:17:7:34 | throw ... [ExceptionA] | test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] |
19+
| test_no_except.cpp:8:6:8:19 | indirectThrowA [ExceptionA] | test_no_except.cpp:33:3:33:16 | call to indirectThrowA [ExceptionA] |
20+
| test_no_except.cpp:8:25:8:30 | call to throwA [ExceptionA] | test_no_except.cpp:8:6:8:19 | indirectThrowA [ExceptionA] |
21+
| test_no_except.cpp:9:42:9:47 | call to throwA [ExceptionA] | test_no_except.cpp:9:6:9:27 | noexceptIndirectThrowA [ExceptionA] |
22+
| test_no_except.cpp:12:3:12:8 | call to throwA [ExceptionA] | test_no_except.cpp:11:6:11:24 | test_indirect_throw [ExceptionA] |
23+
| test_no_except.cpp:16:3:16:8 | call to throwA [ExceptionA] | test_no_except.cpp:15:6:15:26 | test_indirect_throw_2 [ExceptionA] |
24+
| test_no_except.cpp:33:3:33:16 | call to indirectThrowA [ExceptionA] | test_no_except.cpp:32:6:32:26 | test_indirect_throw_6 [ExceptionA] |
525
#select
626
| test_dynamic_specification.cpp:20:6:20:49 | test_simple_exception_spec_covered_inherited | test_dynamic_specification.cpp:22:3:22:24 | throw ... [exception] | test_dynamic_specification.cpp:20:6:20:49 | test_simple_exception_spec_covered_inherited [exception] | test_simple_exception_spec_covered_inherited can throw an exception of type std::exception but has a dynamic exception specification that does not specify this type. |
727
| test_dynamic_specification.cpp:28:6:28:30 | test_no_throw_contravened | test_dynamic_specification.cpp:29:3:29:24 | throw ... [exception] | test_dynamic_specification.cpp:28:6:28:30 | test_no_throw_contravened [exception] | test_no_throw_contravened can throw an exception of type std::exception but has a dynamic exception specification that does not specify this type. |
8-
| test_no_except.cpp:7:6:7:23 | test_noexcept_true | test_no_except.cpp:8:3:8:14 | throw ... [char *] | test_no_except.cpp:7:6:7:23 | test_noexcept_true [char *] | test_noexcept_true can throw an exception of type char * but is marked noexcept(true). |
28+
| test_dynamic_specification.cpp:37:6:37:46 | indirect_throw_logic_error_but_terminates | test_dynamic_specification.cpp:9:28:9:58 | throw ... [logic_error] | test_dynamic_specification.cpp:37:6:37:46 | indirect_throw_logic_error_but_terminates [logic_error] | indirect_throw_logic_error_but_terminates can throw an exception of type std::logic_error but has a dynamic exception specification that does not specify this type. |
29+
| test_dynamic_specification.cpp:41:6:41:48 | indirect_throw_logic_error_but_terminates_2 | test_dynamic_specification.cpp:9:28:9:58 | throw ... [logic_error] | test_dynamic_specification.cpp:41:6:41:48 | indirect_throw_logic_error_but_terminates_2 [logic_error] | indirect_throw_logic_error_but_terminates_2 can throw an exception of type std::logic_error but has a dynamic exception specification that does not specify this type. |
30+
| test_dynamic_specification.cpp:47:6:47:25 | test_indirect_throws | test_dynamic_specification.cpp:9:28:9:58 | throw ... [logic_error] | test_dynamic_specification.cpp:47:6:47:25 | test_indirect_throws [logic_error] | test_indirect_throws can throw an exception of type std::logic_error but has a dynamic exception specification that does not specify this type. |
31+
| test_no_except.cpp:3:6:3:15 | test_throw | test_no_except.cpp:4:3:4:20 | throw ... [ExceptionA] | test_no_except.cpp:3:6:3:15 | test_throw [ExceptionA] | test_throw can throw an exception of type ExceptionA but is marked noexcept(true). |
32+
| test_no_except.cpp:9:6:9:27 | noexceptIndirectThrowA | test_no_except.cpp:7:17:7:34 | throw ... [ExceptionA] | test_no_except.cpp:9:6:9:27 | noexceptIndirectThrowA [ExceptionA] | noexceptIndirectThrowA can throw an exception of type ExceptionA but is marked noexcept(true). |
33+
| test_no_except.cpp:11:6:11:24 | test_indirect_throw | test_no_except.cpp:7:17:7:34 | throw ... [ExceptionA] | test_no_except.cpp:11:6:11:24 | test_indirect_throw [ExceptionA] | test_indirect_throw can throw an exception of type ExceptionA but is marked noexcept(true). |
34+
| test_no_except.cpp:15:6:15:26 | test_indirect_throw_2 | test_no_except.cpp:7:17:7:34 | throw ... [ExceptionA] | test_no_except.cpp:15:6:15:26 | test_indirect_throw_2 [ExceptionA] | test_indirect_throw_2 can throw an exception of type ExceptionA but is marked noexcept(true). |
35+
| test_no_except.cpp:32:6:32:26 | test_indirect_throw_6 | test_no_except.cpp:7:17:7:34 | throw ... [ExceptionA] | test_no_except.cpp:32:6:32:26 | test_indirect_throw_6 [ExceptionA] | test_indirect_throw_6 can throw an exception of type ExceptionA but is marked noexcept(true). |

cpp/cert/test/rules/ERR55-CPP/test_dynamic_specification.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,28 @@ void test_no_throw() throw() { // COMPLIANT
2727

2828
void test_no_throw_contravened() throw() { // NON_COMPLIANT
2929
throw std::exception();
30+
}
31+
32+
class DummyException {};
33+
void indirect_throw_logic_error() throw(std::logic_error) {
34+
throw_logic_error(); // Exception flows out of function as specification is
35+
// compatible
36+
}
37+
void indirect_throw_logic_error_but_terminates() throw() { // NON_COMPLIANT
38+
throw_logic_error(); // Exception does not flow out of function due to
39+
// specification
40+
}
41+
void indirect_throw_logic_error_but_terminates_2() // NON_COMPLIANT
42+
throw(DummyException) {
43+
throw_logic_error(); // Exception does not flow out of function due to
44+
// specification
45+
}
46+
47+
void test_indirect_throws() throw() { // NON_COMPLIANT
48+
indirect_throw_logic_error();
49+
}
50+
51+
void test_indirect_throws_but_terminated() throw() { // COMPLIANT
52+
indirect_throw_logic_error_but_terminates();
53+
indirect_throw_logic_error_but_terminates_2();
3054
}
Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
1-
#include <stdexcept>
1+
class ExceptionA {};
22

3-
void test_noexcept_false() { // COMPLIANT
4-
throw "test";
3+
void test_throw() noexcept(true) {
4+
throw ExceptionA(); // NON_COMPLIANT - function marked as noexcept(true)
55
}
66

7-
void test_noexcept_true() noexcept(true) { // NON_COMPLIANT
8-
throw "test";
7+
void throwA() { throw ExceptionA(); }
8+
void indirectThrowA() { throwA(); }
9+
void noexceptIndirectThrowA() noexcept { throwA(); } // NON_COMPLIANT
10+
11+
void test_indirect_throw() noexcept(true) {
12+
throwA(); // NON_COMPLIANT - function marked as noexcept(true)
13+
}
14+
15+
void test_indirect_throw_2() noexcept {
16+
throwA(); // NON_COMPLIANT - function marked as noexcept(true)
17+
}
18+
19+
void test_indirect_throw_3() noexcept(false) {
20+
throwA(); // COMPLIANT - function marked as noexcept(false)
21+
}
22+
23+
void test_indirect_throw_4() {
24+
throwA(); // COMPLIANT - function marked as noexcept(false)
25+
}
26+
27+
void test_indirect_throw_5() noexcept {
28+
noexceptIndirectThrowA(); // COMPLIANT - noexceptIndirectThrowA would call
29+
// std::terminate() if ExceptionA is thrown
30+
}
31+
32+
void test_indirect_throw_6() noexcept {
33+
indirectThrowA(); // NON_COMPLIANT
934
}

cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import cpp
66
import codingstandards.cpp.standardlibrary.Exceptions
7+
import codingstandards.cpp.exceptions.ExceptionSpecifications
78
import ThirdPartyExceptions
89

910
/*
@@ -312,7 +313,28 @@ class ReThrowExprThrowingExpr extends ReThrowExpr, ThrowingExpr {
312313

313314
/** An expression which calls a function which may throw an exception. */
314315
class FunctionCallThrowingExpr extends FunctionCall, ThrowingExpr {
315-
override ExceptionType getAnExceptionType() { result = getAFunctionThrownType(getTarget(), _) }
316+
override ExceptionType getAnExceptionType() {
317+
exists(Function target |
318+
target = getTarget() and
319+
result = getAFunctionThrownType(target, _) and
320+
// [expect.spec] states that throwing an exception type that is prohibited
321+
// by the specification will result in the program terminating, unless
322+
// a custom `unexpected_handler` is registered that throws an exception type
323+
// which is compatible with the dynamic exception specification, or the
324+
// dynamic exception specification lists `std::bad_exception`, in which case
325+
// a `std::bad_exception` is thrown.
326+
// As dynamic exception specifications and the `unexpected_handler` are both
327+
// deprecated in C++14 and removed in C++17, we assume a default
328+
// `std::unexpected` handler that calls `std::terminate` and therefore
329+
// do not propagate such exceptions to the call sites for the function.
330+
not (
331+
hasDynamicExceptionSpecification(target) and
332+
not result = getAHandledExceptionType(target.getAThrownType())
333+
or
334+
isNoExceptTrue(target)
335+
)
336+
)
337+
}
316338
}
317339

318340
module ExceptionPathGraph {

0 commit comments

Comments
 (0)