1 | //===--- DeclRefExprUtils.cpp - clang-tidy---------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "DeclRefExprUtils.h" |
10 | #include "Matchers.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/AST/DeclCXX.h" |
13 | #include "clang/AST/ExprCXX.h" |
14 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
15 | #include <cassert> |
16 | |
17 | namespace clang::tidy::utils::decl_ref_expr { |
18 | |
19 | using namespace ::clang::ast_matchers; |
20 | using llvm::SmallPtrSet; |
21 | |
22 | namespace { |
23 | |
24 | template <typename S> bool isSetDifferenceEmpty(const S &S1, const S &S2) { |
25 | for (auto E : S1) |
26 | if (S2.count(E) == 0) |
27 | return false; |
28 | return true; |
29 | } |
30 | |
31 | // Extracts all Nodes keyed by ID from Matches and inserts them into Nodes. |
32 | template <typename Node> |
33 | void (ArrayRef<BoundNodes> Matches, StringRef ID, |
34 | SmallPtrSet<const Node *, 16> &Nodes) { |
35 | for (const auto &Match : Matches) |
36 | Nodes.insert(Match.getNodeAs<Node>(ID)); |
37 | } |
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(Args: &Node, Args: 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(Node: *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>(Val: P) || isa<MaterializeTemporaryExpr>(Val: P)) { |
122 | Stack.emplace_back(Args: P, Args: Entry.Indirections); |
123 | continue; |
124 | } |
125 | if (const auto *const Cast = dyn_cast<CastExpr>(Val: 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(Args: Cast, Args: 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(Args: Cast, Args: 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>(Val: P)) { |
156 | if (const auto *const Method = |
157 | dyn_cast<CXXMethodDecl>(Val: Member->getMemberDecl())) { |
158 | if (Method->isConst() || Method->isStatic()) { |
159 | // The method call cannot mutate our variable. |
160 | continue; |
161 | } |
162 | return false; |
163 | } |
164 | Stack.emplace_back(Args: Member, Args: 0); |
165 | continue; |
166 | } |
167 | |
168 | if (const auto *const Op = dyn_cast<UnaryOperator>(Val: P)) { |
169 | switch (Op->getOpcode()) { |
170 | case UO_AddrOf: |
171 | Stack.emplace_back(Args: Op, Args: Entry.Indirections + 1); |
172 | continue; |
173 | case UO_Deref: |
174 | assert(Entry.Indirections > 0); |
175 | Stack.emplace_back(Args: Op, Args: Entry.Indirections - 1); |
176 | continue; |
177 | default: |
178 | // Bail out on unary operators that we cannot analyze. |
179 | return false; |
180 | } |
181 | } |
182 | |
183 | // Assume any other expression can modify the underlying variable. |
184 | return false; |
185 | } |
186 | } |
187 | |
188 | // No parent can modify the variable. |
189 | return true; |
190 | } |
191 | |
192 | } // namespace |
193 | |
194 | SmallPtrSet<const DeclRefExpr *, 16> |
195 | constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, |
196 | ASTContext &Context, int Indirections) { |
197 | auto Matches = match(findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl))), |
198 | doesNotMutateObject(Indirections)) |
199 | .bind("declRef" )), |
200 | Stmt, Context); |
201 | SmallPtrSet<const DeclRefExpr *, 16> DeclRefs; |
202 | extractNodesByIdTo(Matches, "declRef" , DeclRefs); |
203 | |
204 | return DeclRefs; |
205 | } |
206 | |
207 | bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, |
208 | ASTContext &Context, int Indirections) { |
209 | // Collect all DeclRefExprs to the loop variable and all CallExprs and |
210 | // CXXConstructExprs where the loop variable is used as argument to a const |
211 | // reference parameter. |
212 | // If the difference is empty it is safe for the loop variable to be a const |
213 | // reference. |
214 | auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context); |
215 | auto ConstReferenceDeclRefs = |
216 | constReferenceDeclRefExprs(Var, Stmt, Context, Indirections); |
217 | return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs); |
218 | } |
219 | |
220 | SmallPtrSet<const DeclRefExpr *, 16> |
221 | allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) { |
222 | auto Matches = match( |
223 | findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef" )), |
224 | Stmt, Context); |
225 | SmallPtrSet<const DeclRefExpr *, 16> DeclRefs; |
226 | extractNodesByIdTo(Matches, "declRef" , DeclRefs); |
227 | return DeclRefs; |
228 | } |
229 | |
230 | SmallPtrSet<const DeclRefExpr *, 16> |
231 | allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context) { |
232 | auto Matches = match( |
233 | decl(forEachDescendant( |
234 | declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef" ))), |
235 | Decl, Context); |
236 | SmallPtrSet<const DeclRefExpr *, 16> DeclRefs; |
237 | extractNodesByIdTo(Matches, "declRef" , DeclRefs); |
238 | return DeclRefs; |
239 | } |
240 | |
241 | bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl, |
242 | ASTContext &Context) { |
243 | auto UsedAsConstRefArg = forEachArgumentWithParam( |
244 | declRefExpr(equalsNode(&DeclRef)), |
245 | parmVarDecl(hasType(InnerMatcher: matchers::isReferenceToConst()))); |
246 | auto Matches = match( |
247 | decl(hasDescendant( |
248 | cxxConstructExpr(UsedAsConstRefArg, hasDeclaration(InnerMatcher: cxxConstructorDecl( |
249 | isCopyConstructor()))) |
250 | .bind("constructExpr" ))), |
251 | Decl, Context); |
252 | return !Matches.empty(); |
253 | } |
254 | |
255 | bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl, |
256 | ASTContext &Context) { |
257 | auto UsedAsConstRefArg = forEachArgumentWithParam( |
258 | declRefExpr(equalsNode(&DeclRef)), |
259 | parmVarDecl(hasType(InnerMatcher: matchers::isReferenceToConst()))); |
260 | auto Matches = match( |
261 | decl(hasDescendant( |
262 | cxxOperatorCallExpr(UsedAsConstRefArg, hasOverloadedOperatorName(Name: "=" ), |
263 | callee(InnerMatcher: cxxMethodDecl(isCopyAssignmentOperator()))) |
264 | .bind("operatorCallExpr" ))), |
265 | Decl, Context); |
266 | return !Matches.empty(); |
267 | } |
268 | |
269 | } // namespace clang::tidy::utils::decl_ref_expr |
270 | |