Skip to content

Commit 95a5a61

Browse files
Merge pull request #1121 from github/michaelrfairhurst/classes-3-take-2-rule-15-0-1
Narrowly focused implementation of RULE 15-0-1
2 parents 956f69d + 705b598 commit 95a5a61

11 files changed

Lines changed: 1183 additions & 0 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
2+
import cpp
3+
import RuleMetadata
4+
import codingstandards.cpp.exclusions.RuleMetadata
5+
6+
newtype Classes3Query =
7+
TImproperlyProvidedSpecialMemberFunctionsQuery() or
8+
TImproperlyProvidedSpecialMemberFunctionsAuditQuery()
9+
10+
predicate isClasses3QueryMetadata(Query query, string queryId, string ruleId, string category) {
11+
query =
12+
// `Query` instance for the `improperlyProvidedSpecialMemberFunctions` query
13+
Classes3Package::improperlyProvidedSpecialMemberFunctionsQuery() and
14+
queryId =
15+
// `@id` for the `improperlyProvidedSpecialMemberFunctions` query
16+
"cpp/misra/improperly-provided-special-member-functions" and
17+
ruleId = "RULE-15-0-1" and
18+
category = "required"
19+
or
20+
query =
21+
// `Query` instance for the `improperlyProvidedSpecialMemberFunctionsAudit` query
22+
Classes3Package::improperlyProvidedSpecialMemberFunctionsAuditQuery() and
23+
queryId =
24+
// `@id` for the `improperlyProvidedSpecialMemberFunctionsAudit` query
25+
"cpp/misra/improperly-provided-special-member-functions-audit" and
26+
ruleId = "RULE-15-0-1" and
27+
category = "required"
28+
}
29+
30+
module Classes3Package {
31+
Query improperlyProvidedSpecialMemberFunctionsQuery() {
32+
//autogenerate `Query` type
33+
result =
34+
// `Query` type for `improperlyProvidedSpecialMemberFunctions` query
35+
TQueryCPP(TClasses3PackageQuery(TImproperlyProvidedSpecialMemberFunctionsQuery()))
36+
}
37+
38+
Query improperlyProvidedSpecialMemberFunctionsAuditQuery() {
39+
//autogenerate `Query` type
40+
result =
41+
// `Query` type for `improperlyProvidedSpecialMemberFunctionsAudit` query
42+
TQueryCPP(TClasses3PackageQuery(TImproperlyProvidedSpecialMemberFunctionsAuditQuery()))
43+
}
44+
}

cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import BannedSyntax
1818
import BannedTypes
1919
import Classes
2020
import Classes2
21+
import Classes3
2122
import Classes4
2223
import Comments
2324
import Concurrency
@@ -129,6 +130,7 @@ newtype TCPPQuery =
129130
TBannedTypesPackageQuery(BannedTypesQuery q) or
130131
TClassesPackageQuery(ClassesQuery q) or
131132
TClasses2PackageQuery(Classes2Query q) or
133+
TClasses3PackageQuery(Classes3Query q) or
132134
TClasses4PackageQuery(Classes4Query q) or
133135
TCommentsPackageQuery(CommentsQuery q) or
134136
TConcurrencyPackageQuery(ConcurrencyQuery q) or
@@ -240,6 +242,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat
240242
isBannedTypesQueryMetadata(query, queryId, ruleId, category) or
241243
isClassesQueryMetadata(query, queryId, ruleId, category) or
242244
isClasses2QueryMetadata(query, queryId, ruleId, category) or
245+
isClasses3QueryMetadata(query, queryId, ruleId, category) or
243246
isClasses4QueryMetadata(query, queryId, ruleId, category) or
244247
isCommentsQueryMetadata(query, queryId, ruleId, category) or
245248
isConcurrencyQueryMetadata(query, queryId, ruleId, category) or
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/**
2+
* Provides predicates and classes to perform a precursor analysis of which classes the rule can
3+
* potentially analyze, or should be excluded and instead selected by the audit query.
4+
*
5+
* For example, the class `AnalyzableClass` resolves all of the special member functions that we
6+
* must have in order to determine rule compliance.
7+
*
8+
* Part of what this module does is perform a very approximate analysis of which classes will
9+
* produce a value of true in `std::is_(copy|move)_(constructible|assignable)_v`.
10+
*
11+
* Fully predicting these standard type traits requires performing a very thorough overload
12+
* resolution analysis, including value category propagation and reference binding and user defined
13+
* conversion operators and standard conversions and promotions and ranking viable candidates and
14+
* properly handling ambiguous overloads.
15+
*/
16+
17+
import cpp
18+
19+
/**
20+
* For `std::is_(copy|move)_(constructible|assignable)_v` to return true for a given class, the
21+
* member must exist, it must not be deleted, and it must be publicly accessible.
22+
*
23+
* This is a very coarse approximation of true behavior of the standard type traits.
24+
*/
25+
private predicate isUsable(MemberFunction f) {
26+
not f.isDeleted() and
27+
f.isPublic()
28+
}
29+
30+
/**
31+
* Holds if the member function `f` has been "customized" by the user, e.g., they explicitly wrote
32+
* the implementation of the function.
33+
*/
34+
private predicate isMemberCustomized(MemberFunction f) {
35+
exists(f.getDefinition()) and
36+
not f.isDefaulted() and
37+
not f.isDeleted() and
38+
not f.isCompilerGenerated()
39+
}
40+
41+
/**
42+
* Holds if the user declared the member function `f`, as opposed to it being implicitly declared
43+
* by the compiler.
44+
*
45+
* Note that `T(T&&) = default` and `T(T&&) = delete` are both user declared. This is not to be
46+
* confused with "user defined."
47+
*/
48+
private predicate isUserDeclared(MemberFunction f) { not f.isCompilerGenerated() }
49+
50+
/**
51+
* Holds if the class `c` contains a user declared copy constructor, copy assignment operator,
52+
* or destructor.
53+
*
54+
* Encapsulates common behavior related to implicit move operation suppression defined in
55+
* [class.copy.ctor]/8 and [class.copy.assign]/4.
56+
*/
57+
predicate hasUserDeclaredCopyOrDestruct(Class c) {
58+
isUserDeclared(c.getAConstructor().(CopyConstructor))
59+
or
60+
isUserDeclared(c.getAMemberFunction().(CopyAssignmentOperator))
61+
or
62+
isUserDeclared(c.getDestructor())
63+
}
64+
65+
/**
66+
* Holds if the implicit move constructor of the class `c` will not be declared, as specified in
67+
* [class.copy.ctor]/8.
68+
*/
69+
private predicate implicitMoveConstructorSuppressed(Class c) {
70+
// Sanity check: if the move constructor exists, it must not have been suppressed.
71+
not exists(c.getAConstructor().(MoveConstructor)) and
72+
(
73+
// Per the spec, the move constructor is suppressed if:
74+
// There is a user declared copy constructor, copy assignment operator, or destructor,
75+
hasUserDeclaredCopyOrDestruct(c)
76+
or
77+
// or, the move assignment operator is user declared.
78+
isUserDeclared(c.getAMemberFunction().(MoveAssignmentOperator))
79+
)
80+
}
81+
82+
/**
83+
* Holds if the implicit move assignment operator of the class `c` will not be declared, as specified
84+
* in [class.copy.assign]/4.
85+
*/
86+
private predicate implicitMoveAssignmentSuppressed(Class c) {
87+
// Sanity check: if the move assignment operator exists, it must not have been suppressed.
88+
not exists(c.getAMemberFunction().(MoveAssignmentOperator)) and
89+
(
90+
// Per the spec, the move assignment operator is suppressed if:
91+
// There is a user declared copy constructor, copy assignment operator, or destructor,
92+
hasUserDeclaredCopyOrDestruct(c)
93+
or
94+
// or, the move constructor is user declared.
95+
isUserDeclared(c.getAMemberFunction().(MoveConstructor))
96+
)
97+
}
98+
99+
/**
100+
* Returns the move constructor of the class `c` if it exists, or the copy constructor if it does
101+
* not exist and the implicit definition was suppressed by the compiler.
102+
*
103+
* For example:
104+
* ```cpp
105+
* class OnlyCopyCtor {
106+
* public:
107+
* OnlyCopyCtor(const OnlyCopyCtor &) = default;
108+
* };
109+
*
110+
* static_assert(std::is_copy_constructible_v<OnlyCopyCtor>); // Succeeds
111+
* static_assert(std::is_move_constructible_v<OnlyCopyCtor>); // Also succeeds
112+
* ```
113+
*
114+
* Note that without the declared copy constructor, the compiler may define an implicit move
115+
* constructor.
116+
*
117+
* Additionally note that if the move constructor was declared as `= delete;`, then the second
118+
* assertion in the above example would fail.
119+
*/
120+
private Constructor getMoveConstructor(Class c) {
121+
if implicitMoveConstructorSuppressed(c)
122+
then result = c.getAConstructor().(CopyConstructor)
123+
else result = c.getAConstructor().(MoveConstructor)
124+
}
125+
126+
/**
127+
* Returns the move assignment operator of the class `c` if it exists, or the copy assignment
128+
* operator if it does not exist and the implicit definition was suppressed by the compiler.
129+
*
130+
* For example:
131+
* ```cpp
132+
* class OnlyCopyAssign {
133+
* public:
134+
* OnlyCopyAssign& operator=(const OnlyCopyAssign &) = default;
135+
* };
136+
*
137+
* static_assert(std::is_copy_assignable_v<OnlyCopyAssign>); // Succeeds
138+
* static_assert(std::is_move_assignable_v<OnlyCopyAssign>); // Also succeeds
139+
* ```
140+
*
141+
* Note that without the declared copy assignment operator, the compiler may define an implicit move
142+
* assignment operator.
143+
*
144+
* Additionally note that if the move assignment operator was declared as `= delete;`, then the second
145+
* assertion in the above example would fail.
146+
*/
147+
private Operator getMoveAssign(Class c) {
148+
if implicitMoveAssignmentSuppressed(c)
149+
then result = c.getAMemberFunction().(CopyAssignmentOperator)
150+
else result = c.getAMemberFunction().(MoveAssignmentOperator)
151+
}
152+
153+
/**
154+
* The types of special member functions that the `AnalyzableClass` tracks and analyzes.
155+
*/
156+
newtype TSpecialMember =
157+
TMoveConstructor() or
158+
TMoveAssignmentOperator() or
159+
TCopyConstructor() or
160+
TCopyAssignmentOperator() or
161+
TDestructor()
162+
163+
/**
164+
* A class for which we can see all special member functions, including implicitly declared ones,
165+
* and therefore we can attempt to analyze it in the current rule.
166+
*
167+
* If one of the special member functions cannot be found, we cannot know if it is missing because
168+
* it should not have been generated, or if EDG did not emit a definition for it. For instance, EDG
169+
* may not generate these functions if they are trivial, or if they are delete, or not ODR used. We,
170+
* the authors of this project, do not know the exact conditions we have to consider in this case.
171+
*
172+
* Determining for ourselves whether a certain constructor would be implicitly declared, and with
173+
* what signature, and whether it is deleted, requires implementing a significant portion of the C++
174+
* language rules regarding special member function generation, including a significant portion of
175+
* C++ overload resolution rules which are non-trivial.
176+
*
177+
* Therefore we must find a definition for each special member in the database to proceed. The only
178+
* exception we allow is certain missing `MoveConstructor` or `MoveAssignmentOperator` members; if
179+
* the class defines copy operations or the destructor, we expect these to be missing, and typically
180+
* this means the corresponding copy operation acts in place of the equivalent move.
181+
*
182+
* The last difficulty in analysis that this class attempts to handle is the values of the type
183+
* traits `std::is_(copy|move)_(constructible|assignable)`. These type traits are defined as true if
184+
* certain C++ expressions, such as `T(declval<T>())` or `declval<T>() = declval<T>()`, are
185+
* well-formed. We cannot correctly determine this in all cases without implementing a significant
186+
* portion of the C++ language rules for reference binding and overload resolution.
187+
*
188+
* To handle these type traits, we take a very rough approximation. If the corresponding special
189+
* member function is public and not deleted, then we assume the type trait will evaluate to true.
190+
* We also handle the case where a user declared copy operation suppresses the implicit move
191+
* operations, which typically means overload resolution selects the copy operation. (This is not
192+
* the case when the move operations are declared as deleted). We handle this by treating the copy
193+
* operation as effectively acting in place of the move operation for the purposes of evaluating
194+
* the type traits.
195+
*/
196+
class AnalyzableClass extends Class {
197+
CopyConstructor copyCtor;
198+
// The move constructor may be suppressed, and the copy constructor may be used during moves.
199+
Constructor moveCtor;
200+
CopyAssignmentOperator copyAssign;
201+
// The move assignment operator may be suppressed, and the copy assignment operator may be used during moves.
202+
Operator moveAssign;
203+
Destructor dtor;
204+
205+
AnalyzableClass() {
206+
copyCtor = this.getAConstructor() and
207+
moveCtor = getMoveConstructor(this) and
208+
copyAssign = this.getAMemberFunction() and
209+
moveAssign = getMoveAssign(this) and
210+
dtor = this.getDestructor()
211+
}
212+
213+
/**
214+
* Holds `std::is_move_constructible_v<T>` is likely true for this class.
215+
*
216+
* Specifically this holds if there's a non-deleted public move constructor available for this
217+
* class, or if there is a non-deleted public copy constructor that acts as the move constructor.
218+
*/
219+
predicate moveConstructible() { isUsable(moveCtor) }
220+
221+
/**
222+
* Holds `std::is_copy_constructible_v<T>` is likely true for this class.
223+
*
224+
* Specifically this holds if there's a non-deleted public copy constructor available for this
225+
* class.
226+
*/
227+
predicate copyConstructible() { isUsable(copyCtor) }
228+
229+
/**
230+
* Holds `std::is_move_assignable_v<T>` is likely true for this class.
231+
*
232+
* Specifically this holds if there's a non-deleted public move assignment operator available for
233+
* this class, or if there is a non-deleted public copy assignment operator that acts as the move
234+
* assignment operator.
235+
*/
236+
predicate moveAssignable() { isUsable(moveAssign) }
237+
238+
/**
239+
* Holds `std::is_copy_assignable_v<T>` is likely true for this class.
240+
*
241+
* Specifically this holds if there's a non-deleted public copy assignment operator available for
242+
* this class.
243+
*/
244+
predicate copyAssignable() { isUsable(copyAssign) }
245+
246+
/**
247+
* Holds if the given special member function `s` is customized for this class.
248+
*
249+
* For most cases, this checks that the given special member function `s` has a user-provided
250+
* body (other than `= default;` or `= delete;`).
251+
*
252+
* If the class has copy operations that act in place of the move operations, that means the
253+
* corresponding move operation was not declared, so we say this predicate does not hold for the
254+
* given move operation `s`.
255+
*/
256+
predicate isCustomized(TSpecialMember s) {
257+
s instanceof TMoveConstructor and
258+
isMemberCustomized(moveCtor) and
259+
declaresMoveConstructor()
260+
or
261+
s instanceof TMoveAssignmentOperator and
262+
isMemberCustomized(moveAssign) and
263+
declaresMoveAssignmentOperator()
264+
or
265+
s instanceof TCopyConstructor and isMemberCustomized(copyCtor)
266+
or
267+
s instanceof TCopyAssignmentOperator and isMemberCustomized(copyAssign)
268+
or
269+
s instanceof TDestructor and isMemberCustomized(dtor)
270+
}
271+
272+
/**
273+
* Holds if this class declares a move constructor.
274+
*
275+
* This will be true if move constructor resolution found a non-implicit constructor that is not
276+
* the copy constructor masquerading as a move constructor.
277+
*/
278+
predicate declaresMoveConstructor() { not moveCtor = copyCtor and isUserDeclared(moveCtor) }
279+
280+
/**
281+
* Holds if this class declares a move assignment operator.
282+
*
283+
* This will be true if move assignment resolution found a non-implicit operator that is not
284+
* the copy assignment operator masquerading as a move assignment operator.
285+
*/
286+
predicate declaresMoveAssignmentOperator() {
287+
not moveAssign = copyAssign and isUserDeclared(moveAssign)
288+
}
289+
}

0 commit comments

Comments
 (0)