Skip to content

Implement package exceptions3 for MISRA C++ #901

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
71 changes: 71 additions & 0 deletions cpp/common/src/codingstandards/cpp/Function.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}

Expand Down
61 changes: 61 additions & 0 deletions cpp/common/src/codingstandards/cpp/exclusions/cpp/Exceptions3.qll
Original file line number Diff line number Diff line change
@@ -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()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Declarations
import ExceptionSafety
import Exceptions1
import Exceptions2
import Exceptions3
import Expressions
import FloatingPoint
import Freed
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
25 changes: 25 additions & 0 deletions cpp/misra/src/rules/RULE-18-3-2/ClassExceptionCaughtByValue.ql
Original file line number Diff line number Diff line change
@@ -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."
Copy link
Preview

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] When concatenating the type in the select message, use type.toString() or type.getQualifiedName() to ensure a clear textual representation.

Suggested change
select catch, "Catch block catches a class type '" + type + "' by value, which can lead to slicing."
select catch, "Catch block catches a class type '" + type.getQualifiedName() + "' by value, which can lead to slicing."

Copilot uses AI. Check for mistakes.

Loading
Loading