1010#include " Matchers.h"
1111#include " clang/AST/ASTContext.h"
1212#include " clang/AST/DeclCXX.h"
13+ #include " clang/AST/ExprCXX.h"
1314#include " clang/ASTMatchers/ASTMatchFinder.h"
15+ #include < cassert>
1416
1517namespace clang ::tidy::utils::decl_ref_expr {
1618
@@ -34,69 +36,183 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
3436 Nodes.insert (Match.getNodeAs <Node>(ID));
3537}
3638
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+
37191} // namespace
38192
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.
41193SmallPtrSet<const DeclRefExpr *, 16 >
42194constReferenceDeclRefExprs (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);
60200 SmallPtrSet<const DeclRefExpr *, 16 > DeclRefs;
61201 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+
88203 return DeclRefs;
89204}
90205
91206bool isOnlyUsedAsConst (const VarDecl &Var, const Stmt &Stmt,
92- ASTContext &Context) {
207+ ASTContext &Context, int Indirections ) {
93208 // Collect all DeclRefExprs to the loop variable and all CallExprs and
94209 // CXXConstructExprs where the loop variable is used as argument to a const
95210 // reference parameter.
96211 // If the difference is empty it is safe for the loop variable to be a const
97212 // reference.
98213 auto AllDeclRefs = allDeclRefExprs (Var, Stmt, Context);
99- auto ConstReferenceDeclRefs = constReferenceDeclRefExprs (Var, Stmt, Context);
214+ auto ConstReferenceDeclRefs =
215+ constReferenceDeclRefExprs (Var, Stmt, Context, Indirections);
100216 return isSetDifferenceEmpty (AllDeclRefs, ConstReferenceDeclRefs);
101217}
102218
0 commit comments