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,183 @@ 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
+ ASTContext &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
+ // Otherwise we have to look at the parents to see how the expression is
97
+ // used.
98
+ const DynTypedNodeList Parents = Ctx.getParents (*Entry.E );
99
+ // Note: most nodes have a single parents, but there exist nodes that have
100
+ // several parents, such as `InitListExpr` that have semantic and syntactic
101
+ // forms.
102
+ for (const auto &Parent : Parents) {
103
+ if (Parent.get <CompoundStmt>()) {
104
+ // Unused block-scope statement.
105
+ continue ;
106
+ }
107
+ const Expr *const P = Parent.get <Expr>();
108
+ if (P == nullptr ) {
109
+ // `Parent` is not an expr (e.g. a `VarDecl`).
110
+ // The case of binding to a `const&` or `const*` variable is handled by
111
+ // the fact that there is going to be a `NoOp` cast to const below the
112
+ // `VarDecl`, so we're not even going to get there.
113
+ // The case of copying into a value-typed variable is handled by the
114
+ // rvalue cast.
115
+ // This triggers only when binding to a mutable reference/ptr variable.
116
+ // FIXME: When we take a mutable reference we could keep checking the
117
+ // new variable for const usage only.
118
+ return false ;
119
+ }
120
+ // Cosmetic nodes.
121
+ if (isa<ParenExpr>(P) || isa<MaterializeTemporaryExpr>(P)) {
122
+ Stack.emplace_back (P, Entry.Indirections );
123
+ continue ;
124
+ }
125
+ if (const auto *const Cast = dyn_cast<CastExpr>(P)) {
126
+ switch (Cast->getCastKind ()) {
127
+ // NoOp casts are used to add `const`. We'll check whether adding that
128
+ // const prevents modification when we process the cast.
129
+ case CK_NoOp:
130
+ // These do nothing w.r.t. to mutability.
131
+ case CK_BaseToDerived:
132
+ case CK_DerivedToBase:
133
+ case CK_UncheckedDerivedToBase:
134
+ case CK_Dynamic:
135
+ case CK_BaseToDerivedMemberPointer:
136
+ case CK_DerivedToBaseMemberPointer:
137
+ Stack.emplace_back (Cast, Entry.Indirections );
138
+ continue ;
139
+ case CK_ToVoid:
140
+ case CK_PointerToBoolean:
141
+ // These do not mutate the underlying variable.
142
+ continue ;
143
+ case CK_LValueToRValue: {
144
+ // An rvalue is immutable.
145
+ if (Entry.Indirections == 0 )
146
+ continue ;
147
+ Stack.emplace_back (Cast, Entry.Indirections );
148
+ continue ;
149
+ }
150
+ default :
151
+ // Bail out on casts that we cannot analyze.
152
+ return false ;
153
+ }
154
+ }
155
+ if (const auto *const Member = dyn_cast<MemberExpr>(P)) {
156
+ if (const auto *const Method =
157
+ dyn_cast<CXXMethodDecl>(Member->getMemberDecl ())) {
158
+ if (!Method->isConst ()) {
159
+ // The method can mutate our variable.
160
+ return false ;
161
+ }
162
+ continue ;
163
+ }
164
+ Stack.emplace_back (Member, 0 );
165
+ continue ;
166
+ }
167
+ if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
168
+ switch (Op->getOpcode ()) {
169
+ case UO_AddrOf:
170
+ Stack.emplace_back (Op, Entry.Indirections + 1 );
171
+ continue ;
172
+ case UO_Deref:
173
+ assert (Entry.Indirections > 0 );
174
+ Stack.emplace_back (Op, Entry.Indirections - 1 );
175
+ continue ;
176
+ default :
177
+ // Bail out on unary operators that we cannot analyze.
178
+ return false ;
179
+ }
180
+ }
181
+
182
+ // Assume any other expression can modify the underlying variable.
183
+ return false ;
184
+ }
185
+ }
186
+
187
+ // No parent can modify the variable.
188
+ return true ;
189
+ }
190
+
37
191
} // namespace
38
192
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
193
SmallPtrSet<const DeclRefExpr *, 16 >
42
194
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);
195
+ ASTContext &Context, int Indirections) {
196
+ auto Matches = match (findAll (declRefExpr (to (varDecl (equalsNode (&VarDecl))),
197
+ doesNotMutateObject (Indirections))
198
+ .bind (" declRef" )),
199
+ Stmt, Context);
60
200
SmallPtrSet<const DeclRefExpr *, 16 > DeclRefs;
61
201
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);
202
+
88
203
return DeclRefs;
89
204
}
90
205
91
206
bool isOnlyUsedAsConst (const VarDecl &Var, const Stmt &Stmt,
92
- ASTContext &Context) {
207
+ ASTContext &Context, int Indirections ) {
93
208
// Collect all DeclRefExprs to the loop variable and all CallExprs and
94
209
// CXXConstructExprs where the loop variable is used as argument to a const
95
210
// reference parameter.
96
211
// If the difference is empty it is safe for the loop variable to be a const
97
212
// reference.
98
213
auto AllDeclRefs = allDeclRefExprs (Var, Stmt, Context);
99
- auto ConstReferenceDeclRefs = constReferenceDeclRefExprs (Var, Stmt, Context);
214
+ auto ConstReferenceDeclRefs =
215
+ constReferenceDeclRefExprs (Var, Stmt, Context, Indirections);
100
216
return isSetDifferenceEmpty (AllDeclRefs, ConstReferenceDeclRefs);
101
217
}
102
218
0 commit comments