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..a556320b6 --- /dev/null +++ b/cpp/misra/src/rules/RULE-15-0-1/AnalyzableClass.qll @@ -0,0 +1,112 @@ +import cpp + +private predicate isUsable(MemberFunction f) { + not f.isDeleted() and + f.isPublic() +} + +private predicate isMemberCustomized(MemberFunction f) { + exists(f.getDefinition()) and + not f.isDefaulted() and + not f.isDeleted() and + not f.isCompilerGenerated() +} + +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. + * + * See [class.copy]/8 and [class.copy] + */ +private predicate implicitMoveIsSuppressed(Class c) { + isUserDeclared(c.getAConstructor().(CopyConstructor)) + or + isUserDeclared(c.getAConstructor().(CopyAssignmentOperator)) + or + isUserDeclared(c.getDestructor()) +} + +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) +} + +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) +} + +newtype TSpecialMember = + TMoveConstructor() or + TMoveAssignmentOperator() or + TCopyConstructor() or + TCopyAssignmentOperator() or + TDestructor() + +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() + } + + predicate exposes(TSpecialMember m) { + m instanceof TMoveConstructor and moveConstructible() + or + m instanceof TMoveAssignmentOperator and moveAssignable() + or + m instanceof TCopyConstructor and copyConstructible() + or + m instanceof TCopyAssignmentOperator and copyAssignable() + or + m instanceof TDestructor and destructible() + } + + predicate moveConstructible() { isUsable(moveCtor) } + + predicate copyConstructible() { isUsable(copyCtor) } + + predicate moveAssignable() { isUsable(moveAssign) } + + predicate copyAssignable() { isUsable(copyAssign) } + + predicate destructible() { isUsable(dtor) } + + 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) + } + + predicate declaresMoveConstructor() { not moveCtor = copyCtor } + + predicate declaresMoveAssignmentOperator() { not moveAssign = copyAssign } +} 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..e1930ae42 --- /dev/null +++ b/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.ql @@ -0,0 +1,150 @@ +/** + * @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.moveAssignable() and + not c.declaresMoveConstructor() + or + // A copy-assignable class may leave both move operations undeclared. + isCopyEnabled(c) and + c.moveAssignable() 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..1c10475ae --- /dev/null +++ b/cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.ql @@ -0,0 +1,49 @@ +/** + * @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 + +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 + + " are 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..eeab1781a --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.expected @@ -0,0 +1,37 @@ +| 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:307:7:307:47 | CopyAssignableCustomizedDtorNonCompliant1 | Class 'CopyAssignableCustomizedDtorNonCompliant1' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:317:7:317:47 | CopyAssignableCustomizedDtorNonCompliant2 | Class 'CopyAssignableCustomizedDtorNonCompliant2' has customized the destructor, but does not customize the move constructor. | +| test.cpp:327:7:327:47 | CopyAssignableCustomizedDtorNonCompliant3 | Class 'CopyAssignableCustomizedDtorNonCompliant3' does not fall into a valid category (isUnmovable, move-only, or copy-enabled). | +| test.cpp:337:7:337:47 | CopyAssignableCustomizedDtorNonCompliant4 | Class 'CopyAssignableCustomizedDtorNonCompliant4' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:337:7:337:47 | CopyAssignableCustomizedDtorNonCompliant4 | Class 'CopyAssignableCustomizedDtorNonCompliant4' has customized the destructor, but does not customize the move constructor. | +| test.cpp:347:7:347:47 | CopyAssignableCustomizedDtorNonCompliant5 | Class 'CopyAssignableCustomizedDtorNonCompliant5' has customized the destructor, but does not customize the move assignment operator. | +| test.cpp:347:7:347:47 | CopyAssignableCustomizedDtorNonCompliant5 | Class 'CopyAssignableCustomizedDtorNonCompliant5' has customized the destructor, but does not customize the move constructor. | +| test.cpp:357:7:357:33 | UnmovableBaseNonvirtualDtor | Class 'UnmovableBaseNonvirtualDtor' violates inheritance requirements for special member functions. | +| test.cpp:378:7:378:33 | UnmovablePrivateVirtualDtor | Class 'UnmovablePrivateVirtualDtor' violates inheritance requirements for special member functions. | +| test.cpp:405:7:405:30 | BaseVirtualProtectedDtor | Class 'BaseVirtualProtectedDtor' violates inheritance requirements for special member functions. | 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..cf289aa06 --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.expected @@ -0,0 +1,8 @@ +| test.cpp:363:7:363: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:375:7:375:39 | UnmovableDerivedPublicVirtualDtor | Class 'UnmovableDerivedPublicVirtualDtor' is not analyzable because the move assignment operator and move constructor are not present in the CodeQL database. | +| test.cpp:389:7:389:40 | UnmovablePrivateVirtualDtorDerived | Class 'UnmovablePrivateVirtualDtorDerived' is not analyzable because the move assignment operator and move constructor are not present in the CodeQL database. | +| test.cpp:403:7:403:26 | ProtectedDtorDerived | Class 'ProtectedDtorDerived' is not analyzable because the destructor and move assignment operator are not present in the CodeQL database. | +| test.cpp:416:7:416:33 | VirtualProtectedDtorDerived | Class 'VirtualProtectedDtorDerived' is not analyzable because the move assignment operator are not present in the CodeQL database. | +| test.cpp:429:8:429: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:435:7:435: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:445:7:445:14 | CopyOnly | Class 'CopyOnly' is not analyzable because the destructor are 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..17810bbd7 --- /dev/null +++ b/cpp/misra/test/rules/RULE-15-0-1/test.cpp @@ -0,0 +1,469 @@ +#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 customized +class CopyAssignableCustomizedDtorCompliant1 { // COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorCompliant1) CUSTOMIZED; + MOVE_CTOR(CopyAssignableCustomizedDtorCompliant1) CUSTOMIZED; + // No move constructor declared + COPY_ASSIGN(CopyAssignableCustomizedDtorCompliant1) DELETED; + MOVE_ASSIGN(CopyAssignableCustomizedDtorCompliant1) DELETED; + ~CopyAssignableCustomizedDtorCompliant1() CUSTOMIZED; +}; + +// copy-assignable class with both move operations not declared +class CopyAssignableCustomizedDtorCompliant2 { // COMPLIANT +public: + COPY_CTOR(CopyAssignableCustomizedDtorCompliant2) CUSTOMIZED; + // Move constructor is not declared + COPY_ASSIGN(CopyAssignableCustomizedDtorCompliant2) CUSTOMIZED; + // Move assignment operator is not declared + DTOR(CopyAssignableCustomizedDtorCompliant2) 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) +}; + +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; +}; + +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; +}; + +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; +}; + +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; +}; + +class VirtualProtectedDtorDerived : public BaseVirtualProtectedDtor {}; + +} // namespace additional_requirements + +} // namespace fully_specified + +namespace audit_results { + +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; } +}; + +// 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 audit_results 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