Skip to content

Exceptions: do not rethrow from functions with incompatible exception specifications. #497

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* 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:
- `A15-4-2`, `ERR55-CPP` - reduce false positives for `noexcept` functions which call other `noexcept` function which may throw.
- `A15-2-2` - reduce false positives for constructors which call `noexcept` functions.
- `A15-4-5` - reduce false positives for checked exceptions that are thrown from `noexcept` functions called by the original function.
- `DCL57-CPP` - do not report exceptions thrown from `noexcept` functions called by deallocation functions or destructors.
- `A15-5-1`, `M15-3-1` - do not report exceptions thrown from `noexcept` functions called by special functions.
20 changes: 14 additions & 6 deletions cpp/autosar/test/rules/A15-4-2/NoExceptFunctionThrows.expected
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
edges
| test.cpp:5:3:5:20 | throw ... [ExceptionA] | test.cpp:4:6:4:15 | test_throw [ExceptionA] |
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:11:3:11:8 | call to throwA [ExceptionA] |
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:15:3:15:8 | call to throwA [ExceptionA] |
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:9:25:9:30 | call to throwA [ExceptionA] |
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:10:42:10:47 | call to throwA [ExceptionA] |
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:13:3:13:8 | call to throwA [ExceptionA] |
| test.cpp:8:6:8:11 | throwA [ExceptionA] | test.cpp:17:3:17:8 | call to throwA [ExceptionA] |
| test.cpp:8:17:8:34 | throw ... [ExceptionA] | test.cpp:8:6:8:11 | throwA [ExceptionA] |
| test.cpp:11:3:11:8 | call to throwA [ExceptionA] | test.cpp:10:6:10:24 | test_indirect_throw [ExceptionA] |
| test.cpp:15:3:15:8 | call to throwA [ExceptionA] | test.cpp:14:6:14:26 | test_indirect_throw_2 [ExceptionA] |
| test.cpp:9:6:9:19 | indirectThrowA [ExceptionA] | test.cpp:34:3:34:16 | call to indirectThrowA [ExceptionA] |
| test.cpp:9:25:9:30 | call to throwA [ExceptionA] | test.cpp:9:6:9:19 | indirectThrowA [ExceptionA] |
| test.cpp:10:42:10:47 | call to throwA [ExceptionA] | test.cpp:10:6:10:27 | noexceptIndirectThrowA [ExceptionA] |
| test.cpp:13:3:13:8 | call to throwA [ExceptionA] | test.cpp:12:6:12:24 | test_indirect_throw [ExceptionA] |
| test.cpp:17:3:17:8 | call to throwA [ExceptionA] | test.cpp:16:6:16:26 | test_indirect_throw_2 [ExceptionA] |
| test.cpp:34:3:34:16 | call to indirectThrowA [ExceptionA] | test.cpp:33:6:33:26 | test_indirect_throw_6 [ExceptionA] |
#select
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
11 changes: 11 additions & 0 deletions cpp/autosar/test/rules/A15-4-2/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ void test_throw() noexcept(true) {
}

void throwA() { throw ExceptionA(); }
void indirectThrowA() { throwA(); }
void noexceptIndirectThrowA() noexcept { throwA(); } // NON_COMPLIANT

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

void test_indirect_throw_4() {
throwA(); // COMPLIANT - function marked as noexcept(false)
}

void test_indirect_throw_5() noexcept {
noexceptIndirectThrowA(); // COMPLIANT - noexceptIndirectThrowA would call
// std::terminate() if ExceptionA is thrown
}

