diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Classes3.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Classes3.qll new file mode 100644 index 000000000..4eec67198 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Classes3.qll @@ -0,0 +1,44 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Classes3Query = + TImproperlyProvidedSpecialMemberFunctionsQuery() or + TImproperlyProvidedSpecialMemberFunctionsAuditQuery() + +predicate isClasses3QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `improperlyProvidedSpecialMemberFunctions` query + Classes3Package::improperlyProvidedSpecialMemberFunctionsQuery() and + queryId = + // `@id` for the `improperlyProvidedSpecialMemberFunctions` query + "cpp/misra/improperly-provided-special-member-functions" and + ruleId = "RULE-15-0-1" and + category = "required" + or + query = + // `Query` instance for the `improperlyProvidedSpecialMemberFunctionsAudit` query + Classes3Package::improperlyProvidedSpecialMemberFunctionsAuditQuery() and + queryId = + // `@id` for the `improperlyProvidedSpecialMemberFunctionsAudit` query + "cpp/misra/improperly-provided-special-member-functions-audit" and + ruleId = "RULE-15-0-1" and + category = "required" +} + +module Classes3Package { + Query improperlyProvidedSpecialMemberFunctionsQuery() { + //autogenerate `Query` type + result = + // `Query` type for `improperlyProvidedSpecialMemberFunctions` query + TQueryCPP(TClasses3PackageQuery(TImproperlyProvidedSpecialMemberFunctionsQuery())) + } + + Query improperlyProvidedSpecialMemberFunctionsAuditQuery() { + //autogenerate `Query` type + result = + // `Query` type for `improperlyProvidedSpecialMemberFunctionsAudit` query + TQueryCPP(TClasses3PackageQuery(TImproperlyProvidedSpecialMemberFunctionsAuditQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index d0ac06886..3d0b195b0 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -18,6 +18,7 @@ import BannedSyntax import BannedTypes import Classes import Classes2 +import Classes3 import Classes4 import Comments import Concurrency @@ -126,6 +127,7 @@ newtype TCPPQuery = TBannedTypesPackageQuery(BannedTypesQuery q) or TClassesPackageQuery(ClassesQuery q) or TClasses2PackageQuery(Classes2Query q) or + TClasses3PackageQuery(Classes3Query q) or TClasses4PackageQuery(Classes4Query q) or TCommentsPackageQuery(CommentsQuery q) or TConcurrencyPackageQuery(ConcurrencyQuery q) or @@ -234,6 +236,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isBannedTypesQueryMetadata(query, queryId, ruleId, category) or isClassesQueryMetadata(query, queryId, ruleId, category) or isClasses2QueryMetadata(query, queryId, ruleId, category) or + isClasses3QueryMetadata(query, queryId, ruleId, category) or isClasses4QueryMetadata(query, queryId, ruleId, category) or isCommentsQueryMetadata(query, queryId, ruleId, category) or isConcurrencyQueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/misra/src/rules/RULE-15-0-1/AnalyzableClass.qll b/cpp/misra/src/rules/RULE-15-0-1/AnalyzableClass.qll new file mode 100644 index 000000000..3d6c03e20 --- /dev/null +++ b/cpp/misra/src/rules/RULE-15-0-1/AnalyzableClass.qll @@ -0,0 +1,258 @@ +/** + * Provides predicates and classes to perform a precursor analysis of which classes the rule can + * potentially analyze, or should be excluded and instead selected by the audit query. + * + * For example, the class `AnalyzableClass` resolves all of the special member functions that we + * must have in order to determine rule compliance. + * + * Part of what this module does is perform a very approximate analysis of which classes will + * produce a value of true in `std::is_(copy|move)_(constructible|assignable)_v`. + * + * Fully predicting these standard type traits requires performing a very thorough overload + * resolution analysis, including value category propagation and reference binding and user defined + * conversion operators and standard conversions and promotions and ranking viable candidates and + * properly handling ambiguous overloads. + */ + +import cpp + +/** + * For `std::is_(copy|move)_(constructible|assignable)_v` to return true for a given class, the + * member must exist, it must not be deleted, and it must be publicly accessible. + * + * This is a very coarse approximation of true behavior of the standard type traits. + */ +private predicate isUsable(MemberFunction f) { + not f.isDeleted() and + f.isPublic() +} + +/** + * Holds if the member function `f` has been "customized" by the user, e.g., they explicitly wrote + * the implementation of the function. + */ +private predicate isMemberCustomized(MemberFunction f) { + exists(f.getDefinition()) and + not f.isDefaulted() and + not f.isDeleted() and + not f.isCompilerGenerated() +} + +/** + * Holds if the user declared the member function `f`, as opposed to it being implicitly declared + * by the compiler. + * + * Note that `T(T&&) = default` and `T(T&&) = delete` are both user declared. This is not to be + * confused with "user defined." + */ +private predicate isUserDeclared(MemberFunction f) { not f.isCompilerGenerated() } + +/** + * Holds if the implicit move constructor or move assignment operator of the class `c` will not be + * declared. + * + * Specified in [class.copy.ctor]/8 and [class.copy.assign]/4. + */ +private predicate implicitMoveIsSuppressed(Class c) { + isUserDeclared(c.getAConstructor().(CopyConstructor)) + or + isUserDeclared(c.getAMemberFunction().(CopyAssignmentOperator)) + or + isUserDeclared(c.getDestructor()) +} + +/** + * Returns the move constructor of the class `c` if it exists, or the copy constructor if it does + * not exist and the implicit definition was suppressed by the compiler. + * + * For example: + * ```cpp + * class OnlyCopyCtor { + * public: + * OnlyCopyCtor(const OnlyCopyCtor &) = default; + * }; + * + * static_assert(std::is_copy_constructible_v); // Succeeds + * static_assert(std::is_move_constructible_v); // Also succeeds + * ``` + * + * Note that without the declared copy constructor, the compiler may define an implicit move + * constructor. + * + * Additionally note that if the move constructor was declared as `= delete;`, then the second + * assertion in the above example would fail. + */ +private Constructor getMoveConstructor(Class c) { + if + not exists(MoveConstructor mc | mc = c.getAConstructor() and isUserDeclared(mc)) and + implicitMoveIsSuppressed(c) + then result = c.getAConstructor().(CopyConstructor) + else result = c.getAConstructor().(MoveConstructor) +} + +/** + * Returns the move assignment operator of the class `c` if it exists, or the copy assignment + * operator if it does not exist and the implicit definition was suppressed by the compiler. + * + * For example: + * ```cpp + * class OnlyCopyAssign { + * public: + * OnlyCopyAssign& operator=(const OnlyCopyAssign &) = default; + * }; + * + * static_assert(std::is_copy_assignable_v); // Succeeds + * static_assert(std::is_move_assignable_v); // Also succeeds + * ``` + * + * Note that without the declared copy assignment operator, the compiler may define an implicit move + * assignment operator. + * + * Additionally note that if the move assignment operator was declared as `= delete;`, then the second + * assertion in the above example would fail. + */ +private Operator getMoveAssign(Class c) { + if + not exists(MoveAssignmentOperator mc | mc = c.getAMemberFunction() and isUserDeclared(mc)) and + implicitMoveIsSuppressed(c) + then result = c.getAMemberFunction().(CopyAssignmentOperator) + else result = c.getAMemberFunction().(MoveAssignmentOperator) +} + +/** + * The types of special member functions that the `AnalyzableClass` tracks and analyzes. + */ +newtype TSpecialMember = + TMoveConstructor() or + TMoveAssignmentOperator() or + TCopyConstructor() or + TCopyAssignmentOperator() or + TDestructor() + +/** + * A class for which we can see all special member functions, including implicitly declared ones, + * and therefore we can attempt to analyze it in the current rule. + * + * If one of the special member functions cannot be found, we cannot know if it is missing because + * it should not have been generated, or if EDG did not emit a definition for it. For instance, EDG + * may not generate these functions if they are trivial, or if they are delete, or not ODR used. We, + * the authors of this project, do not know the exact conditions we have to consider in this case. + * + * Determining for ourselves whether a certain constructor would be implicitly declared, and with + * what signature, and whether it is deleted, requires implementing a significant portion of the C++ + * language rules regarding special member function generation, including a significant portion of + * C++ overload resolution rules which are non-trivial. + * + * Therefore we must find a definition for each special member in the database to proceed. The only + * exception we allow is certain missing `MoveConstructor` or `MoveAssignmentOperator` members; if + * the class defines copy operations or the destructor, we expect these to be missing, and typically + * this means the corresponding copy operation acts in place of the equivalent move. + * + * The last difficulty in analysis that this class attempts to handle is the values of the type + * traits `std::is_(copy|move)_(constructible|assignable)`. These type traits are defined as true if + * certain C++ expressions, such as `T(declval())` or `declval() = declval()`, are + * well-formed. We cannot correctly determine this in all cases without implementing a significant + * portion of the C++ language rules for reference binding and overload resolution. + * + * To handle these type traits, we take a very rough approximation. If the corresponding special + * member function is public and not deleted, then we assume the type trait will evaluate to true. + * We also handle the case where a user declared copy operation suppresses the implicit move + * operations, which typically means overload resolution selects the copy operation. (This is not + * the case when the move operations are declared as deleted). We handle this by treating the copy + * operation as effectively acting in place of the move operation for the purposes of evaluating + * the type traits. + */ +class AnalyzableClass extends Class { + CopyConstructor copyCtor; + // The move constructor may be suppressed, and the copy constructor may be used during moves. + Constructor moveCtor; + CopyAssignmentOperator copyAssign; + // The move assignment operator may be suppressed, and the copy assignment operator may be used during moves. + Operator moveAssign; + Destructor dtor; + + AnalyzableClass() { + copyCtor = this.getAConstructor() and + moveCtor = getMoveConstructor(this) and + copyAssign = this.getAMemberFunction() and + moveAssign = getMoveAssign(this) and + dtor = this.getDestructor() + } + + /** + * Holds `std::is_move_constructible_v` is likely true for this class. + * + * Specifically this holds if there's a non-deleted public move constructor available for this + * class, or if there is a non-deleted public copy constructor that acts as the move constructor. + */ + predicate moveConstructible() { isUsable(moveCtor) } + + /** + * Holds `std::is_copy_constructible_v` is likely true for this class. + * + * Specifically this holds if there's a non-deleted public copy constructor available for this + * class. + */ + predicate copyConstructible() { isUsable(copyCtor) } + + /** + * Holds `std::is_move_assignable_v` is likely true for this class. + * + * Specifically this holds if there's a non-deleted public move assignment operator available for + * this class, or if there is a non-deleted public copy assignment operator that acts as the move + * assignment operator. + */ + predicate moveAssignable() { isUsable(moveAssign) } + + /** + * Holds `std::is_copy_assignable_v` is likely true for this class. + * + * Specifically this holds if there's a non-deleted public copy assignment operator available for + * this class. + */ + predicate copyAssignable() { isUsable(copyAssign) } + + /** + * Holds if the given special member function `s` is customized for this class. + * + * For most cases, this checks that the given special member function `s` has a user-provided + * body (other than `= default;` or `= delete;`). + * + * If the class has copy operations that act in place of the move operations, that means the + * corresponding move operation was not declared, so we say this predicate does not hold for the + * given move operation `s`. + */ + predicate isCustomized(TSpecialMember s) { + s instanceof TMoveConstructor and + isMemberCustomized(moveCtor) and + declaresMoveConstructor() + or + s instanceof TMoveAssignmentOperator and + isMemberCustomized(moveAssign) and + declaresMoveAssignmentOperator() + or + s instanceof TCopyConstructor and isMemberCustomized(copyCtor) + or + s instanceof TCopyAssignmentOperator and isMemberCustomized(copyAssign) + or + s instanceof TDestructor and isMemberCustomized(dtor) + } + + /** + * Holds if this class declares a move constructor. + * + * This will be true if move constructor resolution found a non-implicit constructor that is not + * the copy constructor masquerading as a move constructor. + */ + predicate declaresMoveConstructor() { not moveCtor = copyCtor and isUserDeclared(moveCtor) } + + /** + * Holds if this class declares a move assignment operator. + * + * This will be true if move assignment resolution found a non-implicit operator that is not + * the copy assignment operator masquerading as a move assignment operator. + */ + predicate declaresMoveAssignmentOperator() { + not moveAssign = copyAssign and isUserDeclared(moveAssign) + } +} diff --git a/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.ql b/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.ql new file mode 100644 index 000000000..9af397903 --- /dev/null +++ b/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.ql @@ -0,0 +1,149 @@ +/** + * @id cpp/misra/improperly-provided-special-member-functions + * @name RULE-15-0-1: Special member functions shall be provided appropriately + * @description Incorrect provision of special member functions can lead to unexpected or undefined + * behavior when objects of the class are copied, moved, or destroyed. + * @kind problem + * @precision medium + * @problem.severity warning + * @tags external/misra/id/rule-15-0-1 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import AnalyzableClass + +predicate isCopyEnabled(AnalyzableClass c) { + c.moveConstructible() and + not c.moveAssignable() and + c.copyConstructible() and + not c.copyAssignable() + or + c.moveConstructible() and + c.moveAssignable() and + c.copyConstructible() and + c.copyAssignable() +} + +predicate isMoveOnly(AnalyzableClass c) { + c.moveConstructible() and + not c.moveAssignable() and + not c.copyConstructible() and + not c.copyAssignable() + or + c.moveConstructible() and + c.moveAssignable() and + not c.copyConstructible() and + not c.copyAssignable() +} + +predicate isUnmovable(AnalyzableClass c) { + not c.moveConstructible() and + not c.moveAssignable() and + not c.copyConstructible() and + not c.copyAssignable() +} + +predicate isValidCategory(AnalyzableClass c) { + isCopyEnabled(c) or + isMoveOnly(c) or + isUnmovable(c) +} + +string specialMemberName(TSpecialMember f) { + f = TCopyConstructor() and result = "copy constructor" + or + f = TMoveConstructor() and result = "move constructor" + or + f = TCopyAssignmentOperator() and result = "copy assignment operator" + or + f = TMoveAssignmentOperator() and result = "move assignment operator" +} + +predicate violatesCustomizedMoveOrCopyRequirements(AnalyzableClass c, string reason) { + not c.isCustomized(TDestructor()) and + exists(string concatenated | + concatenated = + strictconcat(TSpecialMember f | + not f = TDestructor() and + c.isCustomized(f) + | + specialMemberName(f), ", " + ) and + reason = "has customized " + concatenated + ", but does not customize the destructor." + ) +} + +private predicate undeclaredMoveException(AnalyzableClass c) { + // A copy-enabled class may have an undeclared move constructor. + isCopyEnabled(c) and + not c.copyAssignable() and + not c.declaresMoveConstructor() + or + // A copy-assignable class may leave both move operations undeclared. + c.copyAssignable() and + not c.declaresMoveConstructor() and + not c.declaresMoveAssignmentOperator() +} + +predicate violatesCustomizedDestructorRequirements(AnalyzableClass c, string reason) { + c.isCustomized(TDestructor()) and + ( + c.moveConstructible() and + not c.isCustomized(TMoveConstructor()) and + not undeclaredMoveException(c) and + reason = "has customized the destructor, but does not customize the move constructor." + or + c.moveAssignable() and + not undeclaredMoveException(c) and + not c.isCustomized(TMoveAssignmentOperator()) and + reason = "has customized the destructor, but does not customize the move assignment operator." + or + c.copyConstructible() and + not c.isCustomized(TCopyConstructor()) and + reason = "has customized the destructor, but does not customize the copy constructor." + or + c.copyAssignable() and + not c.isCustomized(TCopyAssignmentOperator()) and + reason = "has customized the destructor, but does not customize the copy assignment operator." + ) +} + +predicate isPublicBase(AnalyzableClass c) { + exists(ClassDerivation d | + d.getBaseClass() = c and + d.hasSpecifier("public") + ) +} + +predicate satisfiesInheritanceRequirements(AnalyzableClass c) { + not isPublicBase(c) + or + isUnmovable(c) and + c.getDestructor().isPublic() and + c.getDestructor().isVirtual() + or + c.getDestructor().isProtected() and + not c.getDestructor().isVirtual() +} + +from AnalyzableClass c, string message +where + not isExcluded(c, Classes3Package::improperlyProvidedSpecialMemberFunctionsQuery()) and + ( + not isValidCategory(c) and + message = "does not fall into a valid category (isUnmovable, move-only, or copy-enabled)." + or + violatesCustomizedMoveOrCopyRequirements(c, message) + or + violatesCustomizedDestructorRequirements(c, message) + or + not satisfiesInheritanceRequirements(c) and + message = "violates inheritance requirements for special member functions." + ) +select c, "Class '" + c.getName() + "' " + message diff --git a/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.ql b/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.ql new file mode 100644 index 000000000..66abfad03 --- /dev/null +++ b/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.ql @@ -0,0 +1,50 @@ +/** + * @id cpp/misra/improperly-provided-special-member-functions-audit + * @name RULE-15-0-1: Special member functions shall be provided appropriately, Audit + * @description Audit: incorrect provision of special member functions can lead to unexpected or + * undefined behavior when objects of the class are copied, moved, or destroyed. + * @kind problem + * @precision low + * @problem.severity warning + * @tags external/misra/id/rule-15-0-1 + * scope/single-translation-unit + * correctness + * external/misra/audit + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import AnalyzableClass +import qtil.Qtil + +string missingKind(Class c) { + not c.getAConstructor() instanceof MoveConstructor and + result = "move constructor" + or + not c.getAMemberFunction() instanceof MoveAssignmentOperator and + result = "move assignment operator" + or + not c.getAConstructor() instanceof CopyConstructor and + result = "copy constructor" + or + not c.getAMemberFunction() instanceof CopyAssignmentOperator and + result = "copy assignment operator" + or + not c.getAMemberFunction() instanceof Destructor and + result = "destructor" +} + +string missingKinds(Class c) { result = concat(missingKind(c), " and ") } + +from Class c, string kinds +where + not isExcluded(c, Classes3Package::improperlyProvidedSpecialMemberFunctionsAuditQuery()) and + not c instanceof AnalyzableClass and + not c.isPod() and + kinds = missingKinds(c) +select c, + "Class '" + c.getName() + "' is not analyzable because the " + kinds + " " + + Qtil::plural("is", "are", count(missingKind(c))) + " not present in the CodeQL database." diff --git a/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.expected b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.expected new file mode 100644 index 000000000..0bc835a3b --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.expected @@ -0,0 +1,39 @@ +| test.cpp:71:11:71:12 | C2 | Class 'C2' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:72:11:72:12 | C3 | Class 'C3' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:74:11:74:12 | C5 | Class 'C5' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:76:11:76:12 | C7 | Class 'C7' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:78:11:78:12 | C9 | Class 'C9' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:79:11:79:13 | C10 | Class 'C10' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:80:11:80:13 | C11 | Class 'C11' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:81:11:81:13 | C12 | Class 'C12' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:82:11:82:13 | C13 | Class 'C13' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:83:11:83:13 | C14 | Class 'C14' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:84:11:84:13 | C15 | Class 'C15' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:102:7:102:21 | PrivateCopyCtor | Class 'PrivateCopyCtor' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:115:7:115:21 | PrivateMoveCtor | Class 'PrivateMoveCtor' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:128:7:128:23 | PrivateCopyAssign | Class 'PrivateCopyAssign' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:141:7:141:23 | PrivateMoveAssign | Class 'PrivateMoveAssign' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:182:7:182:45 | CustomizedCopyCtorDefaultedNonCompliant | Class 'CustomizedCopyCtorDefaultedNonCompliant' has customized copy constructor, but does not customize the destructor. | +| test.cpp:189:7:189:43 | CustomizedCopyCtorDeletedNonCompliant | Class 'CustomizedCopyCtorDeletedNonCompliant' has customized copy constructor, but does not customize the destructor. | +| test.cpp:195:7:195:36 | CustomizedMoveCtorNonCompliant | Class 'CustomizedMoveCtorNonCompliant' has customized move constructor, but does not customize the destructor. | +| test.cpp:201:7:201:38 | CustomizedCopyAssignNonCompliant | Class 'CustomizedCopyAssignNonCompliant' has customized copy assignment operator, but does not customize the destructor. | +| test.cpp:207:7:207:38 | CustomizedMoveAssignNonCompliant | Class 'CustomizedMoveAssignNonCompliant' has customized copy constructor, move assignment operator, move constructor, but does not customize the destructor. | +| test.cpp:214:7:214:39 | MoveOnlyNotCustomizedNonCompliant | Class 'MoveOnlyNotCustomizedNonCompliant' has customized the destructor, but does not customize the move constructor. | +| test.cpp:223:7:223:49 | MoveOnlyAssignableNotCustomizedNonCompliant | Class 'MoveOnlyAssignableNotCustomizedNonCompliant' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:223:7:223:49 | MoveOnlyAssignableNotCustomizedNonCompliant | Class 'MoveOnlyAssignableNotCustomizedNonCompliant' has customized the destructor, but does not customize the move constructor. | +| test.cpp:258:7:258:43 | CopyEnabledCustomizedDtorNonCompliant | Class 'CopyEnabledCustomizedDtorNonCompliant' has customized the destructor, but does not customize the copy assignment operator. | +| test.cpp:258:7:258:43 | CopyEnabledCustomizedDtorNonCompliant | Class 'CopyEnabledCustomizedDtorNonCompliant' has customized the destructor, but does not customize the copy constructor. | +| test.cpp:258:7:258:43 | CopyEnabledCustomizedDtorNonCompliant | Class 'CopyEnabledCustomizedDtorNonCompliant' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:258:7:258:43 | CopyEnabledCustomizedDtorNonCompliant | Class 'CopyEnabledCustomizedDtorNonCompliant' has customized the destructor, but does not customize the move constructor. | +| test.cpp:296:7:296:47 | CopyAssignableCustomizedDtorNonCompliant1 | Class 'CopyAssignableCustomizedDtorNonCompliant1' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:306:7:306:47 | CopyAssignableCustomizedDtorNonCompliant2 | Class 'CopyAssignableCustomizedDtorNonCompliant2' has customized the destructor, but does not customize the move constructor. | +| test.cpp:316:7:316:47 | CopyAssignableCustomizedDtorNonCompliant3 | Class 'CopyAssignableCustomizedDtorNonCompliant3' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:326:7:326:47 | CopyAssignableCustomizedDtorNonCompliant4 | Class 'CopyAssignableCustomizedDtorNonCompliant4' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:326:7:326:47 | CopyAssignableCustomizedDtorNonCompliant4 | Class 'CopyAssignableCustomizedDtorNonCompliant4' has customized the destructor, but does not customize the move constructor. | +| test.cpp:336:7:336:47 | CopyAssignableCustomizedDtorNonCompliant5 | Class 'CopyAssignableCustomizedDtorNonCompliant5' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:336:7:336:47 | CopyAssignableCustomizedDtorNonCompliant5 | Class 'CopyAssignableCustomizedDtorNonCompliant5' has customized the destructor, but does not customize the move constructor. | +| test.cpp:346:7:346:33 | UnmovableBaseNonvirtualDtor | Class 'UnmovableBaseNonvirtualDtor' violates inheritance requirements for special member functions. | +| test.cpp:369:7:369:33 | UnmovablePrivateVirtualDtor | Class 'UnmovablePrivateVirtualDtor' violates inheritance requirements for special member functions. | +| test.cpp:398:7:398:30 | BaseVirtualProtectedDtor | Class 'BaseVirtualProtectedDtor' violates inheritance requirements for special member functions. | +| test.cpp:439:7:439:26 | NonTrivialDestructor | Class 'NonTrivialDestructor' has customized the destructor, but does not customize the copy assignment operator. | +| test.cpp:439:7:439:26 | NonTrivialDestructor | Class 'NonTrivialDestructor' has customized the destructor, but does not customize the copy constructor. | diff --git a/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.qlref b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.qlref new file mode 100644 index 000000000..4c14a48a2 --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.qlref @@ -0,0 +1 @@ +rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.expected b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.expected new file mode 100644 index 000000000..527d1b592 --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.expected @@ -0,0 +1,8 @@ +| test.cpp:353:7:353:32 | UnmovableNonvirtualDerived | Class 'UnmovableNonvirtualDerived' is not analyzable because the destructor and move assignment operator and move constructor are not present in the CodeQL database. | +| test.cpp:366:7:366:39 | UnmovableDerivedPublicVirtualDtor | Class 'UnmovableDerivedPublicVirtualDtor' is not analyzable because the move assignment operator and move constructor are not present in the CodeQL database. | +| test.cpp:381:7:381:40 | UnmovablePrivateVirtualDtorDerived | Class 'UnmovablePrivateVirtualDtorDerived' is not analyzable because the move assignment operator and move constructor are not present in the CodeQL database. | +| test.cpp:396:7:396:26 | ProtectedDtorDerived | Class 'ProtectedDtorDerived' is not analyzable because the destructor and move assignment operator are not present in the CodeQL database. | +| test.cpp:410:7:410:33 | VirtualProtectedDtorDerived | Class 'VirtualProtectedDtorDerived' is not analyzable because the move assignment operator is not present in the CodeQL database. | +| test.cpp:423:8:423:19 | TrivialClass | Class 'TrivialClass' is not analyzable because the destructor and move assignment operator and move constructor are not present in the CodeQL database. | +| test.cpp:429:7:429:21 | NonTrivialClass | Class 'NonTrivialClass' is not analyzable because the copy constructor and move assignment operator and move constructor are not present in the CodeQL database. | +| test.cpp:450:7:450:14 | CopyOnly | Class 'CopyOnly' is not analyzable because the destructor is not present in the CodeQL database. | diff --git a/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.qlref b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.qlref new file mode 100644 index 000000000..1a3eac2ba --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.qlref @@ -0,0 +1 @@ +rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-15-0-1/test.cpp b/cpp/misra/test/rules/RULE-15-0-1/test.cpp new file mode 100644 index 000000000..b5323b937 --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/test.cpp @@ -0,0 +1,474 @@ +#include + +namespace helpers { +void f(); +} + +// A macro to generate `T(const T&)` that may be followed by `= default;` or `= +// delete;` etc. +#define COPY_CTOR(NAME) NAME(const NAME &) + +// A macro to generate `T(T&&)` that may be followed by `= default;` or `= +// delete;` etc. +#define MOVE_CTOR(NAME) NAME(NAME &&) + +// A macro to generate `T& operator=(const T&)` that may be followed by `= +// default;` or `= delete;` etc. +#define COPY_ASSIGN(NAME) NAME &operator=(const NAME &) + +// A macro to generate `T& operator=(T&&)` that may be followed by `= default;` +// or `= delete;` etc. +#define MOVE_ASSIGN(NAME) NAME &operator=(NAME &&) + +// A macro to generate `~T()` that may be followed by `= default;` or `= +// delete;` etc. +#define DTOR(NAME) ~NAME() + +// clang-format off +// A macro to generate all five special member functions for a class `NAME` +// with the ability to specify for each whether it is `= default;`, `= delete;`, +// or a non-trivial definition via the corresponding `_SPEC` parameter. +#define DEFINE_ALL_SPECIAL_MEMBERS(NAME, \ + COPY_CTOR_SPEC, MOVE_CTOR_SPEC, COPY_ASSIGN_SPEC, MOVE_ASSIGN_SPEC, DTOR_SPEC) \ + COPY_CTOR(NAME) COPY_CTOR_SPEC /* T(const T&) = default; */ \ + MOVE_CTOR(NAME) MOVE_CTOR_SPEC /* T(T&&) = default; */ \ + COPY_ASSIGN(NAME) COPY_ASSIGN_SPEC /* T& operator=(const T&) = default; */ \ + MOVE_ASSIGN(NAME) MOVE_ASSIGN_SPEC /* T& operator=(T&&) = default; */ \ + DTOR(NAME) DTOR_SPEC /* ~T() = default; */ + +// Macros to generate `= default;`, `= delete;`, or a non-trivial definition for a +// special member function. +#define DEFAULTED = default; +#define DELETED = delete; +#define CUSTOMIZED { helpers::f(); } +// clang-format on + +namespace fully_specified { +/** + * Go through all combinations of defaulted and deleted copy/move + * ctor/assignment. + */ +namespace combinations { + +// NOTE: THIS USES THE MISRA SPECIFICATION TABLE ORDER (move ctor, move assign, +// copy ctor, copy assign) AND NOT THE ORDER OF `DEFINE_ALL_SPECIAL_MEMBERS` +// (copy ctor, move ctor, copy assign, move assign) !!!!!!! +#define TABLE_ROW(NAME, MOVE_CTOR_SPEC, MOVE_ASSIGN_SPEC, COPY_CTOR_SPEC, \ + COPY_ASSIGN_SPEC) \ + class NAME { \ + int x; \ + \ + public: \ + DEFINE_ALL_SPECIAL_MEMBERS(NAME, COPY_CTOR_SPEC, MOVE_CTOR_SPEC, \ + COPY_ASSIGN_SPEC, MOVE_ASSIGN_SPEC, DEFAULTED) \ + }; + +// clang-format off +#define YES DEFAULTED +#define NO DELETED +// See `TABLE_ROW` for ordering notes!! This matches the table from the spec. +TABLE_ROW(C1, YES, YES, YES, YES) // COMPLIANT - Copy-enabled +TABLE_ROW(C2, YES, YES, YES, NO ) // NON_COMPLIANT +TABLE_ROW(C3, YES, YES, NO, YES) // NON_COMPLIANT +TABLE_ROW(C4, YES, YES, NO, NO ) // COMPLIANT - Move-only +TABLE_ROW(C5, YES, NO, YES, YES) // NON_COMPLIANT +TABLE_ROW(C6, YES, NO, YES, NO ) // COMPLIANT - Copy-enabled +TABLE_ROW(C7, YES, NO, NO, YES) // NON_COMPLIANT +TABLE_ROW(C8, YES, NO, NO, NO ) // COMPLIANT - Move-only +TABLE_ROW(C9, NO, YES, YES, YES) // NON_COMPLIANT +TABLE_ROW(C10, NO, YES, YES, NO ) // NON_COMPLIANT +TABLE_ROW(C11, NO, YES, NO, YES) // NON_COMPLIANT +TABLE_ROW(C12, NO, YES, NO, NO ) // NON_COMPLIANT +TABLE_ROW(C13, NO, NO, YES, YES) // NON_COMPLIANT +TABLE_ROW(C14, NO, NO, YES, NO ) // NON_COMPLIANT +TABLE_ROW(C15, NO, NO, NO, YES) // NON_COMPLIANT +TABLE_ROW(C16, NO, NO, NO, NO ) // COMPLIANT - Unmovable +// clang-format on + +#undef YES +#undef NO +#undef TABLE_ROW +} // namespace combinations + +class AllPrivate { // COMPLIANT -- unmovable +private: + int x; + +public: + DEFINE_ALL_SPECIAL_MEMBERS(AllPrivate, DEFAULTED, DEFAULTED, DEFAULTED, + DEFAULTED, DEFAULTED) +}; + +class PrivateCopyCtor { // NON_COMPLIANT + int x; + +private: + COPY_CTOR(PrivateCopyCtor) = default; + +public: + MOVE_CTOR(PrivateCopyCtor) = default; + COPY_ASSIGN(PrivateCopyCtor) = default; + MOVE_ASSIGN(PrivateCopyCtor) = default; + DTOR(PrivateCopyCtor) = default; +}; + +class PrivateMoveCtor { // NON_COMPLIANT + int x; + +private: + MOVE_CTOR(PrivateMoveCtor) = default; + +public: + COPY_CTOR(PrivateMoveCtor) = default; + COPY_ASSIGN(PrivateMoveCtor) = default; + MOVE_ASSIGN(PrivateMoveCtor) = default; + DTOR(PrivateMoveCtor) = default; +}; + +class PrivateCopyAssign { // NON_COMPLIANT + int x; + +private: + COPY_ASSIGN(PrivateCopyAssign) = default; + +public: + COPY_CTOR(PrivateCopyAssign) = default; + MOVE_CTOR(PrivateCopyAssign) = default; + MOVE_ASSIGN(PrivateCopyAssign) = default; + DTOR(PrivateCopyAssign) = default; +}; + +class PrivateMoveAssign { // NON_COMPLIANT + int x; + +private: + MOVE_ASSIGN(PrivateMoveAssign) = default; + +public: + COPY_CTOR(PrivateMoveAssign) = default; + MOVE_CTOR(PrivateMoveAssign) = default; + COPY_ASSIGN(PrivateMoveAssign) = default; + DTOR(PrivateMoveAssign) = default; +}; + +namespace additional_requirements { + +// A class with a customized copy constructor requires a customized destructor +class CustomizedMoveCtorCompliant { // COMPLIANT: move-only (1) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedMoveCtorCompliant, DELETED, CUSTOMIZED, + DELETED, DELETED, CUSTOMIZED) +}; + +class CustomizedCtorsCompliant { // COMPLIANT: copy-enabled (1) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedCtorsCompliant, CUSTOMIZED, CUSTOMIZED, + DELETED, DELETED, CUSTOMIZED) +}; + +class CustomizedMoveAssignCompliant { // COMPLIANT: move-only (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedMoveAssignCompliant, DELETED, CUSTOMIZED, + DELETED, CUSTOMIZED, CUSTOMIZED) +}; + +class CustomizedAssignsCompliant { // COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedAssignsCompliant, CUSTOMIZED, CUSTOMIZED, + CUSTOMIZED, CUSTOMIZED, CUSTOMIZED) +}; + +// copy-enabled (2) +class CustomizedCopyCtorDefaultedNonCompliant { // NON_COMPLIANT +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedCopyCtorDefaultedNonCompliant, + CUSTOMIZED, DEFAULTED, DEFAULTED, DEFAULTED, + DEFAULTED) +}; + +class CustomizedCopyCtorDeletedNonCompliant { // NON_COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedCopyCtorDeletedNonCompliant, CUSTOMIZED, + DEFAULTED, DEFAULTED, DEFAULTED, DELETED) +}; + +class CustomizedMoveCtorNonCompliant { // NON_COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedMoveCtorNonCompliant, DEFAULTED, + CUSTOMIZED, DEFAULTED, DEFAULTED, DEFAULTED) +}; + +class CustomizedCopyAssignNonCompliant { // NON_COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedCopyAssignNonCompliant, DEFAULTED, + DEFAULTED, CUSTOMIZED, DEFAULTED, DEFAULTED) +}; + +class CustomizedMoveAssignNonCompliant { // NON_COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CustomizedMoveAssignNonCompliant, CUSTOMIZED, + CUSTOMIZED, DEFAULTED, CUSTOMIZED, DEFAULTED) +}; + +// Move-only with a customized dtor requires customized move constructor. +class MoveOnlyNotCustomizedNonCompliant { // NON_COMPLIANT: move-only (1) +public: + DEFINE_ALL_SPECIAL_MEMBERS(MoveOnlyNotCustomizedNonCompliant, DELETED, + DEFAULTED, DELETED, DELETED, CUSTOMIZED) +}; + +// Move-only with a customized dtor requires customized move assignment +// operator. +// move-only (2) +class MoveOnlyAssignableNotCustomizedNonCompliant { // NON_COMPLIANT +public: + DEFINE_ALL_SPECIAL_MEMBERS(MoveOnlyAssignableNotCustomizedNonCompliant, + DELETED, DEFAULTED, DELETED, DEFAULTED, CUSTOMIZED) +}; + +// customized move: move-only (1) +class MoveOnlyCustomizedCompliant { // COMPLIANT +public: + DEFINE_ALL_SPECIAL_MEMBERS(MoveOnlyCustomizedCompliant, DELETED, CUSTOMIZED, + DELETED, DELETED, CUSTOMIZED) +}; + +// customized move: move-only (2) +class MoveOnlyAssignableCustomizedCompliant { // COMPLIANT +public: + DEFINE_ALL_SPECIAL_MEMBERS(MoveOnlyAssignableCustomizedCompliant, DELETED, + CUSTOMIZED, DELETED, CUSTOMIZED, CUSTOMIZED) +}; + +// default dtor: move-only (1) +class MoveOnlyNotCustomizedCompliant { // COMPLIANT +public: + DEFINE_ALL_SPECIAL_MEMBERS(MoveOnlyNotCustomizedCompliant, DELETED, DEFAULTED, + DELETED, DELETED, DEFAULTED) +}; + +// default dtor: move-only (2) +class MoveOnlyAssignableNotCustomizedCompliant { // COMPLIANT +public: + DEFINE_ALL_SPECIAL_MEMBERS(MoveOnlyAssignableNotCustomizedCompliant, DELETED, + DEFAULTED, DELETED, DEFAULTED, DEFAULTED) +}; + +// Copy-enabled with customized dtor requires customized copy and move +class CopyEnabledCustomizedDtorNonCompliant { // NON_COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CopyEnabledCustomizedDtorNonCompliant, DEFAULTED, + DEFAULTED, DEFAULTED, DEFAULTED, CUSTOMIZED) +}; + +class CopyEnabledNonCustomizedDtorCompliant { // COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CopyEnabledNonCustomizedDtorCompliant, DEFAULTED, + DEFAULTED, DEFAULTED, DEFAULTED, DEFAULTED) +}; + +class CopyEnabledCustomizedDtorCompliant1 { // COMPLIANT: copy-enabled (2) +public: + DEFINE_ALL_SPECIAL_MEMBERS(CopyEnabledCustomizedDtorCompliant1, CUSTOMIZED, + CUSTOMIZED, CUSTOMIZED, CUSTOMIZED, CUSTOMIZED) +}; + +class CopyEnabledCustomizedDtorCompliant2 { // COMPLIANT: copy-enabled (2) +public: + COPY_CTOR(CopyEnabledCustomizedDtorCompliant2) CUSTOMIZED; + // No move constructor declared + COPY_ASSIGN(CopyEnabledCustomizedDtorCompliant2) DELETED; + MOVE_ASSIGN(CopyEnabledCustomizedDtorCompliant2) DELETED; + ~CopyEnabledCustomizedDtorCompliant2() CUSTOMIZED; +}; + +// copy-assignable class with both move operations not declared +class CopyAssignableCustomizedDtorCompliant1 { // COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorCompliant1) CUSTOMIZED; + // Move constructor is not declared + COPY_ASSIGN(CopyAssignableCustomizedDtorCompliant1) CUSTOMIZED; + // Move assignment operator is not declared + DTOR(CopyAssignableCustomizedDtorCompliant1) CUSTOMIZED +}; + +// copy-assignable class with only one of move operations not declared +class CopyAssignableCustomizedDtorNonCompliant1 { // NON_COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorNonCompliant1) CUSTOMIZED; + MOVE_CTOR(CopyAssignableCustomizedDtorNonCompliant1) CUSTOMIZED; + COPY_ASSIGN(CopyAssignableCustomizedDtorNonCompliant1) CUSTOMIZED; + // Move assignment operator is not declared + DTOR(CopyAssignableCustomizedDtorNonCompliant1) CUSTOMIZED; +}; + +// copy-assignable class with only one of move operations not declared +class CopyAssignableCustomizedDtorNonCompliant2 { // NON_COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorNonCompliant2) CUSTOMIZED; + // Move constructor is not declared + COPY_ASSIGN(CopyAssignableCustomizedDtorNonCompliant2) CUSTOMIZED; + MOVE_ASSIGN(CopyAssignableCustomizedDtorNonCompliant2) CUSTOMIZED; + DTOR(CopyAssignableCustomizedDtorNonCompliant2) CUSTOMIZED; +}; + +// copy-assignable class with only a copy operation not declared +class CopyAssignableCustomizedDtorNonCompliant3 { // NON_COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorNonCompliant3) CUSTOMIZED; + MOVE_CTOR(CopyAssignableCustomizedDtorNonCompliant3) CUSTOMIZED; + // Copy assignment operator is not declared + MOVE_ASSIGN(CopyAssignableCustomizedDtorNonCompliant3) CUSTOMIZED; + DTOR(CopyAssignableCustomizedDtorNonCompliant3) CUSTOMIZED; +}; + +// copy-assignable class with only one of move operations not declared +class CopyAssignableCustomizedDtorNonCompliant4 { // NON_COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorNonCompliant4) CUSTOMIZED; + MOVE_CTOR(CopyAssignableCustomizedDtorNonCompliant4) DEFAULTED; + COPY_ASSIGN(CopyAssignableCustomizedDtorNonCompliant4) CUSTOMIZED; + // Move assignment operator is not declared + DTOR(CopyAssignableCustomizedDtorNonCompliant4) CUSTOMIZED; +}; + +// copy-assignable class with only one of move operations not declared +class CopyAssignableCustomizedDtorNonCompliant5 { // NON_COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorNonCompliant5) CUSTOMIZED; + // Move constructor is not declared + COPY_ASSIGN(CopyAssignableCustomizedDtorNonCompliant5) CUSTOMIZED; + MOVE_ASSIGN(CopyAssignableCustomizedDtorNonCompliant5) DEFAULTED; + DTOR(CopyAssignableCustomizedDtorNonCompliant5) CUSTOMIZED; +}; + +// A public unmovable base class shall have a public virtual destructor +class UnmovableBaseNonvirtualDtor { // NON_COMPLIANT +public: + DEFINE_ALL_SPECIAL_MEMBERS(UnmovableBaseNonvirtualDtor, DELETED, DELETED, + DELETED, DELETED, DEFAULTED) +}; + +// NON_COMPLIANT - reported by the audit query for having missing info. +class UnmovableNonvirtualDerived : public UnmovableBaseNonvirtualDtor {}; + +class UnmovableBasePublicVirtualDtor { // COMPLIANT +public: + COPY_CTOR(UnmovableBasePublicVirtualDtor) = delete; + MOVE_CTOR(UnmovableBasePublicVirtualDtor) = delete; + COPY_ASSIGN(UnmovableBasePublicVirtualDtor) = delete; + MOVE_ASSIGN(UnmovableBasePublicVirtualDtor) = delete; + + virtual ~UnmovableBasePublicVirtualDtor() = default; +}; + +// NON_COMPLIANT - reported by the audit query for having missing info. +class UnmovableDerivedPublicVirtualDtor + : public UnmovableBasePublicVirtualDtor {}; + +class UnmovablePrivateVirtualDtor { // NON_COMPLIANT +public: + COPY_CTOR(UnmovablePrivateVirtualDtor) = delete; + MOVE_CTOR(UnmovablePrivateVirtualDtor) = delete; + COPY_ASSIGN(UnmovablePrivateVirtualDtor) = delete; + MOVE_ASSIGN(UnmovablePrivateVirtualDtor) = delete; + +private: + virtual ~UnmovablePrivateVirtualDtor() = default; +}; + +// NON_COMPLIANT - reported by the audit query for having missing info. +class UnmovablePrivateVirtualDtorDerived : public UnmovablePrivateVirtualDtor { +}; + +class BaseProtectedDtor { // COMPLIANT +public: + COPY_CTOR(BaseProtectedDtor) = default; + MOVE_CTOR(BaseProtectedDtor) = default; + COPY_ASSIGN(BaseProtectedDtor) = delete; + MOVE_ASSIGN(BaseProtectedDtor) = delete; + +protected: + ~BaseProtectedDtor() = default; +}; + +// NON_COMPLIANT - reported by the audit query for having missing info. +class ProtectedDtorDerived : public BaseProtectedDtor {}; + +class BaseVirtualProtectedDtor { // NON_COMPLIANT +public: + COPY_CTOR(BaseVirtualProtectedDtor) = default; + MOVE_CTOR(BaseVirtualProtectedDtor) = default; + COPY_ASSIGN(BaseVirtualProtectedDtor) = delete; + MOVE_ASSIGN(BaseVirtualProtectedDtor) = delete; + +protected: + virtual ~BaseVirtualProtectedDtor() = default; +}; + +// NON_COMPLIANT - reported by the audit query for having missing info. +class VirtualProtectedDtorDerived : public BaseVirtualProtectedDtor {}; + +} // namespace additional_requirements + +} // namespace fully_specified + +namespace implicit_special_members { + +struct PodClass { // COMPLIANT - we know PODs are OK. + int x; + int y; +}; + +struct TrivialClass { // NON_COMPLIANT - audit result + int x; + int y; + COPY_CTOR(TrivialClass) = default; +}; + +class NonTrivialClass { // NON_COMPLIANT - audit result + int x; + int y; + +public: + ~NonTrivialClass() { x = 1; } +}; + +// CodeQL resolves and stores all of the special member functions in the +// database for this class, so it is not an audit query result. +class NonTrivialDestructor { // NON_COMPLIANT - uncustomized copy ops. + int x; + int y; + +public: + COPY_CTOR(NonTrivialDestructor) = default; + ~NonTrivialDestructor() { x = 1; } +}; + +// This class is not a valid category but hard to analyze in the general case. +// This should not be reported as a violation except by the audit query. +class CopyOnly { // NON_COMPLIANT - audit result + COPY_CTOR(CopyOnly) = default; + MOVE_CTOR(CopyOnly) = delete; + MOVE_ASSIGN(CopyOnly) = delete; +}; + +// this class should not appear in the audit results, because we have all +// members in the database even though they aren't all explicitly declared +class OdrUsedMoveEnabled { // COMPLIANT + NonTrivialClass x; + +public: + // copy operations are generated as deleted + MOVE_CTOR(OdrUsedMoveEnabled) = default; + MOVE_ASSIGN(OdrUsedMoveEnabled) = default; + // destructor is generated as defaulted since it's non trivial and ODR-used. +}; + +void f(OdrUsedMoveEnabled o) { + // This function ensures the non-trivial destructor is ODR-used. + OdrUsedMoveEnabled o3 = std::move(o); + o3 = std::move(o); +} + +} // namespace implicit_special_members diff --git a/rule_packages/cpp/Classes3.json b/rule_packages/cpp/Classes3.json new file mode 100644 index 000000000..c2f438472 --- /dev/null +++ b/rule_packages/cpp/Classes3.json @@ -0,0 +1,58 @@ +{ + "MISRA-C++-2023": { + "RULE-15-0-1": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Incorrect provision of special member functions can lead to unexpected or undefined behavior when objects of the class are copied, moved, or destroyed.", + "kind": "problem", + "name": "Special member functions shall be provided appropriately", + "precision": "medium", + "severity": "warning", + "short_name": "ImproperlyProvidedSpecialMemberFunctions", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ], + "implementation_scope": { + "description": "This query does not model `std::is_(copy|move)_(constructible|assignable)` traits or the implicit generation of certain special member functions.", + "items": [ + "The only classes that will be analyzed are classes for which the CodeQL database contains an entry for each special member function. See the audit version of this query to find classes we do not analyze.", + "The CodeQL database does not contain certain special member function definitions, such as those that are trivial and not ODR-used. As a result, a query cannot determine whether such special member functions exist or are correctly provided.", + "The CodeQL database does not contain the necessary compiler intrinsics to query `std::is_(copy|move)_(constructible|assignable)` traits.", + "Fully implementing `std::is_(copy|move)_(constructible|assignable)` traits without access to these intrinsics requires a very thorough reimplementation of C++ overload resolution rules, which are spread across dozens of sections of the language specification.", + "Fully implementing implicit special member function generation also requires performing overload resolution on the member-wise direct initialization and assignment for potentially constructed base objects.", + "Therefore, this query considers classes with a public, non-deleted move/copy constructor/assignment operator, as being copy/move constructible/assignable.", + "And this query does not analyze classes for which we do not have a complete set of special member function definitions in the CodeQL database.", + "To analyze the remaining classes, one can run the audit version of this query to find those that are excluded, and for each excluded class, one can either explicitly define all as `= default` or `= delete` or perform a human review of compliance and correctness." + ] + } + }, + { + "description": "Audit: incorrect provision of special member functions can lead to unexpected or undefined behavior when objects of the class are copied, moved, or destroyed.", + "kind": "problem", + "name": "Special member functions shall be provided appropriately, Audit", + "precision": "low", + "severity": "warning", + "short_name": "ImproperlyProvidedSpecialMemberFunctionsAudit", + "tags": [ + "scope/single-translation-unit", + "correctness", + "external/misra/audit", + "maintainability" + ], + "implementation_scope": { + "description": "This query finds all classes for which the CodeQL database does not contain a complete set of special member function definitions, allowing a human reviewer to audit compliance where the automated query does not, or provide explicit `= default` or `= delete` definitions so the main query will analyze them.", + "items": [ + ] + } + } + ], + "title": "Special member functions shall be provided appropriately" + } + } +} \ No newline at end of file