10
10
#include " Matchers.h"
11
11
#include " clang/AST/ASTContext.h"
12
12
#include " clang/AST/DeclCXX.h"
13
+ #include " clang/AST/ExprCXX.h"
13
14
#include " clang/ASTMatchers/ASTMatchFinder.h"
15
+ #include < cassert>
14
16
15
17
namespace clang ::tidy::utils::decl_ref_expr {
16
18
@@ -34,69 +36,185 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
34
36
Nodes.insert (Match.getNodeAs <Node>(ID));
35
37
}
36
38
39
+ // A matcher that matches DeclRefExprs that are used in ways such that the
40
+ // underlying declaration is not modified.
41
+ // If the declaration is of pointer type, `Indirections` specifies the level
42
+ // of indirection of the object whose mutations we are tracking.
43
+ //
44
+ // For example, given:
45
+ // ```
46
+ // int i;
47
+ // int* p;
48
+ // p = &i; // (A)
49
+ // *p = 3; // (B)
50
+ // ```
51
+ //
52
+ // `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(0))` matches
53
+ // (B), but `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(1))`
54
+ // matches (A).
55
+ //
56
+ 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
61
+ // recursion there.
62
+ // - Those that can be used to mutate the underlying object in analyzable
63
+ // ways (such as taking the address or accessing a subobject). We have to
64
+ // examine the parents.
65
+ // - 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.
67
+
68
+ struct StackEntry {
69
+ StackEntry (const Expr *E, int Indirections)
70
+ : E(E), Indirections(Indirections) {}
71
+ // The expression to analyze.
72
+ const Expr *E;
73
+ // The number of pointer indirections of the object being tracked (how
74
+ // many times an address was taken).
75
+ int Indirections;
76
+ };
77
+
78
+ llvm::SmallVector<StackEntry, 4 > Stack;
79
+ Stack.emplace_back (&Node, Indirections);
80
+ auto &Ctx = Finder->getASTContext ();
81
+
82
+ while (!Stack.empty ()) {
83
+ const StackEntry Entry = Stack.back ();
84
+ Stack.pop_back ();
85
+
86
+ // If the expression type is const-qualified at the appropriate indirection
87
+ // level then we can not mutate the object.
88
+ QualType Ty = Entry.E ->getType ().getCanonicalType ();
89
+ for (int I = 0 ; I < Entry.Indirections ; ++I) {
90
+ assert (Ty->isPointerType ());
91
+ Ty = Ty->getPointeeType ().getCanonicalType ();
92
+ }
93
+ if (Ty.isConstQualified ()) {
94
+ continue ;
95
+ }
96
+
97
+ // Otherwise we have to look at the parents to see how the expression is
98
+ // used.
99
+ const auto Parents = Ctx.getParents (*Entry.E );
100
+ // Note: most nodes have a single parents, but there exist nodes that have
101
+ // several parents, such as `InitListExpr` that have semantic and syntactic
102
+ // forms.
103
+ for (const auto &Parent : Parents) {
104
+ if (Parent.get <CompoundStmt>()) {
105
+ // Unused block-scope statement.
106
+ continue ;
107
+ }
108
+ const Expr *const P = Parent.get <Expr>();
109
+ if (P == nullptr ) {
110
+ // `Parent` is not an expr (e.g. a `VarDecl`).
111
+ // The case of binding to a `const&` or `const*` variable is handled by
112
+ // the fact that there is going to be a `NoOp` cast to const below the
113
+ // `VarDecl`, so we're not even going to get there.
114
+ // The case of copying into a value-typed variable is handled by the
115
+ // rvalue cast.
116
+ // This triggers only when binding to a mutable reference/ptr variable.
117
+ // FIXME: When we take a mutable reference we could keep checking the
118
+ // new variable for const usage only.
119
+ return false ;
120
+ }
121
+ // Cosmetic nodes.
122
+ if (isa<ParenExpr>(P) || isa<MaterializeTemporaryExpr>(P)) {
123
+ Stack.emplace_back (P, Entry.Indirections );
124
+ continue ;
125
+ }
126
+ if (const auto *const Cast = dyn_cast<CastExpr>(P)) {
127
+ switch (Cast->getCastKind ()) {
128
+ // NoOp casts are used to add `const`. We'll check whether adding that
129
+ // const prevents modification when we process the cast.
130
+ case CK_NoOp:
131
+ // These do nothing w.r.t. to mutability.
132
+ case CK_BaseToDerived:
133
+ case CK_DerivedToBase:
134
+ case CK_UncheckedDerivedToBase:
135
+ case CK_Dynamic:
136
+ case CK_BaseToDerivedMemberPointer:
137
+ case CK_DerivedToBaseMemberPointer:
138
+ Stack.emplace_back (Cast, Entry.Indirections );
139
+ continue ;
140
+ case CK_ToVoid:
141
+ case CK_PointerToBoolean:
142
+ // These do not mutate the underlying variable.
143
+ continue ;
144
+ case CK_LValueToRValue: {
145
+ // An rvalue is immutable.
146
+ if (Entry.Indirections == 0 ) {
147
+ continue ;
148
+ }
149
+ Stack.emplace_back (Cast, Entry.Indirections );
150
+ continue ;
151
+ }
152
+ default :
153
+ // Bail out on casts that we cannot analyze.
154
+ return false ;
155
+ }
156
+ }
157
+ if (const auto *const Member = dyn_cast<MemberExpr>(P)) {
158
+ if (const auto *const Method =
159
+ dyn_cast<CXXMethodDecl>(Member->getMemberDecl ())) {
160
+ if (!Method->isConst ()) {
161
+ // The method can mutate our variable.
162
+ return false ;
163
+ }
164
+ continue ;
165
+ }
166
+ Stack.emplace_back (Member, 0 );
167
+ continue ;
168
+ }
169
+ if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
170
+ switch (Op->getOpcode ()) {
171
+ case UO_AddrOf:
172
+ Stack.emplace_back (Op, Entry.Indirections + 1 );
173
+ continue ;
174
+ case UO_Deref:
175
+ assert (Entry.Indirections > 0 );
176
+ Stack.emplace_back (Op, Entry.Indirections - 1 );
177
+ continue ;
178
+ default :
179
+ // Bail out on unary operators that we cannot analyze.
180
+ return false ;
181
+ }
182
+ }
183
+
184
+ // Assume any other expression can modify the underlying variable.
185
+ return false ;
186
+ }
187
+ }
188
+
189
+ // No parent can modify the variable.
190
+ return true ;
191
+ }
192
+
37
193
} // namespace
38
194
39
- // Finds all DeclRefExprs where a const method is called on VarDecl or VarDecl
40
- // is the a const reference or value argument to a CallExpr or CXXConstructExpr.
41
195
SmallPtrSet<const DeclRefExpr *, 16 >
42
196
constReferenceDeclRefExprs (const VarDecl &VarDecl, const Stmt &Stmt,
43
- ASTContext &Context) {
44
- auto DeclRefToVar =
45
- declRefExpr (to (varDecl (equalsNode (&VarDecl)))).bind (" declRef" );
46
- auto MemberExprOfVar = memberExpr (hasObjectExpression (DeclRefToVar));
47
- auto DeclRefToVarOrMemberExprOfVar =
48
- stmt (anyOf (DeclRefToVar, MemberExprOfVar));
49
- auto ConstMethodCallee = callee (cxxMethodDecl (isConst ()));
50
- // Match method call expressions where the variable is referenced as the this
51
- // implicit object argument and operator call expression for member operators
52
- // where the variable is the 0-th argument.
53
- auto Matches = match (
54
- findAll (expr (anyOf (
55
- cxxMemberCallExpr (ConstMethodCallee,
56
- on (DeclRefToVarOrMemberExprOfVar)),
57
- cxxOperatorCallExpr (ConstMethodCallee,
58
- hasArgument (0 , DeclRefToVarOrMemberExprOfVar))))),
59
- Stmt, Context);
197
+ ASTContext &Context, int Indirections) {
198
+ auto Matches = match (findAll (declRefExpr (to (varDecl (equalsNode (&VarDecl))),
199
+ doesNotMutateObject (Indirections))
200
+ .bind (" declRef" )),
201
+ Stmt, Context);
60
202
SmallPtrSet<const DeclRefExpr *, 16 > DeclRefs;
61
203
extractNodesByIdTo (Matches, " declRef" , DeclRefs);
62
- auto ConstReferenceOrValue =
63
- qualType (anyOf (matchers::isReferenceToConst (),
64
- unless (anyOf (referenceType (), pointerType (),
65
- substTemplateTypeParmType ()))));
66
- auto ConstReferenceOrValueOrReplaced = qualType (anyOf (
67
- ConstReferenceOrValue,
68
- substTemplateTypeParmType (hasReplacementType (ConstReferenceOrValue))));
69
- auto UsedAsConstRefOrValueArg = forEachArgumentWithParam (
70
- DeclRefToVarOrMemberExprOfVar,
71
- parmVarDecl (hasType (ConstReferenceOrValueOrReplaced)));
72
- Matches = match (findAll (invocation (UsedAsConstRefOrValueArg)), Stmt, Context);
73
- extractNodesByIdTo (Matches, " declRef" , DeclRefs);
74
- // References and pointers to const assignments.
75
- Matches = match (
76
- findAll (declStmt (has (varDecl (
77
- hasType (qualType (matchers::isReferenceToConst ())),
78
- hasInitializer (ignoringImpCasts (DeclRefToVarOrMemberExprOfVar)))))),
79
- Stmt, Context);
80
- extractNodesByIdTo (Matches, " declRef" , DeclRefs);
81
- Matches = match (findAll (declStmt (has (varDecl (
82
- hasType (qualType (matchers::isPointerToConst ())),
83
- hasInitializer (ignoringImpCasts (unaryOperator (
84
- hasOperatorName (" &" ),
85
- hasUnaryOperand (DeclRefToVarOrMemberExprOfVar)))))))),
86
- Stmt, Context);
87
- extractNodesByIdTo (Matches, " declRef" , DeclRefs);
204
+
88
205
return DeclRefs;
89
206
}
90
207
91
208
bool isOnlyUsedAsConst (const VarDecl &Var, const Stmt &Stmt,
92
- ASTContext &Context) {
209
+ ASTContext &Context, int Indirections ) {
93
210
// Collect all DeclRefExprs to the loop variable and all CallExprs and
94
211
// CXXConstructExprs where the loop variable is used as argument to a const
95
212
// reference parameter.
96
213
// If the difference is empty it is safe for the loop variable to be a const
97
214
// reference.
98
215
auto AllDeclRefs = allDeclRefExprs (Var, Stmt, Context);
99
- auto ConstReferenceDeclRefs = constReferenceDeclRefExprs (Var, Stmt, Context);
216
+ auto ConstReferenceDeclRefs =
217
+ constReferenceDeclRefExprs (Var, Stmt, Context, Indirections);
100
218
return isSetDifferenceEmpty (AllDeclRefs, ConstReferenceDeclRefs);
101
219
}
102
220
0 commit comments