-
Notifications
You must be signed in to change notification settings - Fork 75
Expand file tree
/
Copy pathAnalyzableClass.qll
More file actions
258 lines (241 loc) · 10.3 KB
/
AnalyzableClass.qll
File metadata and controls
258 lines (241 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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<OnlyCopyCtor>); // Succeeds
* static_assert(std::is_move_constructible_v<OnlyCopyCtor>); // 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<OnlyCopyAssign>); // Succeeds
* static_assert(std::is_move_assignable_v<OnlyCopyAssign>); // 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<T>())` or `declval<T>() = declval<T>()`, 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<T>` 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<T>` 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<T>` 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<T>` 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)
}
}