diff --git a/change_notes/2024-01-24-throwing-functions-exclude-noexcept.md b/change_notes/2024-01-24-throwing-functions-exclude-noexcept.md new file mode 100644 index 0000000000..4752123832 --- /dev/null +++ b/change_notes/2024-01-24-throwing-functions-exclude-noexcept.md @@ -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. \ No newline at end of file diff --git a/cpp/autosar/test/rules/A15-4-2/NoExceptFunctionThrows.expected b/cpp/autosar/test/rules/A15-4-2/NoExceptFunctionThrows.expected index 2a0726c356..b2f8391b15 100644 --- a/cpp/autosar/test/rules/A15-4-2/NoExceptFunctionThrows.expected +++ b/cpp/autosar/test/rules/A15-4-2/NoExceptFunctionThrows.expected @@ -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. | diff --git a/cpp/autosar/test/rules/A15-4-2/test.cpp b/cpp/autosar/test/rules/A15-4-2/test.cpp index afa46e5ae6..de5e00bd35 100644 --- a/cpp/autosar/test/rules/A15-4-2/test.cpp +++ b/cpp/autosar/test/rules/A15-4-2/test.cpp @@ -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) @@ -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 } \ No newline at end of file diff --git a/cpp/cert/test/rules/ERR55-CPP/HonorExceptionSpecifications.expected b/cpp/cert/test/rules/ERR55-CPP/HonorExceptionSpecifications.expected index ba234df2b8..5091d1fc2e 100644 --- a/cpp/cert/test/rules/ERR55-CPP/HonorExceptionSpecifications.expected +++ b/cpp/cert/test/rules/ERR55-CPP/HonorExceptionSpecifications.expected @@ -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). | diff --git a/cpp/cert/test/rules/ERR55-CPP/test_dynamic_specification.cpp b/cpp/cert/test/rules/ERR55-CPP/test_dynamic_specification.cpp index 4b218e1847..82e32bd433 100644 --- a/cpp/cert/test/rules/ERR55-CPP/test_dynamic_specification.cpp +++ b/cpp/cert/test/rules/ERR55-CPP/test_dynamic_specification.cpp @@ -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(); } \ No newline at end of file diff --git a/cpp/cert/test/rules/ERR55-CPP/test_no_except.cpp b/cpp/cert/test/rules/ERR55-CPP/test_no_except.cpp index 7897bed237..767dbb7ec0 100644 --- a/cpp/cert/test/rules/ERR55-CPP/test_no_except.cpp +++ b/cpp/cert/test/rules/ERR55-CPP/test_no_except.cpp @@ -1,9 +1,34 @@ -#include +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 } \ No newline at end of file diff --git a/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll b/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll index df23fa4e95..5a4e7fee6e 100644 --- a/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll +++ b/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll @@ -4,6 +4,7 @@ import cpp import codingstandards.cpp.standardlibrary.Exceptions +import codingstandards.cpp.exceptions.ExceptionSpecifications import ThirdPartyExceptions /* @@ -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 {