void test_indirect_throw_6() noexcept {
indirectThrowA(); // NON_COMPLIANT
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
edges
| 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] |
| 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] |
| 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] |
| test_dynamic_specification.cpp:9:28:9:58 | throw ... [logic_error] | test_dynamic_specification.cpp:9:6:9:22 | throw_logic_error [logic_error] |
| 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_dynamic_specification.cpp:29:3:29:24 | throw ... [exception] | test_dynamic_specification.cpp:28:6:28:30 | test_no_throw_contravened [exception] |
| test_no_except.cpp:8:3:8:14 | throw ... [char *] | test_no_except.cpp:7:6:7:23 | test_noexcept_true [char *] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| test_no_except.cpp:4:3:4:20 | throw ... [ExceptionA] | test_no_except.cpp:3:6:3:15 | test_throw [ExceptionA] |
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:8:25:8:30 | call to throwA [ExceptionA] |
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:9:42:9:47 | call to throwA [ExceptionA] |
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:12:3:12:8 | call to throwA [ExceptionA] |
| test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] | test_no_except.cpp:16:3:16:8 | call to throwA [ExceptionA] |
| test_no_except.cpp:7:17:7:34 | throw ... [ExceptionA] | test_no_except.cpp:7:6:7:11 | throwA [ExceptionA] |
| test_no_except.cpp:8:6:8:19 | indirectThrowA [ExceptionA] | test_no_except.cpp:33:3:33:16 | call to indirectThrowA [ExceptionA] |
| test_no_except.cpp:8:25:8:30 | call to throwA [ExceptionA] | test_no_except.cpp:8:6:8:19 | indirectThrowA [ExceptionA] |
| test_no_except.cpp:9:42:9:47 | call to throwA [ExceptionA] | test_no_except.cpp:9:6:9:27 | noexceptIndirectThrowA [ExceptionA] |
| test_no_except.cpp:12:3:12:8 | call to throwA [ExceptionA] | test_no_except.cpp:11:6:11:24 | test_indirect_throw [ExceptionA] |
| 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] |
| 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] |
#select
| 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. |
| 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. |
| 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). |
| 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. |
| 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. |
| 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. |
| 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). |
| 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). |
| 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). |
| 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). |
| 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). |
24 changes: 24 additions & 0 deletions cpp/cert/test/rules/ERR55-CPP/test_dynamic_specification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,28 @@ void test_no_throw() throw() { // COMPLIANT

void test_no_throw_contravened() throw() { // NON_COMPLIANT
throw std::exception();
}

class DummyException {};
void indirect_throw_logic_error() throw(std::logic_error) {
throw_logic_error(); // Exception flows out of function as specification is
// compatible
}
void indirect_throw_logic_error_but_terminates() throw() { // NON_COMPLIANT
throw_logic_error(); // Exception does not flow out of function due to
// specification
}
void indirect_throw_logic_error_but_terminates_2() // NON_COMPLIANT
throw(DummyException) {
throw_logic_error(); // Exception does not flow out of function due to
// specification
}

void test_indirect_throws() throw() { // NON_COMPLIANT
indirect_throw_logic_error();
}

void test_indirect_throws_but_terminated() throw() { // COMPLIANT
indirect_throw_logic_error_but_terminates();
indirect_throw_logic_error_but_terminates_2();
}
35 changes: 30 additions & 5 deletions cpp/cert/test/rules/ERR55-CPP/test_no_except.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
#include <stdexcept>
class ExceptionA {};

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

void test_noexcept_true() noexcept(true) { // NON_COMPLIANT
throw "test";
void throwA() { throw ExceptionA(); }
void indirectThrowA() { throwA(); }
void noexceptIndirectThrowA() noexcept { throwA(); } // NON_COMPLIANT

void test_indirect_throw() noexcept(true) {
throwA(); // NON_COMPLIANT - function marked as noexcept(true)
}

void test_indirect_throw_2() noexcept {
throwA(); // NON_COMPLIANT - function marked as noexcept(true)
}

void test_indirect_throw_3() noexcept(false) {
throwA(); // COMPLIANT - function marked as noexcept(false)
}

void test_indirect_throw_4() {
throwA(); // COMPLIANT - function marked as noexcept(false)
}

void test_indirect_throw_5() noexcept {
noexceptIndirectThrowA(); // COMPLIANT - noexceptIndirectThrowA would call
// std::terminate() if ExceptionA is thrown
}

void test_indirect_throw_6() noexcept {
indirectThrowA(); // NON_COMPLIANT
}
24 changes: 23 additions & 1 deletion cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import cpp
import codingstandards.cpp.standardlibrary.Exceptions
import codingstandards.cpp.exceptions.ExceptionSpecifications
import ThirdPartyExceptions

/*
Expand Down Expand Up @@ -312,7 +313,28 @@ class ReThrowExprThrowingExpr extends ReThrowExpr, ThrowingExpr {

/** An expression which calls a function which may throw an exception. */
class FunctionCallThrowingExpr extends FunctionCall, ThrowingExpr {
override ExceptionType getAnExceptionType() { result = getAFunctionThrownType(getTarget(), _) }
override ExceptionType getAnExceptionType() {
exists(Function target |
target = getTarget() and
result = getAFunctionThrownType(target, _) and
// [expect.spec] states that throwing an exception type that is prohibited
// by the specification will result in the program terminating, unless
// a custom `unexpected_handler` is registered that throws an exception type
// which is compatible with the dynamic exception specification, or the
// dynamic exception specification lists `std::bad_exception`, in which case
// a `std::bad_exception` is thrown.
// As dynamic exception specifications and the `unexpected_handler` are both
// deprecated in C++14 and removed in C++17, we assume a default
// `std::unexpected` handler that calls `std::terminate` and therefore
// do not propagate such exceptions to the call sites for the function.
not (
hasDynamicExceptionSpecification(target) and
not result = getAHandledExceptionType(target.getAThrownType())
or
isNoExceptTrue(target)
)
)
}
}

module ExceptionPathGraph {
Expand Down