@@ -36,6 +36,116 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
36
36
Nodes.insert (Match.getNodeAs <Node>(ID));
37
37
}
38
38
39
+ // Returns true if both types refer to the same type,
40
+ // ignoring the const-qualifier.
41
+ bool isSameTypeIgnoringConst (QualType A, QualType B) {
42
+ A = A.getCanonicalType ();
43
+ B = B.getCanonicalType ();
44
+ A.addConst ();
45
+ B.addConst ();
46
+ return A == B;
47
+ }
48
+
49
+ // Returns true if `D` and `O` have the same parameter types.
50
+ bool hasSameParameterTypes (const CXXMethodDecl &D, const CXXMethodDecl &O) {
51
+ if (D.getNumParams () != O.getNumParams ())
52
+ return false ;
53
+ for (int I = 0 , E = D.getNumParams (); I < E; ++I) {
54
+ if (!isSameTypeIgnoringConst (D.getParamDecl (I)->getType (),
55
+ O.getParamDecl (I)->getType ()))
56
+ return false ;
57
+ }
58
+ return true ;
59
+ }
60
+
61
+ // If `D` has a const-qualified overload with otherwise identical
62
+ // ref-qualifiers and parameter types, returns that overload.
63
+ const CXXMethodDecl *findConstOverload (const CXXMethodDecl &D) {
64
+ assert (!D.isConst ());
65
+
66
+ DeclContext::lookup_result LookupResult =
67
+ D.getParent ()->lookup (D.getNameInfo ().getName ());
68
+ if (LookupResult.isSingleResult ()) {
69
+ // No overload.
70
+ return nullptr ;
71
+ }
72
+ for (const Decl *Overload : LookupResult) {
73
+ const auto *O = dyn_cast<CXXMethodDecl>(Overload);
74
+ if (O && !O->isDeleted () && O->isConst () &&
75
+ O->getRefQualifier () == D.getRefQualifier () &&
76
+ hasSameParameterTypes (D, *O))
77
+ return O;
78
+ }
79
+ return nullptr ;
80
+ }
81
+
82
+ // Returns true if both types are pointers or reference to the same type,
83
+ // ignoring the const-qualifier.
84
+ bool pointsToSameTypeIgnoringConst (QualType A, QualType B) {
85
+ assert (A->isPointerType () || A->isReferenceType ());
86
+ assert (B->isPointerType () || B->isReferenceType ());
87
+ return isSameTypeIgnoringConst (A->getPointeeType (), B->getPointeeType ());
88
+ }
89
+
90
+ // Return true if non-const member function `M` likely does not mutate `*this`.
91
+ //
92
+ // Note that if the member call selects a method/operator `f` that
93
+ // is not const-qualified, then we also consider that the object is
94
+ // not mutated if:
95
+ // - (A) there is a const-qualified overload `cf` of `f` that has
96
+ // the
97
+ // same ref-qualifiers;
98
+ // - (B) * `f` returns a value, or
99
+ // * if `f` returns a `T&`, `cf` returns a `const T&` (up to
100
+ // possible aliases such as `reference` and
101
+ // `const_reference`), or
102
+ // * if `f` returns a `T*`, `cf` returns a `const T*` (up to
103
+ // possible aliases).
104
+ // - (C) the result of the call is not mutated.
105
+ //
106
+ // The assumption that `cf` has the same semantics as `f`.
107
+ // For example:
108
+ // - In `std::vector<T> v; const T t = v[...];`, we consider that
109
+ // expression `v[...]` does not mutate `v` as
110
+ // `T& std::vector<T>::operator[]` has a const overload
111
+ // `const T& std::vector<T>::operator[] const`, and the
112
+ // result expression of type `T&` is only used as a `const T&`;
113
+ // - In `std::map<K, V> m; V v = m.at(...);`, we consider
114
+ // `m.at(...)` to be an immutable access for the same reason.
115
+ // However:
116
+ // - In `std::map<K, V> m; const V v = m[...];`, We consider that
117
+ // `m[...]` mutates `m` as `V& std::map<K, V>::operator[]` does
118
+ // not have a const overload.
119
+ // - In `std::vector<T> v; T& t = v[...];`, we consider that
120
+ // expression `v[...]` mutates `v` as the result is kept as a
121
+ // mutable reference.
122
+ //
123
+ // This function checks (A) ad (B), but the caller should make sure that the
124
+ // object is not mutated through the return value.
125
+ bool isLikelyShallowConst (const CXXMethodDecl &M) {
126
+ assert (!M.isConst ());
127
+ // The method can mutate our variable.
128
+
129
+ // (A)
130
+ const CXXMethodDecl *ConstOverload = findConstOverload (M);
131
+ if (ConstOverload == nullptr ) {
132
+ return false ;
133
+ }
134
+
135
+ // (B)
136
+ const QualType CallTy = M.getReturnType ().getCanonicalType ();
137
+ const QualType OverloadTy = ConstOverload->getReturnType ().getCanonicalType ();
138
+ if (CallTy->isReferenceType ()) {
139
+ return OverloadTy->isReferenceType () &&
140
+ pointsToSameTypeIgnoringConst (CallTy, OverloadTy);
141
+ }
142
+ if (CallTy->isPointerType ()) {
143
+ return OverloadTy->isPointerType () &&
144
+ pointsToSameTypeIgnoringConst (CallTy, OverloadTy);
145
+ }
146
+ return isSameTypeIgnoringConst (CallTy, OverloadTy);
147
+ }
148
+
39
149
// A matcher that matches DeclRefExprs that are used in ways such that the
40
150
// underlying declaration is not modified.
41
151
// If the declaration is of pointer type, `Indirections` specifies the level
@@ -54,16 +164,15 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
54
164
// matches (A).
55
165
//
56
166
AST_MATCHER_P (DeclRefExpr, doesNotMutateObject, int , Indirections) {
57
- // We walk up the parents of the DeclRefExpr recursively until we end up on a
58
- // parent that cannot modify the underlying object. There are a few kinds of
59
- // expressions:
60
- // - Those that cannot be used to mutate the underlying object. We can stop
167
+ // We walk up the parents of the DeclRefExpr recursively. There are a few
168
+ // kinds of expressions:
169
+ // - Those that cannot be used to mutate the underlying variable. We can stop
61
170
// recursion there.
62
- // - Those that can be used to mutate the underlying object in analyzable
171
+ // - Those that can be used to mutate the underlying variable in analyzable
63
172
// ways (such as taking the address or accessing a subobject). We have to
64
173
// examine the parents.
65
174
// - Those that we don't know how to analyze. In that case we stop there and
66
- // we assume that they can mutate the underlying expression.
175
+ // we assume that they can modify the expression.
67
176
68
177
struct StackEntry {
69
178
StackEntry (const Expr *E, int Indirections)
@@ -90,7 +199,7 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
90
199
assert (Ty->isPointerType ());
91
200
Ty = Ty->getPointeeType ().getCanonicalType ();
92
201
}
93
- if (Ty.isConstQualified ())
202
+ if (Ty-> isVoidType () || Ty .isConstQualified ())
94
203
continue ;
95
204
96
205
// Otherwise we have to look at the parents to see how the expression is
@@ -159,11 +268,56 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
159
268
// The method call cannot mutate our variable.
160
269
continue ;
161
270
}
271
+ if (isLikelyShallowConst (*Method)) {
272
+ // We still have to check that the object is not modified through
273
+ // the method's return value (C).
274
+ const auto MemberParents = Ctx.getParents (*Member);
275
+ assert (MemberParents.size () == 1 );
276
+ const auto *Call = MemberParents[0 ].get <CallExpr>();
277
+ // If `o` is an object of class type and `f` is a member function,
278
+ // then `o.f` has to be used as part of a call expression.
279
+ assert (Call != nullptr && " member function has to be called" );
280
+ Stack.emplace_back (
281
+ Call,
282
+ Method->getReturnType ().getCanonicalType ()->isPointerType ()
283
+ ? 1
284
+ : 0 );
285
+ continue ;
286
+ }
162
287
return false ;
163
288
}
164
289
Stack.emplace_back (Member, 0 );
165
290
continue ;
166
291
}
292
+ if (const auto *const OpCall = dyn_cast<CXXOperatorCallExpr>(P)) {
293
+ // Operator calls have function call syntax. The `*this` parameter
294
+ // is the first parameter.
295
+ if (OpCall->getNumArgs () == 0 || OpCall->getArg (0 ) != Entry.E ) {
296
+ return false ;
297
+ }
298
+ const auto *const Method =
299
+ dyn_cast<CXXMethodDecl>(OpCall->getDirectCallee ());
300
+
301
+ if (Method == nullptr ) {
302
+ // This is not a member operator. Typically, a friend operator. These
303
+ // are handled like function calls.
304
+ return false ;
305
+ }
306
+
307
+ if (Method->isConst () || Method->isStatic ()) {
308
+ continue ;
309
+ }
310
+ if (isLikelyShallowConst (*Method)) {
311
+ // We still have to check that the object is not modified through
312
+ // the operator's return value (C).
313
+ Stack.emplace_back (
314
+ OpCall,
315
+ Method->getReturnType ().getCanonicalType ()->isPointerType () ? 1
316
+ : 0 );
317
+ continue ;
318
+ }
319
+ return false ;
320
+ }
167
321
168
322
if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
169
323
switch (Op->getOpcode ()) {
0 commit comments