diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Declarations5.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Declarations5.qll new file mode 100644 index 000000000..ff4806a5b --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Declarations5.qll @@ -0,0 +1,44 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Declarations5Query = + TMemberFunctionsRefqualifiedQuery() or + TTypeAliasesDeclarationQuery() + +predicate isDeclarations5QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `memberFunctionsRefqualified` query + Declarations5Package::memberFunctionsRefqualifiedQuery() and + queryId = + // `@id` for the `memberFunctionsRefqualified` query + "cpp/misra/member-functions-refqualified" and + ruleId = "RULE-6-8-4" and + category = "advisory" + or + query = + // `Query` instance for the `typeAliasesDeclaration` query + Declarations5Package::typeAliasesDeclarationQuery() and + queryId = + // `@id` for the `typeAliasesDeclaration` query + "cpp/misra/type-aliases-declaration" and + ruleId = "RULE-6-9-1" and + category = "required" +} + +module Declarations5Package { + Query memberFunctionsRefqualifiedQuery() { + //autogenerate `Query` type + result = + // `Query` type for `memberFunctionsRefqualified` query + TQueryCPP(TDeclarations5PackageQuery(TMemberFunctionsRefqualifiedQuery())) + } + + Query typeAliasesDeclarationQuery() { + //autogenerate `Query` type + result = + // `Query` type for `typeAliasesDeclaration` query + TQueryCPP(TDeclarations5PackageQuery(TTypeAliasesDeclarationQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index 5618f4d85..7b55e22bc 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -39,6 +39,7 @@ import Declarations1 import Declarations2 import Declarations3 import Declarations4 +import Declarations5 import Declarations6 import Declarations7 import ExceptionSafety @@ -144,6 +145,7 @@ newtype TCPPQuery = TDeclarations2PackageQuery(Declarations2Query q) or TDeclarations3PackageQuery(Declarations3Query q) or TDeclarations4PackageQuery(Declarations4Query q) or + TDeclarations5PackageQuery(Declarations5Query q) or TDeclarations6PackageQuery(Declarations6Query q) or TDeclarations7PackageQuery(Declarations7Query q) or TExceptionSafetyPackageQuery(ExceptionSafetyQuery q) or @@ -249,6 +251,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isDeclarations2QueryMetadata(query, queryId, ruleId, category) or isDeclarations3QueryMetadata(query, queryId, ruleId, category) or isDeclarations4QueryMetadata(query, queryId, ruleId, category) or + isDeclarations5QueryMetadata(query, queryId, ruleId, category) or isDeclarations6QueryMetadata(query, queryId, ruleId, category) or isDeclarations7QueryMetadata(query, queryId, ruleId, category) or isExceptionSafetyQueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/lifetimes/CppObjects.qll b/cpp/common/src/codingstandards/cpp/lifetimes/CppObjects.qll index 1c38859cb..b82df69bb 100644 --- a/cpp/common/src/codingstandards/cpp/lifetimes/CppObjects.qll +++ b/cpp/common/src/codingstandards/cpp/lifetimes/CppObjects.qll @@ -2,6 +2,7 @@ import cpp import codingstandards.cpp.lifetimes.StorageDuration import semmle.code.cpp.valuenumbering.HashCons import codingstandards.cpp.Clvalues +import codingstandards.cpp.types.Pointers /** * A library for handling "Objects" in C++. @@ -136,19 +137,39 @@ abstract class ObjectIdentityBase extends Element { } /** - * Finds expressions `e.x` or `e[x]` for expression `e`, recursively. Does not resolve pointers. + * Finds expressions `e.x` or `e[x]` for expression `e`, recursively. + * + * Omits accesses to reference fields, as they are not subobjects. * * Note that this does not hold for `e->x` or `e[x]` where `e` is a pointer. */ -private Expr getASubobjectAccessOf(Expr e) { - result = e - or - result.(DotFieldAccess).getQualifier() = getASubobjectAccessOf(e) +Expr getASubobjectAccessOf(Expr e) { + exists(Field f | + not f.getUnderlyingType() instanceof ReferenceType and + f.getAnAccess().(FieldAccess) = result.getAChild*() + ) and + ( + result = e + or + result.(DotFieldAccess).getQualifier() = getASubobjectAccessOf(e) + ) or result.(ArrayExpr).getArrayBase() = getASubobjectAccessOf(e) and not result.(ArrayExpr).getArrayBase().getUnspecifiedType() instanceof PointerType } +/** + * gets an access where the pointee is the subobject + */ +Expr getASubobjectAccessOfPointee(Expr e) { + e.getParent() instanceof AddressOfExpr and + result = getASubobjectAccessOf(e.getParent()) + or + // the accessed field is a pointer to a subobject + e.getParent().(PointerFieldAccess).getTarget().getUnspecifiedType() instanceof PointerType and + result = getASubobjectAccessOf(e.getParent()) +} + /** * Find the object types that are embedded within the current type. * diff --git a/cpp/misra/src/rules/RULE-6-8-4/MemberFunctionsRefqualified.ql b/cpp/misra/src/rules/RULE-6-8-4/MemberFunctionsRefqualified.ql new file mode 100644 index 000000000..6e537bc66 --- /dev/null +++ b/cpp/misra/src/rules/RULE-6-8-4/MemberFunctionsRefqualified.ql @@ -0,0 +1,119 @@ +/** + * @id cpp/misra/member-functions-refqualified + * @name RULE-6-8-4: Member functions returning references to their object should be refqualified appropriately + * @description Member functions that return references to temporary objects (or subobjects) can + * lead to dangling pointers. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-6-8-4 + * correctness + * scope/single-translation-unit + * external/misra/enforcement/decidable + * external/misra/obligation/advisory + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.types.Compatible +import codingstandards.cpp.Operator +import codingstandards.cpp.types.Pointers +import codingstandards.cpp.lifetimes.CppObjects + +class MembersReturningPointerOrRef extends MemberFunction { + MembersReturningPointerOrRef() { this.getType() instanceof PointerLikeType } +} + +abstract class MembersReturningObjectOrSubobject extends MembersReturningPointerOrRef { + string toString() { result = "Members returning object or subobject" } +} + +/** + * Members that have an explicit `this` access within their return statement. + */ +class MembersReturningObject extends MembersReturningObjectOrSubobject { + MembersReturningObject() { + exists(ReturnStmt r, ThisExpr t | + r.getEnclosingFunction() = this and + ( + //direct access only + r.getAChild() = t + or + //or one level of indirection + exists(PointerDereferenceExpr p | + p.getAChild() = t and + r.getAChild() = p + ) + ) and + t.getActualType().stripType() = this.getDeclaringType() + ) + } +} + +/** + * Members that have an explicit subobject access within their return statement. + * + * Specifically, this captures when the return is a reference or pointer + * to a subobject. + */ +class MembersReturningSubObject extends MembersReturningObjectOrSubobject { + MembersReturningSubObject() { + exists(ReturnStmt r, FieldAccess access, Expr e | + r.getEnclosingFunction() = this and + //direct access only + r.getAChild() = e and + ( + //pointer or reference to pointer subobject returned + e = getASubobjectAccessOfPointee(access) and + (e.getType() instanceof PointerType or e.getType() instanceof ReferenceType) + or + //reference to subobject returned + (this.getType() instanceof ReferenceType or e.getType() instanceof ReferenceType) and + not access.getTarget().getType() instanceof PointerType + ) + ) + } +} + +predicate relevantTypes(Type a, Type b) { + exists(MembersReturningObjectOrSubobject f, MemberFunction overload | + f.getAnOverload() = overload and + exists(int i | + f.getParameter(i).getType() = a and + overload.getParameter(i).getType() = b + ) + ) +} + +class AppropriatelyQualified extends MembersReturningObjectOrSubobject { + AppropriatelyQualified() { + //non-const-lvalue-ref-qualified + this.isLValueRefQualified() and + this.getType().(PointerLikeType).pointsToNonConst() + or + //const-lvalue-ref-qualified + this.isLValueRefQualified() and + this.getType().(PointerLikeType).pointsToConst() and + //and overload exists that is rvalue-ref-qualified + exists(MemberFunction overload | + this.getAnOverload() = overload and + overload.isRValueRefQualified() and + //and has same param list + forall(int i | exists([this, overload].getParameter(i)) | + TypeEquivalence::equalTypes(this.getParameter(i) + .getType(), overload.getParameter(i).getType()) + ) + ) + } +} + +class DefaultedAssignmentOperator extends AssignmentOperator { + DefaultedAssignmentOperator() { this.isDefaulted() } +} + +from MembersReturningObjectOrSubobject f +where + not isExcluded(f, Declarations5Package::memberFunctionsRefqualifiedQuery()) and + not f instanceof AppropriatelyQualified and + not f instanceof DefaultedAssignmentOperator +select f, "Member function is not properly ref qualified." diff --git a/cpp/misra/src/rules/RULE-6-9-1/TypeAliasesDeclaration.ql b/cpp/misra/src/rules/RULE-6-9-1/TypeAliasesDeclaration.ql new file mode 100644 index 000000000..d8898377d --- /dev/null +++ b/cpp/misra/src/rules/RULE-6-9-1/TypeAliasesDeclaration.ql @@ -0,0 +1,33 @@ +/** + * @id cpp/misra/type-aliases-declaration + * @name RULE-6-9-1: The same type aliases shall be used in all declarations of the same entity + * @description Using different type aliases on redeclarations can make code hard to understand and + * maintain. + * @kind problem + * @precision very-high + * @problem.severity warning + * @tags external/misra/id/rule-6-9-1 + * maintainability + * readability + * scope/single-translation-unit + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from DeclarationEntry decl1, DeclarationEntry decl2, TypedefType t +where + not isExcluded(decl1, Declarations5Package::typeAliasesDeclarationQuery()) and + not isExcluded(decl2, Declarations5Package::typeAliasesDeclarationQuery()) and + not decl1 = decl2 and + decl1.getDeclaration() = decl2.getDeclaration() and + t.getATypeNameUse() = decl1 and + not t.getATypeNameUse() = decl2 and + //exception cases - we dont want to disallow struct typedef name use + not t.getBaseType() instanceof Struct and + not t.getBaseType() instanceof Enum +select decl1, + "Declaration entry has a different type alias than $@ where the type alias used is '$@'.", decl2, + decl2.getName(), t, t.getName() diff --git a/cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.expected b/cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.expected new file mode 100644 index 000000000..98a5b94c3 --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.expected @@ -0,0 +1,20 @@ +| test.cpp:12:14:12:20 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:24:12:24:23 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:28:6:28:18 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:42:16:42:16 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:42:16:42:16 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:42:16:42:16 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:61:8:61:8 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:71:9:71:10 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:79:8:79:9 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:82:8:82:9 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:85:8:85:9 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:89:9:89:10 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:93:9:93:10 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:103:8:103:8 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:113:9:113:10 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:121:8:121:9 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:124:8:124:9 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:127:8:127:9 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:131:9:131:10 | Members returning object or subobject | Member function is not properly ref qualified. | +| test.cpp:135:9:135:10 | Members returning object or subobject | Member function is not properly ref qualified. | diff --git a/cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.qlref b/cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.qlref new file mode 100644 index 000000000..c214d5ca1 --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-8-4/MemberFunctionsRefqualified.qlref @@ -0,0 +1 @@ +rules/RULE-6-8-4/MemberFunctionsRefqualified.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-6-8-4/test.cpp b/cpp/misra/test/rules/RULE-6-8-4/test.cpp new file mode 100644 index 000000000..066354b41 --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-8-4/test.cpp @@ -0,0 +1,138 @@ +struct A { + int a; + int &b; + + int &geta() & { return a; } // COMPLIANT + + int const &geta2() const & { // COMPLIANT -- due to overload below + return a; + } + int geta2() && { return a; } + + int const &getabad() const & { // NON_COMPLIANT -- no overload provided + return a; + } + + int getb() && { return b; } // COMPLIANT -- b is not a subobject + + A const *getstruct() const & { // COMPLIANT -- due to overload below + return this; + } + + A getstruct() const && = delete; + + A const *getstructbad() const & { // NON_COMPLIANT -- no overload provided + return this; + } + + A &getstructbad2() { return *this; } // NON_COMPLIANT +}; + +class C { + C *f() { // COMPLIANT -- this is not explicitly designated therefore this is + // not + // relevant for this rule + C *thisclass = this; + return thisclass; + } +}; + +struct Templ { + template + Templ const *f(T) const & { // NON_COMPLIANT -- for an instantiation below + return this; + } + + void f(int) const && = delete; +}; + +void f(int p, float p1) { + Templ t; + t.f(p); + t.f(p1); // instantiation that causes issue due to parameter list type meaning + // there is no overload +} + +class B { + int i; + int *ip; + int **ip2; + + int *f() { + return &i; // NON_COMPLIANT + } + int *f1() { + return ip; // COMPLIANT -- reads pointer + } + int *f2() { + return *ip2; // COMPLIANT -- reads pointer + } + + int **f3() { + return &ip; // NON_COMPLIANT + } + + int **f4() { + return ip2; // COMPLIANT -- copies pointer + } + + int &f5() { + return i; // NON_COMPLIANT + } + int &f6() { + return *ip; // COMPLIANT[FALSE_POSITIVE] -- reads pointer + } + int &f7() { + return **ip2; // COMPLIANT[FALSE_POSITIVE]-- reads pointer + } + + int *&f8() { + // return &p; // won't compile + return ip; // NON_COMPLIANT + } + int *&f9() { + return *ip2; // COMPLIANT[FALSE_POSITIVE] -- reads pointer + } +}; + +class D { + int i; + int *ip; + int **ip2; + + int *f() { + return &(this->i); // NON_COMPLIANT + } + int *f1() { + return this->ip; // COMPLIANT -- reads pointer + } + int *f2() { + return *this->ip2; // COMPLIANT -- reads pointer + } + + int **f3() { + return &this->ip; // NON_COMPLIANT + } + + int **f4() { + return this->ip2; // COMPLIANT -- copies pointer + } + + int &f5() { + return this->i; // NON_COMPLIANT + } + int &f6() { + return *this->ip; // COMPLIANT[FALSE_POSITIVE] -- reads pointer + } + int &f7() { + return **this->ip2; // COMPLIANT[FALSE_POSITIVE] -- reads pointer + } + + int *&f8() { + // return &p; // won't compile + return this->ip; // NON_COMPLIANT + } + int *&f9() { + return *this->ip2; // COMPLIANT[FALSE_POSITIVE] -- reads pointer + } +}; \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.expected b/cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.expected new file mode 100644 index 000000000..db0ef7b72 --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.expected @@ -0,0 +1,2 @@ +| test.cpp:4:5:4:5 | definition of i | Declaration entry has a different type alias than $@ where the type alias used is '$@'. | test.cpp:5:12:5:12 | declaration of i | i | test.cpp:1:13:1:15 | INT | INT | +| test.cpp:11:20:11:20 | declaration of i | Declaration entry has a different type alias than $@ where the type alias used is '$@'. | test.cpp:10:12:10:12 | declaration of i | i | test.cpp:2:7:2:11 | Index | Index | diff --git a/cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.qlref b/cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.qlref new file mode 100644 index 000000000..a3cf4823c --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-9-1/TypeAliasesDeclaration.qlref @@ -0,0 +1 @@ +rules/RULE-6-9-1/TypeAliasesDeclaration.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-6-9-1/test.cpp b/cpp/misra/test/rules/RULE-6-9-1/test.cpp new file mode 100644 index 000000000..dfc18becf --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-9-1/test.cpp @@ -0,0 +1,15 @@ +typedef int INT; +using Index = int; + +INT i; +extern int i; // NON_COMPLIANT + +INT j; +extern INT j; // COMPLIANT + +void g(int i); +void g(Index const i); // NON_COMPLIANT + +void h(Index i); +void h(Index const i); // COMPLIANT +void h(int *i); // COMPLIANT \ No newline at end of file diff --git a/rule_packages/cpp/Declarations5.json b/rule_packages/cpp/Declarations5.json new file mode 100644 index 000000000..4f4c08e7d --- /dev/null +++ b/rule_packages/cpp/Declarations5.json @@ -0,0 +1,47 @@ +{ + "MISRA-C++-2023": { + "RULE-6-8-4": { + "properties": { + "enforcement": "decidable", + "obligation": "advisory" + }, + "queries": [ + { + "description": "Member functions that return references to temporary objects (or subobjects) can lead to dangling pointers.", + "kind": "problem", + "name": "Member functions returning references to their object should be refqualified appropriately", + "precision": "very-high", + "severity": "error", + "short_name": "MemberFunctionsRefqualified", + "tags": [ + "correctness", + "scope/single-translation-unit" + ] + } + ], + "title": "Member functions returning references to their object should be refqualified appropriately" + }, + "RULE-6-9-1": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Using different type aliases on redeclarations can make code hard to understand and maintain.", + "kind": "problem", + "name": "The same type aliases shall be used in all declarations of the same entity", + "precision": "very-high", + "severity": "warning", + "short_name": "TypeAliasesDeclaration", + "tags": [ + "maintainability", + "readability", + "scope/single-translation-unit" + ] + } + ], + "title": "The same type aliases shall be used in all declarations of the same entity" + } + } +} \ No newline at end of file