Narrowly focused implementation of RULE 15-0-1#1121
Narrowly focused implementation of RULE 15-0-1#1121MichaelRFairhurst wants to merge 17 commits intomainfrom
Conversation
…ub.com:github/codeql-coding-standards into michaelrfairhurst/classes-3-take-2-rule-15-0-1
…ub.com:github/codeql-coding-standards into michaelrfairhurst/classes-3-take-2-rule-15-0-1
There was a problem hiding this comment.
Pull request overview
Adds a new MISRA C++:2023 RULE-15-0-1 implementation (plus an audit companion query) to detect incorrectly provided special member functions, including packaging/metadata wiring and a dedicated test suite.
Changes:
- Introduces
ImproperlyProvidedSpecialMemberFunctions.qlandImproperlyProvidedSpecialMemberFunctionsAudit.qlfor RULE-15-0-1. - Adds helper library logic (
AnalyzableClass.qll) to model special-member availability/customization. - Registers the new rule package in exclusions/metadata and adds comprehensive
.qlref/.expected/C++ test fixtures.
Show a summary per file
| File | Description |
|---|---|
| rule_packages/cpp/Classes3.json | Adds RULE-15-0-1 rule package entries (main + audit). |
| cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctions.ql | Main RULE-15-0-1 query implementation and message logic. |
| cpp/misra/src/rules/RULE-15-0-1/ImproperlyProvidedSpecialMemberFunctionsAudit.ql | Audit query for “not analyzable” classes. |
| cpp/misra/src/rules/RULE-15-0-1/AnalyzableClass.qll | Shared modeling of special member functions for analyzable classes. |
| cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll | Wires the new Classes3 package into query metadata/exclusions. |
| cpp/common/src/codingstandards/cpp/exclusions/cpp/Classes3.qll | New autogenerated metadata module for the Classes3 package. |
| cpp/misra/test/rules/RULE-15-0-1/test.cpp | New C++ test fixture covering compliant/non-compliant and audit cases. |
| cpp/misra/test/rules/RULE-15-0-1/*.qlref | Test references to the production queries. |
| cpp/misra/test/rules/RULE-15-0-1/*.expected | Expected results for both main and audit queries. |
Copilot's findings
Comments suppressed due to low confidence (4)
cpp/misra/test/rules/RULE-15-0-1/test.cpp:376
- The audit
.expectedfile reports an alert at this class, but the test source does not mark it// NON_COMPLIANT(or similar). Please add the appropriateNON_COMPLIANTannotation here or remove/update the corresponding row inImproperlyProvidedSpecialMemberFunctionsAudit.expected.
class UnmovableDerivedPublicVirtualDtor
: public UnmovableBasePublicVirtualDtor {};
cpp/misra/test/rules/RULE-15-0-1/test.cpp:403
- The audit
.expectedfile reports an alert at this class, but the test source does not mark it// NON_COMPLIANT(or similar). Please add the appropriateNON_COMPLIANTannotation here or remove/update the corresponding row inImproperlyProvidedSpecialMemberFunctionsAudit.expected.
class ProtectedDtorDerived : public BaseProtectedDtor {};
cpp/misra/test/rules/RULE-15-0-1/test.cpp:416
- The audit
.expectedfile reports an alert at this class, but the test source does not mark it// NON_COMPLIANT(or similar). Please add the appropriateNON_COMPLIANTannotation here or remove/update the corresponding row inImproperlyProvidedSpecialMemberFunctionsAudit.expected.
class VirtualProtectedDtorDerived : public BaseVirtualProtectedDtor {};
cpp/misra/test/rules/RULE-15-0-1/test.cpp:390
- The audit
.expectedfile reports an alert at this class, but the test source does not mark it// NON_COMPLIANT(or similar). Please add the appropriateNON_COMPLIANTannotation here or remove/update the corresponding row inImproperlyProvidedSpecialMemberFunctionsAudit.expected.
class UnmovablePrivateVirtualDtorDerived : public UnmovablePrivateVirtualDtor {
};
- Files reviewed: 11/11 changed files
- Comments generated: 9
| 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) | ||
| } |
There was a problem hiding this comment.
getMoveConstructor and getMoveAssign are public predicates/functions in a library file but have no QLDoc. Either make them private if they are internal helpers, or add QLDoc documenting their behavior and parameters.
There was a problem hiding this comment.
I think it's a good idea to add a motivating example to the QLDoc, respectively.
class OnlyCopyCtor {
public:
OnlyCopyCtor(const OnlyCopyCtor &) = default;
};
static_assert(std::is_copy_constructible_v<OnlyCopyCtor>); // Succeeds
static_assert(std::is_move_constructible_v<OnlyCopyCtor>); // Also succeedsclass OnlyCopyAssign {
public:
OnlyCopyAssign& operator=(const OnlyCopyAssign &) = default;
};
static_assert(std::is_copy_assignable_v<OnlyCopyAssign>); // Succeeds
static_assert(std::is_move_assignable_v<OnlyCopyAssign>); // Also succeedsThere was a problem hiding this comment.
Nice suggestion, done!
| CUSTOMIZED, CUSTOMIZED, CUSTOMIZED, CUSTOMIZED) | ||
| COPY_CTOR(CopyAssignableCustomizedDtorCompliant1) CUSTOMIZED; | ||
| MOVE_CTOR(CopyAssignableCustomizedDtorCompliant1) CUSTOMIZED; | ||
| // No move constructor declared |
There was a problem hiding this comment.
This can now be deleted.
There was a problem hiding this comment.
(I mean the line 290.)
There was a problem hiding this comment.
Oops, I deleted the whole class!
But I think that's ok, it's covered by:
class CustomizedCtorsCompliant { // COMPLIANT: copy-enabled (1)
public:
DEFINE_ALL_SPECIAL_MEMBERS(CustomizedCtorsCompliant, CUSTOMIZED, CUSTOMIZED,
DELETED, DELETED, CUSTOMIZED)
};(both are customized constructors, deleted assignments, and customized dtor)
| * 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] |
There was a problem hiding this comment.
| * See [class.copy]/8 and [class.copy] | |
| * See [class.copy.ctor]/8 and [class.copy] |
| or | ||
| isUserDeclared(c.getAMemberFunction().(CopyAssignmentOperator)) | ||
| or | ||
| isUserDeclared(c.getDestructor()) |
There was a problem hiding this comment.
It may be missing a branch.
| isUserDeclared(c.getDestructor()) | |
| isUserDeclared(c.getDestructor()) | |
| or | |
| isUserDeclared(c.getAMemberFunction().(MoveAssignmentOperator)) |
| private predicate undeclaredMoveException(AnalyzableClass c) { | ||
| // A copy-enabled class may have an undeclared move constructor. | ||
| isCopyEnabled(c) and | ||
| not c.moveAssignable() and |
There was a problem hiding this comment.
Why is this condition needed?
There was a problem hiding this comment.
This (and the next comment) are attempting to check if the class is copy-assignable vs copy-enabled.
If a class is copyEnabled, we can check for the existence of either assignment operator to distinguish copy-enabled from copy-assignable, because a copy-assignable class will have both and a copy-enabled class will have neither.
It took me too long to remember that that's why I wrote it. I'll write a helper predicate that makes this clearer.
| or | ||
| // A copy-assignable class may leave both move operations undeclared. | ||
| isCopyEnabled(c) and | ||
| c.moveAssignable() and |
There was a problem hiding this comment.
Shouldn't this be c.copyAssignable() instead?
| 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." | ||
| ) | ||
| } |
There was a problem hiding this comment.
It was a bit hard for me to follow the logic of this requirement check. Since these come in the form of
isCopyEnabled(class) -> {some_condition}
wouldn't it be simpler to check it using by taking the negation of the above?
isCopyEnabled(class) and ~{some_condition}
For example, the last requirement translates to
c.copyAssignable() implies (
c.isCustomized(TCopyAssignmentOperator()) and
(
c.isCustomized(TMoveConstructor()) and c.isCustomized(TMoveAssignmentOperator()) or
not isUserDeclared(getMoveConstructor(c)) and not isUserDeclared(getMoveAssign(c))
)
)So the check becomes:
c.copyAssignable() and not (
c.isCustomized(TCopyAssignmentOperator()) and
(
c.isCustomized(TMoveConstructor()) and c.isCustomized(TMoveAssignmentOperator()) or
not isUserDeclared(getMoveConstructor(c)) and not isUserDeclared(getMoveAssign(c))
)
)Rewriting above gives an arguably more readable version:
c.copyAssignable() and (
/* 1. The copy assignment operator is not customized. */
not c.isCustomized(TCopyAssignmentOperator()) or
(
/* 2. Any one (or both) of the move operations is not customized. */
not c.isCustomized(TMoveConstructor() or not c.isCustomized(TMoveAssignmentOperator()) and
/* 3. Any one (or both) of the move operations are user-declared. */
(isUserDeclared(getMoveConstructor(c)) or isUserDeclared(getMoveAssign(c)))
)
)| }; | ||
|
|
||
| class CopyEnabledCustomizedDtorCompliant2 { // NON-COMPLIANT: copy-enabled (2) | ||
| class CopyEnabledCustomizedDtorCompliant2 { // COMPLIANT: copy-enabled (2) |
There was a problem hiding this comment.
Thanks for correcting the labels! Phew!
| COPY_CTOR(TrivialClass) = default; | ||
| }; | ||
|
|
||
| class NonTrivialClass { // NON_COMPLIANT - audit result |
There was a problem hiding this comment.
Would there be value to adding a variety of this class that has a copy constructor?
class NonTrivialClass {
int x;
int y;
public:
COPY_CTOR(TrivialClass) = default;
~NonTrivialClass() { x = 1; }
};
jeongsoolee09
left a comment
There was a problem hiding this comment.
Some thoughts, overall looks amazing!! Thank you so much for tackling this to an already great extent!
Description
please enter the description of your change here
Change request type
.ql,.qll,.qlsor unit tests)Rules with added or modified queries
RULE-15-0-1Release change checklist
A change note (development_handbook.md#change-notes) is required for any pull request which modifies:
If you are only adding new rule queries, a change note is not required.
Author: Is a change note required?
🚨🚨🚨
Reviewer: Confirm that format of shared queries (not the .qll file, the
.ql file that imports it) is valid by running them within VS Code.
Reviewer: Confirm that either a change note is not required or the change note is required and has been added.
Query development review checklist
For PRs that add new queries or modify existing queries, the following checklist should be completed by both the author and reviewer:
Author
As a rule of thumb, predicates specific to the query should take no more than 1 minute, and for simple queries be under 10 seconds. If this is not the case, this should be highlighted and agreed in the code review process.
Reviewer
As a rule of thumb, predicates specific to the query should take no more than 1 minute, and for simple queries be under 10 seconds. If this is not the case, this should be highlighted and agreed in the code review process.