diff --git a/cpp/common/src/codingstandards/cpp/Function.qll b/cpp/common/src/codingstandards/cpp/Function.qll index c96fcbd840..ae2fd972a1 100644 --- a/cpp/common/src/codingstandards/cpp/Function.qll +++ b/cpp/common/src/codingstandards/cpp/Function.qll @@ -8,3 +8,74 @@ import cpp class PopCount extends Function { PopCount() { this.getName().toLowerCase().matches("%popc%nt%") } } + +/** + * A function the user explicitly declared, though its body may be compiler-generated. + */ +class UserDeclaredFunction extends Function { + UserDeclaredFunction() { + not isDeleted() and + ( + // Not compiler generated is explicitly declared + not isCompilerGenerated() + or + // Closure functions are compiler generated, but the user + // explicitly declared the lambda expression. + exists(Closure c | c.getLambdaFunction() = this) + or + // Indicates users specifically wrote "= default" + isDefaulted() + ) + } +} + +/** + * An expression that is effectively a function access. + * + * There are more ways to effectively access a function than a basic `FunctionAcess`, for instance + * taking the address of a function access, and declaring/converting a lambda function. + */ +abstract class FunctionAccessLikeExpr extends Expr { + abstract Function getFunction(); +} + +/** + * A basic function access expression. + */ +class FunctionAccessExpr extends FunctionAccess, FunctionAccessLikeExpr { + override Function getFunction() { result = this.getTarget() } +} + +/** + * Taking an address of a function access in effectively a function access. + */ +class FunctionAddressExpr extends AddressOfExpr, FunctionAccessLikeExpr { + Function func; + + FunctionAddressExpr() { func = getOperand().(FunctionAccessLikeExpr).getFunction() } + + override Function getFunction() { result = func } +} + +/** + * An expression that declares a lambda function is essentially a function access of the lambda + * function body. + */ +class LambdaValueExpr extends LambdaExpression, FunctionAccessLikeExpr { + override Function getFunction() { result = this.(LambdaExpression).getLambdaFunction() } +} + +/** + * When a lambda is converted via conversion operator to a function pointer, it is + * effectively a function access of the lambda function. + */ +class LambdaConversionExpr extends FunctionCall, FunctionAccessLikeExpr { + Function func; + + LambdaConversionExpr() { + getTarget() instanceof ConversionOperator and + func = getQualifier().(LambdaValueExpr).getFunction() + } + + override Function getFunction() { result = func } +} diff --git a/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll b/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll index d62bc8c02a..c105c29cd2 100644 --- a/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll +++ b/cpp/common/src/codingstandards/cpp/exceptions/ExceptionFlow.qll @@ -121,6 +121,9 @@ TryStmt getNearestTry(Stmt s) { else result = getNearestTry(s.getParent()) } +/** Gets a try statement that contains the given statement. */ +TryStmt getATryStmt(Stmt s) { result.getStmt() = s.getEnclosingStmt().getEnclosingBlock*() } + /** Gets the nearest parent catch block for the given statement. */ CatchBlock getNearestCatch(Stmt s) { if s.getParent() instanceof CatchBlock diff --git a/cpp/common/src/codingstandards/cpp/exceptions/SpecialFunctionExceptions.qll b/cpp/common/src/codingstandards/cpp/exceptions/SpecialFunctionExceptions.qll index c24cfee66d..7179692c13 100644 --- a/cpp/common/src/codingstandards/cpp/exceptions/SpecialFunctionExceptions.qll +++ b/cpp/common/src/codingstandards/cpp/exceptions/SpecialFunctionExceptions.qll @@ -16,22 +16,102 @@ import cpp private import ExceptionFlow +private import codingstandards.cpp.Function private import codingstandards.cpp.Swap private import codingstandards.cpp.EncapsulatingFunctions private import codingstandards.cpp.exceptions.ExceptionFlow /** A special function. */ class SpecialFunction extends Function { + string specialDescription; + SpecialFunction() { - this instanceof MoveAssignmentOperator + this instanceof MoveAssignmentOperator and + specialDescription = "move assignment operator" or - this instanceof MoveConstructor + this instanceof MoveConstructor and + specialDescription = "move constructor" or - this instanceof Destructor + this instanceof Destructor and + specialDescription = "destructor" or - this instanceof DeallocationFunction + this instanceof DeallocationFunction and + specialDescription = "deallocation function" or - this instanceof SwapFunction + this instanceof SwapFunction and + specialDescription = "swap function" + } + + string getSpecialDescription() { result = specialDescription } +} + +/** + * A function which isn't special by itself, but is used in a special context. + * + * For example, functions which are reachable from initializers of static or thread-local + * variables can result in implementation-defined behavior if they throw exceptions. + */ +abstract class SpecialUseOfFunction extends Expr { + abstract Function getFunction(); + + abstract string getSpecialDescription(Locatable extra, string extraString); +} + +class FunctionUsedInInitializer extends FunctionCall, SpecialUseOfFunction { + Variable var; + + FunctionUsedInInitializer() { + (var.isStatic() or var.isThreadLocal() or var.isTopLevel()) and + exists(Expr initializer | + var.getInitializer().getExpr() = initializer and + getParent*() = initializer + ) + } + + override Function getFunction() { result = this.getTarget() } + + override string getSpecialDescription(Locatable extra, string extraString) { + result = "used to initialize variable $@" and + extra = var and + extraString = var.getName() + } +} + +class FunctionGivenToStdExitHandler extends FunctionAccess, SpecialUseOfFunction { + Function exitHandler; + FunctionCall exitHandlerCall; + + FunctionGivenToStdExitHandler() { + exitHandler.hasGlobalOrStdName(["atexit", "at_quick_exit", "set_terminate"]) and + exitHandlerCall.getTarget() = exitHandler and + exitHandlerCall.getArgument(0) = this + } + + override Function getFunction() { result = getTarget() } + + override string getSpecialDescription(Locatable extra, string extraString) { + result = "$@" and + extra = exitHandlerCall and + extraString = "passed exit handler std::" + exitHandler.getName() + } +} + +class FunctionPassedToExternC extends FunctionAccessLikeExpr, SpecialUseOfFunction { + Function cFunction; + FunctionCall cFunctionCall; + + FunctionPassedToExternC() { + cFunction.hasCLinkage() and + cFunction = cFunctionCall.getTarget() and + cFunctionCall.getAnArgument() = this + } + + override Function getFunction() { result = this.(FunctionAccessLikeExpr).getFunction() } + + override string getSpecialDescription(Locatable extra, string extraString) { + result = "$@ extern \"C\" function '" + cFunction.getName() + "'" and + extra = cFunctionCall and + extraString = "passed to" } } diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Exceptions3.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Exceptions3.qll new file mode 100644 index 0000000000..d4faa1f929 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Exceptions3.qll @@ -0,0 +1,61 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Exceptions3Query = + TMissingCatchAllExceptionHandlerInMainQuery() or + TClassExceptionCaughtByValueQuery() or + TExceptionUnfriendlyFunctionMustBeNoexceptQuery() + +predicate isExceptions3QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `missingCatchAllExceptionHandlerInMain` query + Exceptions3Package::missingCatchAllExceptionHandlerInMainQuery() and + queryId = + // `@id` for the `missingCatchAllExceptionHandlerInMain` query + "cpp/misra/missing-catch-all-exception-handler-in-main" and + ruleId = "RULE-18-3-1" and + category = "advisory" + or + query = + // `Query` instance for the `classExceptionCaughtByValue` query + Exceptions3Package::classExceptionCaughtByValueQuery() and + queryId = + // `@id` for the `classExceptionCaughtByValue` query + "cpp/misra/class-exception-caught-by-value" and + ruleId = "RULE-18-3-2" and + category = "required" + or + query = + // `Query` instance for the `exceptionUnfriendlyFunctionMustBeNoexcept` query + Exceptions3Package::exceptionUnfriendlyFunctionMustBeNoexceptQuery() and + queryId = + // `@id` for the `exceptionUnfriendlyFunctionMustBeNoexcept` query + "cpp/misra/exception-unfriendly-function-must-be-noexcept" and + ruleId = "RULE-18-4-1" and + category = "required" +} + +module Exceptions3Package { + Query missingCatchAllExceptionHandlerInMainQuery() { + //autogenerate `Query` type + result = + // `Query` type for `missingCatchAllExceptionHandlerInMain` query + TQueryCPP(TExceptions3PackageQuery(TMissingCatchAllExceptionHandlerInMainQuery())) + } + + Query classExceptionCaughtByValueQuery() { + //autogenerate `Query` type + result = + // `Query` type for `classExceptionCaughtByValue` query + TQueryCPP(TExceptions3PackageQuery(TClassExceptionCaughtByValueQuery())) + } + + Query exceptionUnfriendlyFunctionMustBeNoexceptQuery() { + //autogenerate `Query` type + result = + // `Query` type for `exceptionUnfriendlyFunctionMustBeNoexcept` query + TQueryCPP(TExceptions3PackageQuery(TExceptionUnfriendlyFunctionMustBeNoexceptQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index abd6aeff96..1a4ef09c42 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -17,6 +17,7 @@ import Declarations import ExceptionSafety import Exceptions1 import Exceptions2 +import Exceptions3 import Expressions import FloatingPoint import Freed @@ -72,6 +73,7 @@ newtype TCPPQuery = TExceptionSafetyPackageQuery(ExceptionSafetyQuery q) or TExceptions1PackageQuery(Exceptions1Query q) or TExceptions2PackageQuery(Exceptions2Query q) or + TExceptions3PackageQuery(Exceptions3Query q) or TExpressionsPackageQuery(ExpressionsQuery q) or TFloatingPointPackageQuery(FloatingPointQuery q) or TFreedPackageQuery(FreedQuery q) or @@ -127,6 +129,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isExceptionSafetyQueryMetadata(query, queryId, ruleId, category) or isExceptions1QueryMetadata(query, queryId, ruleId, category) or isExceptions2QueryMetadata(query, queryId, ruleId, category) or + isExceptions3QueryMetadata(query, queryId, ruleId, category) or isExpressionsQueryMetadata(query, queryId, ruleId, category) or isFloatingPointQueryMetadata(query, queryId, ruleId, category) or isFreedQueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/misra/src/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql b/cpp/misra/src/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql new file mode 100644 index 0000000000..051e29edc0 --- /dev/null +++ b/cpp/misra/src/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql @@ -0,0 +1,56 @@ +/** + * @id cpp/misra/missing-catch-all-exception-handler-in-main + * @name RULE-18-3-1: There should be at least one exception handler to catch all otherwise unhandled exceptions + * @description The main function should have a catch-all exception handler (catch(...)) to catch + * all otherwise unhandled exceptions. + * @kind problem + * @precision high + * @problem.severity warning + * @tags external/misra/id/rule-18-3-1 + * scope/single-translation-unit + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/advisory + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.exceptions.ExceptionFlow +import codingstandards.cpp.exceptions.ExceptionSpecifications + +/** + * A function call in main that is not declared noexcept and is not within a catch-all + * exception handler (catch(...)). + */ +class UncaughtFunctionCallInMain extends FunctionCall { + UncaughtFunctionCallInMain() { + getEnclosingFunction().hasName("main") and + not isNoExceptTrue(getTarget()) and + not exists(TryStmt try | + try = getATryStmt(this.getEnclosingStmt()) and + try.getCatchClause(_) instanceof CatchAnyBlock + ) + } + + /** + * We only want to report one counter-example indicating a missing catch(...), so this holds only + * for the first one we find. + */ + predicate isFirst() { + this = + rank[1](UncaughtFunctionCallInMain fc | + any() + | + fc order by fc.getLocation().getStartLine(), fc.getLocation().getStartColumn() + ) + } +} + +from Function f, UncaughtFunctionCallInMain fc +where + not isExcluded(f, Exceptions3Package::missingCatchAllExceptionHandlerInMainQuery()) and + f.getName() = "main" and + fc.isFirst() +select f, + "Main function has a $@ which is not within a try block with a catch-all ('catch(...)') handler.", + fc, "function call that may throw" diff --git a/cpp/misra/src/rules/RULE-18-3-2/ClassExceptionCaughtByValue.ql b/cpp/misra/src/rules/RULE-18-3-2/ClassExceptionCaughtByValue.ql new file mode 100644 index 0000000000..1e3f5ba746 --- /dev/null +++ b/cpp/misra/src/rules/RULE-18-3-2/ClassExceptionCaughtByValue.ql @@ -0,0 +1,25 @@ +/** + * @id cpp/misra/class-exception-caught-by-value + * @name RULE-18-3-2: An exception of class type shall be caught by const reference or reference + * @description Catching exception classes by value can lead to slicing, which can result in + * unexpected behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-18-3-2 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from CatchBlock catch, Type type +where + not isExcluded(catch, Exceptions3Package::classExceptionCaughtByValueQuery()) and + type = catch.getParameter().getType() and + type.stripTopLevelSpecifiers() instanceof Class +select catch, "Catch block catches a class type '" + type + "' by value, which can lead to slicing." diff --git a/cpp/misra/src/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.ql b/cpp/misra/src/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.ql new file mode 100644 index 0000000000..eae5b46a53 --- /dev/null +++ b/cpp/misra/src/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.ql @@ -0,0 +1,39 @@ +/** + * @id cpp/misra/exception-unfriendly-function-must-be-noexcept + * @name RULE-18-4-1: Exception-unfriendly functions shall be noexcept + * @description Throwing exceptions in constructors, descructors, copy-constructors, move + * constructors, assignments, and functions named swap, may result in + * implementation-defined behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-18-4-1 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.Function +import codingstandards.cpp.exceptions.ExceptionFlow +import codingstandards.cpp.exceptions.SpecialFunctionExceptions + +from UserDeclaredFunction func, string message, Element extra, string extraString +where + not isExcluded([func, extra], Exceptions3Package::exceptionUnfriendlyFunctionMustBeNoexceptQuery()) and + not isNoExceptTrue(func) and + ( + message = "a " + func.(SpecialFunction).getSpecialDescription() and + extra = func and + extraString = "" + or + exists(SpecialUseOfFunction specialUse | + specialUse.getFunction() = func and + message = specialUse.getSpecialDescription(extra, extraString) + ) + ) +select func, "Function '" + func.getName() + "' must be noexcept because it is " + message + ".", + extra, extraString diff --git a/cpp/misra/test/rules/RULE-18-3-1.1/MissingCatchAllExceptionHandlerInMain.expected b/cpp/misra/test/rules/RULE-18-3-1.1/MissingCatchAllExceptionHandlerInMain.expected new file mode 100644 index 0000000000..efdf46b09b --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.1/MissingCatchAllExceptionHandlerInMain.expected @@ -0,0 +1 @@ +| test_missing.cpp:6:5:6:8 | main | Main function has a $@ which is not within a try block with a catch-all ('catch(...)') handler. | test_missing.cpp:9:5:9:5 | call to f | function call that may throw | diff --git a/cpp/misra/test/rules/RULE-18-3-1.1/MissingCatchAllExceptionHandlerInMain.qlref b/cpp/misra/test/rules/RULE-18-3-1.1/MissingCatchAllExceptionHandlerInMain.qlref new file mode 100644 index 0000000000..aa0bf98361 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.1/MissingCatchAllExceptionHandlerInMain.qlref @@ -0,0 +1 @@ +rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.1/test_missing.cpp b/cpp/misra/test/rules/RULE-18-3-1.1/test_missing.cpp new file mode 100644 index 0000000000..9bf360c7a9 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.1/test_missing.cpp @@ -0,0 +1,14 @@ +#include +void f() noexcept(false); + +void noex() noexcept; + +int main() { + noex(); + try { + f(); + } catch (int x) { + // NON-COMPLIANT: No catch(...) handler. + noex(); + } +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.2/MissingCatchAllExceptionHandlerInMain.expected b/cpp/misra/test/rules/RULE-18-3-1.2/MissingCatchAllExceptionHandlerInMain.expected new file mode 100644 index 0000000000..8bfbf64d4b --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.2/MissingCatchAllExceptionHandlerInMain.expected @@ -0,0 +1 @@ +| test_except_before_catch.cpp:7:5:7:8 | main | Main function has a $@ which is not within a try block with a catch-all ('catch(...)') handler. | test_except_before_catch.cpp:8:3:8:3 | call to f | function call that may throw | diff --git a/cpp/misra/test/rules/RULE-18-3-1.2/MissingCatchAllExceptionHandlerInMain.qlref b/cpp/misra/test/rules/RULE-18-3-1.2/MissingCatchAllExceptionHandlerInMain.qlref new file mode 100644 index 0000000000..aa0bf98361 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.2/MissingCatchAllExceptionHandlerInMain.qlref @@ -0,0 +1 @@ +rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.2/test_except_before_catch.cpp b/cpp/misra/test/rules/RULE-18-3-1.2/test_except_before_catch.cpp new file mode 100644 index 0000000000..293db03d7a --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.2/test_except_before_catch.cpp @@ -0,0 +1,17 @@ +#include + +void f(); + +void noex() noexcept; + +int main() { + f(); // NON-COMPLIANT: f() can throw and is outside of the try-catch(...) + // block. + try { + noex(); + f(); + } catch (...) { + noex(); + } + noex(); +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.3/MissingCatchAllExceptionHandlerInMain.expected b/cpp/misra/test/rules/RULE-18-3-1.3/MissingCatchAllExceptionHandlerInMain.expected new file mode 100644 index 0000000000..f02cbc8f6d --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.3/MissingCatchAllExceptionHandlerInMain.expected @@ -0,0 +1 @@ +| test_except_after_catch.cpp:7:5:7:8 | main | Main function has a $@ which is not within a try block with a catch-all ('catch(...)') handler. | test_except_after_catch.cpp:16:3:16:3 | call to f | function call that may throw | diff --git a/cpp/misra/test/rules/RULE-18-3-1.3/MissingCatchAllExceptionHandlerInMain.qlref b/cpp/misra/test/rules/RULE-18-3-1.3/MissingCatchAllExceptionHandlerInMain.qlref new file mode 100644 index 0000000000..aa0bf98361 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.3/MissingCatchAllExceptionHandlerInMain.qlref @@ -0,0 +1 @@ +rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.3/test_except_after_catch.cpp b/cpp/misra/test/rules/RULE-18-3-1.3/test_except_after_catch.cpp new file mode 100644 index 0000000000..c061366abf --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.3/test_except_after_catch.cpp @@ -0,0 +1,17 @@ +#include + +void f(); + +void noex() noexcept; + +int main() { + noex(); + try { + noex(); + f(); + } catch (...) { + noex(); + } + // NON-COMPLIANT: f() can throw and is outside of the try-catch(...) block. + f(); +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.4/MissingCatchAllExceptionHandlerInMain.expected b/cpp/misra/test/rules/RULE-18-3-1.4/MissingCatchAllExceptionHandlerInMain.expected new file mode 100644 index 0000000000..9668a24fc4 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.4/MissingCatchAllExceptionHandlerInMain.expected @@ -0,0 +1 @@ +| test_except_inside_catch.cpp:7:5:7:8 | main | Main function has a $@ which is not within a try block with a catch-all ('catch(...)') handler. | test_except_inside_catch.cpp:14:15:14:15 | call to operator<< | function call that may throw | diff --git a/cpp/misra/test/rules/RULE-18-3-1.4/MissingCatchAllExceptionHandlerInMain.qlref b/cpp/misra/test/rules/RULE-18-3-1.4/MissingCatchAllExceptionHandlerInMain.qlref new file mode 100644 index 0000000000..aa0bf98361 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.4/MissingCatchAllExceptionHandlerInMain.qlref @@ -0,0 +1 @@ +rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.4/test_except_inside_catch.cpp b/cpp/misra/test/rules/RULE-18-3-1.4/test_except_inside_catch.cpp new file mode 100644 index 0000000000..4be15f7736 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.4/test_except_inside_catch.cpp @@ -0,0 +1,17 @@ +#include + +void f(); + +void noex() noexcept; + +int main() { + noex(); + try { + noex(); + f(); + } catch (...) { + // NON-COMPLIANT: cout can throw and is not within a try block. + std::cout << "Caught unknown exception" << std::endl; + } + noex(); +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.5/MissingCatchAllExceptionHandlerInMain.expected b/cpp/misra/test/rules/RULE-18-3-1.5/MissingCatchAllExceptionHandlerInMain.expected new file mode 100644 index 0000000000..0d055c9f38 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.5/MissingCatchAllExceptionHandlerInMain.expected @@ -0,0 +1 @@ +| test_noexcept_false.cpp:7:5:7:8 | main | Main function has a $@ which is not within a try block with a catch-all ('catch(...)') handler. | test_noexcept_false.cpp:8:3:8:3 | call to f | function call that may throw | diff --git a/cpp/misra/test/rules/RULE-18-3-1.5/MissingCatchAllExceptionHandlerInMain.qlref b/cpp/misra/test/rules/RULE-18-3-1.5/MissingCatchAllExceptionHandlerInMain.qlref new file mode 100644 index 0000000000..aa0bf98361 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.5/MissingCatchAllExceptionHandlerInMain.qlref @@ -0,0 +1 @@ +rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1.5/test_noexcept_false.cpp b/cpp/misra/test/rules/RULE-18-3-1.5/test_noexcept_false.cpp new file mode 100644 index 0000000000..71304661de --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1.5/test_noexcept_false.cpp @@ -0,0 +1,17 @@ +#include + +void f() noexcept(false); + +void noex() noexcept; + +int main() { + f(); // NON-COMPLIANT: f() can throw and is outside of the try-catch(...) + // block. + try { + f(); + noex(); + } catch (...) { + noex(); + } + noex(); +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.expected b/cpp/misra/test/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.expected new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cpp/misra/test/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.qlref b/cpp/misra/test/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.qlref new file mode 100644 index 0000000000..aa0bf98361 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.qlref @@ -0,0 +1 @@ +rules/RULE-18-3-1/MissingCatchAllExceptionHandlerInMain.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-1/test_compliant.cpp b/cpp/misra/test/rules/RULE-18-3-1/test_compliant.cpp new file mode 100644 index 0000000000..74733cbe93 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-1/test_compliant.cpp @@ -0,0 +1,29 @@ +#include + +void f(); + +void noex() noexcept; +void noex2() noexcept(true); + +int main() { + noex(); + noex2(); + try { + noex(); + noex2(); + f(); + } catch (...) { + try { + noex(); + noex2(); + std::cout << "Caught unknown exception" << std::endl; + } catch (...) { + // COMPLIANT: All exceptions caught via catch(...), even exceptions from + // cout. + noex(); + noex2(); + } + } + noex(); + noex2(); +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-2/ClassExceptionCaughtByValue.expected b/cpp/misra/test/rules/RULE-18-3-2/ClassExceptionCaughtByValue.expected new file mode 100644 index 0000000000..e958a9d0c6 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-2/ClassExceptionCaughtByValue.expected @@ -0,0 +1,8 @@ +| test.cpp:25:18:27:3 | { ... } | Catch block catches a class type 's1' by value, which can lead to slicing. | +| test.cpp:27:24:29:3 | { ... } | Catch block catches a class type 'const s1' by value, which can lead to slicing. | +| test.cpp:37:24:39:3 | { ... } | Catch block catches a class type 'const C1' by value, which can lead to slicing. | +| test.cpp:39:18:41:3 | { ... } | Catch block catches a class type 'C1' by value, which can lead to slicing. | +| test.cpp:49:20:51:3 | { ... } | Catch block catches a class type 's1_t' by value, which can lead to slicing. | +| test.cpp:51:26:53:3 | { ... } | Catch block catches a class type 's1_const_t' by value, which can lead to slicing. | +| test.cpp:53:20:55:3 | { ... } | Catch block catches a class type 'C1_t' by value, which can lead to slicing. | +| test.cpp:55:26:57:3 | { ... } | Catch block catches a class type 'C1_const_t' by value, which can lead to slicing. | diff --git a/cpp/misra/test/rules/RULE-18-3-2/ClassExceptionCaughtByValue.qlref b/cpp/misra/test/rules/RULE-18-3-2/ClassExceptionCaughtByValue.qlref new file mode 100644 index 0000000000..f8c874ae79 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-2/ClassExceptionCaughtByValue.qlref @@ -0,0 +1 @@ +rules/RULE-18-3-2/ClassExceptionCaughtByValue.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-3-2/test.cpp b/cpp/misra/test/rules/RULE-18-3-2/test.cpp new file mode 100644 index 0000000000..9750d2e0c0 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-3-2/test.cpp @@ -0,0 +1,92 @@ +struct s1 { + int m; +}; + +class C1 {}; + +typedef s1 s1_t; +typedef const s1 s1_const_t; +typedef C1 C1_t; +typedef const C1 C1_const_t; + +void f() { + try { + + } catch (int i) { + // COMPLIANT: Primitives will not be sliced when caught by value. + } catch (long l) { + // COMPLIANT: Primitives will not be sliced when caught by value. + } catch (float f) { + // COMPLIANT: Primitives will not be sliced when caught by value. + } catch (char c) { + // COMPLIANT: Primitives will not be sliced when caught by value. + } catch (int *p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (s1 s) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (const s1 s) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (s1 *p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (s1 *const p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (s1 &r) { + // COMPLIANT: References will not be sliced when caught by value. + } catch (s1 &const r) { + // COMPLIANT: Const eferences will not be sliced when caught by value. + } catch (const C1 c) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (C1 c) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (C1 *p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (C1 *const p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (C1 &r) { + // COMPLIANT: References will not be sliced when caught by value. + } catch (C1 &const r) { + // COMPLIANT: Const eferences will not be sliced when caught by value. + } catch (s1_t s) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (s1_const_t s) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (C1_t c) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (C1_const_t c) { + // NON-COMPLIANT: User-defined types will be sliced when caught by value. + } catch (s1_t *p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (s1_t *const p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (s1_t &r) { + // COMPLIANT: References will not be sliced when caught by value. + } catch (s1_t &const r) { + // COMPLIANT: Const eferences will not be sliced when caught by value. + } catch (C1_t *p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (C1_t *const p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (C1_t &r) { + // COMPLIANT: References will not be sliced when caught by value. + } catch (C1_t &const r) { + // COMPLIANT: Const eferences will not be sliced when caught by value. + } catch (s1_const_t *p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (s1_const_t *const p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (s1_const_t &r) { + // COMPLIANT: References will not be sliced when caught by value. + } catch (s1_const_t &const r) { + // COMPLIANT: Const eferences will not be sliced when caught by value. + } catch (C1_const_t *p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (C1_const_t *const p) { + // COMPLIANT: Pointers will not be sliced when caught by value. + } catch (C1_const_t &r) { + // COMPLIANT: References will not be sliced when caught by value. + } catch (C1_const_t &const r) { + // COMPLIANT: Const eferences will not be sliced when caught by value. + } catch (...) { + // COMPLIANT: Catch-all handler. + } +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.expected b/cpp/misra/test/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.expected new file mode 100644 index 0000000000..c8bdb89437 --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.expected @@ -0,0 +1,15 @@ +| test.cpp:38:3:38:20 | ~NonCompliantClass | Function '~NonCompliantClass' must be noexcept because it is a destructor. | test.cpp:38:3:38:20 | ~NonCompliantClass | | +| test.cpp:40:3:40:19 | NonCompliantClass | Function 'NonCompliantClass' must be noexcept because it is a move constructor. | test.cpp:40:3:40:19 | NonCompliantClass | | +| test.cpp:49:22:49:30 | operator= | Function 'operator=' must be noexcept because it is a move assignment operator. | test.cpp:49:22:49:30 | operator= | | +| test.cpp:51:15:51:18 | swap | Function 'swap' must be noexcept because it is a swap function. | test.cpp:51:15:51:18 | swap | | +| test.cpp:62:3:62:24 | ConstructorNotNoexcept | Function 'ConstructorNotNoexcept' must be noexcept because it is used to initialize variable $@. | test.cpp:67:5:67:6 | g2 | g2 | +| test.cpp:62:3:62:24 | ConstructorNotNoexcept | Function 'ConstructorNotNoexcept' must be noexcept because it is used to initialize variable $@. | test.cpp:71:5:71:6 | g4 | g4 | +| test.cpp:62:3:62:24 | ConstructorNotNoexcept | Function 'ConstructorNotNoexcept' must be noexcept because it is used to initialize variable $@. | test.cpp:74:25:74:26 | g6 | g6 | +| test.cpp:62:3:62:24 | ConstructorNotNoexcept | Function 'ConstructorNotNoexcept' must be noexcept because it is used to initialize variable $@. | test.cpp:87:7:87:8 | l4 | l4 | +| test.cpp:62:3:62:24 | ConstructorNotNoexcept | Function 'ConstructorNotNoexcept' must be noexcept because it is used to initialize variable $@. | test.cpp:91:7:91:8 | l6 | l6 | +| test.cpp:97:5:97:23 | calledInInitializer | Function 'calledInInitializer' must be noexcept because it is used to initialize variable $@. | test.cpp:100:5:100:6 | g9 | g9 | +| test.cpp:109:5:109:15 | some_func_t | Function 'some_func_t' must be noexcept because it is $@ extern "C" function 'take_int_c'. | test.cpp:116:3:116:12 | call to take_int_c | passed to | +| test.cpp:123:16:123:16 | operator() | Function 'operator()' must be noexcept because it is $@ extern "C" function 'take_int_c'. | test.cpp:123:3:123:12 | call to take_int_c | passed to | +| test.cpp:131:6:131:17 | exit_handler | Function 'exit_handler' must be noexcept because it is $@. | test.cpp:135:3:135:13 | call to atexit | passed exit handler std::atexit | +| test.cpp:131:6:131:17 | exit_handler | Function 'exit_handler' must be noexcept because it is $@. | test.cpp:139:3:139:20 | call to at_quick_exit | passed exit handler std::at_quick_exit | +| test.cpp:131:6:131:17 | exit_handler | Function 'exit_handler' must be noexcept because it is $@. | test.cpp:144:3:144:20 | call to set_terminate | passed exit handler std::set_terminate | diff --git a/cpp/misra/test/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.qlref b/cpp/misra/test/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.qlref new file mode 100644 index 0000000000..860cb6d1eb --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.qlref @@ -0,0 +1 @@ +rules/RULE-18-4-1/ExceptionUnfriendlyFunctionMustBeNoexcept.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-18-4-1/test.cpp b/cpp/misra/test/rules/RULE-18-4-1/test.cpp new file mode 100644 index 0000000000..4a8bd68f8a --- /dev/null +++ b/cpp/misra/test/rules/RULE-18-4-1/test.cpp @@ -0,0 +1,149 @@ +#include +#include + +class CompliantClass { +public: + CompliantClass() noexcept {} // COMPLIANT: constructor is noexcept + ~CompliantClass() {} // COMPLIANT: destructor defaults to noexcept + + // COMPLIANT: copy constructor is noexcept + CompliantClass(const CompliantClass &) noexcept {} + + // COMPLIANT: move constructor is noexcept + CompliantClass(CompliantClass &&) noexcept {} + + // COMPLIANT: copy assignment operator is noexcept + CompliantClass &operator=(const CompliantClass &) noexcept { return *this; } + + // COMPLIANT: move assignment operator is noexcept + CompliantClass &operator=(CompliantClass &&) noexcept { return *this; } + + // COMPLIANT: swap function is noexcept + friend void swap(CompliantClass &a, CompliantClass &b) noexcept; + + void f() {} // COMPLIANT: Not a special function. +}; + +class NonCompliantClass { +public: + // COMPLIANT: constructor doesn't need to be noexcept + NonCompliantClass() {} + + // COMPLIANT: copy constructor doesn't need to be noexcept. It seems the rule + // is satisfied if the move constructor is noexcept. If the move constructor + // is noexcept, the standard containers will safely use it instead of the copy + // constructor. + NonCompliantClass(const NonCompliantClass &) {} // COMPLIANT + + ~NonCompliantClass() noexcept(false) {} // NON-COMPLIANT + + NonCompliantClass(NonCompliantClass &&) {} // NON-COMPLIANT + + // COMPLIANT: copy assignment doesn't need to be noexcept. It seems the rule + // is satisfied if the move assignment operator is noexcept. If the move + // constructor is noexcept, the standard containers will safely use it instead + // of the copy assignment operator. + NonCompliantClass &operator=(const NonCompliantClass &) { return *this; } + + // NON-COMPLIANT + NonCompliantClass &operator=(NonCompliantClass &&) { return *this; } + + friend void swap(NonCompliantClass &a, NonCompliantClass &b) { + } // NON-COMPLIANT +}; + +class DeleteDestructorOk { + DeleteDestructorOk() = delete; // COMPLIANT +}; + +class ConstructorNotNoexcept { +public: + // COMPLIANT: at definition, but checked at use site. + ConstructorNotNoexcept() {} +}; + +CompliantClass g1; // COMPLIANT: static initialization with noexcept +ConstructorNotNoexcept + g2; // NON-COMPLIANT: static initialization without noexcept +thread_local CompliantClass + g3; // COMPLIANT: thread-local initialization with noexcept +thread_local ConstructorNotNoexcept + g4; // NON-COMPLIANT: thread-local initialization without noexcept +CompliantClass *g5 = new CompliantClass(); // COMPLIANT: initializing dynamic + // allocation with noexcept +ConstructorNotNoexcept *g6 = + new ConstructorNotNoexcept(); // NON-COMPLIANT: initializing dynamic + // allocation without noexcept + +// Invalid cpp: cannot use constexpr with out noexcept constructor. +// constexpr CompliantClass g7; +// constexpr ConstructorNotNoexcept g8; + +void f1() { + CompliantClass l1; // COMPLIANT: local initialization with noexcept + ConstructorNotNoexcept l2; // COMPLIANT: local initialization without noexcept + static CompliantClass l3; // COMPLIANT: static initialization with noexcept + static ConstructorNotNoexcept + l4; // NON-COMPLIANT: static initialization without noexcept + thread_local CompliantClass + l5; // COMPLIANT: thread-local initialization with noexcept + thread_local ConstructorNotNoexcept + l6; // NON-COMPLIANT: thread-local initialization without noexcept + new CompliantClass(); // COMPLIANT: dynamic allocation with noexcept + new ConstructorNotNoexcept(); // COMPLIANT: not initializing allocation + // without noexcept +} + +int calledInInitializer() { return 0; } +int calledInInitializerNoexcept() noexcept { return 0; } + +int g9 = calledInInitializer(); // NON-COMPLIANT +int g10 = calledInInitializerNoexcept(); // COMPLIANT + +typedef int func_t(int); +void take_int_cpp(func_t f); +extern "C" { +void take_int_c(func_t f); +} + +int some_func_t(int) { return 0; } +int some_func_t_noexcept(int) noexcept { return 0; } + +void f2() { + take_int_cpp(some_func_t); // COMPLIANT: passing function pointer to cpp + take_int_cpp(some_func_t_noexcept); // COMPLIANT: passing noexcept function + // pointer to cpp + take_int_c(some_func_t); // NON-COMPLIANT: passing function pointer to c + // without noexcept + take_int_c(some_func_t_noexcept); // COMPLIANT: passing function pointer to c + // with noexcept + take_int_cpp([](int) { + return 0; + }); // COMPLIANT: passing noexcept function pointer to cpp + take_int_c([](int) { + return 0; + }); // NON-COMPLIANT: passing function pointer to c without noexcept + take_int_c([](int) noexcept { + return 0; + }); // COMPLIANT: passing function pointer to c with noexcept +} + +void exit_handler() {} +void exit_handler_noexcept() noexcept {} + +void f3() { + std::atexit(exit_handler); // NON-COMPLIANT: passing function pointer to + // atexit without noexcept + std::atexit(exit_handler_noexcept); // COMPLIANT: passing function pointer to + // atexit with noexcept + std::at_quick_exit(exit_handler); // NON-COMPLIANT: passing function pointer + // to at_quick_exit without noexcept + std::at_quick_exit( + exit_handler_noexcept); // COMPLIANT: passing function pointer to + // at_quick_exit with noexcept + std::set_terminate(exit_handler); // NON-COMPLIANT: passing function pointer + // to set_terminate without noexcept + std::set_terminate( + exit_handler_noexcept); // COMPLIANT: passing function pointer to + // set_terminate with noexcept +} \ No newline at end of file diff --git a/rule_packages/cpp/Exceptions3.json b/rule_packages/cpp/Exceptions3.json new file mode 100644 index 0000000000..ae6379ddec --- /dev/null +++ b/rule_packages/cpp/Exceptions3.json @@ -0,0 +1,69 @@ +{ + "MISRA-C++-2023": { + "RULE-18-3-1": { + "properties": { + "enforcement": "decidable", + "obligation": "advisory" + }, + "queries": [ + { + "description": "The main function should have a catch-all exception handler (catch(...)) to catch all otherwise unhandled exceptions.", + "kind": "problem", + "name": "There should be at least one exception handler to catch all otherwise unhandled exceptions", + "precision": "high", + "severity": "warning", + "short_name": "MissingCatchAllExceptionHandlerInMain", + "tags": [ + "scope/single-translation-unit", + "maintainability" + ] + } + ], + "title": "There should be at least one exception handler to catch all otherwise unhandled exceptions" + }, + "RULE-18-3-2": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Catching exception classes by value can lead to slicing, which can result in unexpected behavior.", + "kind": "problem", + "name": "An exception of class type shall be caught by const reference or reference", + "precision": "very-high", + "severity": "error", + "short_name": "ClassExceptionCaughtByValue", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ] + } + ], + "title": "An exception of class type shall be caught by const reference or reference" + }, + "RULE-18-4-1": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Throwing exceptions in constructors, descructors, copy-constructors, move constructors, assignments, and functions named swap, may result in implementation-defined behavior.", + "kind": "problem", + "name": "Exception-unfriendly functions shall be noexcept", + "precision": "very-high", + "severity": "error", + "short_name": "ExceptionUnfriendlyFunctionMustBeNoexcept", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ] + } + ], + "title": "Exception-unfriendly functions shall be noexcept" + } + } +} \ No newline at end of file