1 //===--- NonConstParameterCheck.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 "NonConstParameterCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang {
16 namespace tidy {
17 namespace readability {
18 
registerMatchers(MatchFinder * Finder)19 void NonConstParameterCheck::registerMatchers(MatchFinder *Finder) {
20   // Add parameters to Parameters.
21   Finder->addMatcher(parmVarDecl().bind("Parm"), this);
22 
23   // C++ constructor.
24   Finder->addMatcher(cxxConstructorDecl().bind("Ctor"), this);
25 
26   // Track unused parameters, there is Wunused-parameter about unused
27   // parameters.
28   Finder->addMatcher(declRefExpr().bind("Ref"), this);
29 
30   // Analyse parameter usage in function.
31   Finder->addMatcher(stmt(anyOf(unaryOperator(hasAnyOperatorName("++", "--")),
32                                 binaryOperator(), callExpr(), returnStmt(),
33                                 cxxConstructExpr()))
34                          .bind("Mark"),
35                      this);
36   Finder->addMatcher(varDecl(hasInitializer(anything())).bind("Mark"), this);
37 }
38 
check(const MatchFinder::MatchResult & Result)39 void NonConstParameterCheck::check(const MatchFinder::MatchResult &Result) {
40   if (const auto *Parm = Result.Nodes.getNodeAs<ParmVarDecl>("Parm")) {
41     if (const DeclContext *D = Parm->getParentFunctionOrMethod()) {
42       if (const auto *M = dyn_cast<CXXMethodDecl>(D)) {
43         if (M->isVirtual() || M->size_overridden_methods() != 0)
44           return;
45       }
46     }
47     addParm(Parm);
48   } else if (const auto *Ctor =
49                  Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor")) {
50     for (const auto *Parm : Ctor->parameters())
51       addParm(Parm);
52     for (const auto *Init : Ctor->inits())
53       markCanNotBeConst(Init->getInit(), true);
54   } else if (const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("Ref")) {
55     setReferenced(Ref);
56   } else if (const auto *S = Result.Nodes.getNodeAs<Stmt>("Mark")) {
57     if (const auto *B = dyn_cast<BinaryOperator>(S)) {
58       if (B->isAssignmentOp())
59         markCanNotBeConst(B, false);
60     } else if (const auto *CE = dyn_cast<CallExpr>(S)) {
61       // Typically, if a parameter is const then it is fine to make the data
62       // const. But sometimes the data is written even though the parameter
63       // is const. Mark all data passed by address to the function.
64       for (const auto *Arg : CE->arguments()) {
65         markCanNotBeConst(Arg->IgnoreParenCasts(), true);
66       }
67 
68       // Data passed by nonconst reference should not be made const.
69       if (const FunctionDecl *FD = CE->getDirectCallee()) {
70         unsigned ArgNr = 0U;
71         for (const auto *Par : FD->parameters()) {
72           if (ArgNr >= CE->getNumArgs())
73             break;
74           const Expr *Arg = CE->getArg(ArgNr++);
75           // Is this a non constant reference parameter?
76           const Type *ParType = Par->getType().getTypePtr();
77           if (!ParType->isReferenceType() || Par->getType().isConstQualified())
78             continue;
79           markCanNotBeConst(Arg->IgnoreParenCasts(), false);
80         }
81       }
82     } else if (const auto *CE = dyn_cast<CXXConstructExpr>(S)) {
83       for (const auto *Arg : CE->arguments()) {
84         markCanNotBeConst(Arg->IgnoreParenCasts(), true);
85       }
86       // Data passed by nonconst reference should not be made const.
87       unsigned ArgNr = 0U;
88       if (const auto *CD = CE->getConstructor()) {
89         for (const auto *Par : CD->parameters()) {
90           if (ArgNr >= CE->getNumArgs())
91             break;
92           const Expr *Arg = CE->getArg(ArgNr++);
93           // Is this a non constant reference parameter?
94           const Type *ParType = Par->getType().getTypePtr();
95           if (!ParType->isReferenceType() || Par->getType().isConstQualified())
96             continue;
97           markCanNotBeConst(Arg->IgnoreParenCasts(), false);
98         }
99       }
100     } else if (const auto *R = dyn_cast<ReturnStmt>(S)) {
101       markCanNotBeConst(R->getRetValue(), true);
102     } else if (const auto *U = dyn_cast<UnaryOperator>(S)) {
103       markCanNotBeConst(U, true);
104     }
105   } else if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("Mark")) {
106     const QualType T = VD->getType();
107     if ((T->isPointerType() && !T->getPointeeType().isConstQualified()) ||
108         T->isArrayType())
109       markCanNotBeConst(VD->getInit(), true);
110     else if (T->isLValueReferenceType() &&
111              !T->getPointeeType().isConstQualified())
112       markCanNotBeConst(VD->getInit(), false);
113   }
114 }
115 
addParm(const ParmVarDecl * Parm)116 void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) {
117   // Only add nonconst integer/float pointer parameters.
118   const QualType T = Parm->getType();
119   if (!T->isPointerType() || T->getPointeeType().isConstQualified() ||
120       !(T->getPointeeType()->isIntegerType() ||
121         T->getPointeeType()->isFloatingType()))
122     return;
123 
124   if (Parameters.find(Parm) != Parameters.end())
125     return;
126 
127   ParmInfo PI;
128   PI.IsReferenced = false;
129   PI.CanBeConst = true;
130   Parameters[Parm] = PI;
131 }
132 
setReferenced(const DeclRefExpr * Ref)133 void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) {
134   auto It = Parameters.find(dyn_cast<ParmVarDecl>(Ref->getDecl()));
135   if (It != Parameters.end())
136     It->second.IsReferenced = true;
137 }
138 
onEndOfTranslationUnit()139 void NonConstParameterCheck::onEndOfTranslationUnit() {
140   diagnoseNonConstParameters();
141 }
142 
diagnoseNonConstParameters()143 void NonConstParameterCheck::diagnoseNonConstParameters() {
144   for (const auto &It : Parameters) {
145     const ParmVarDecl *Par = It.first;
146     const ParmInfo &ParamInfo = It.second;
147 
148     // Unused parameter => there are other warnings about this.
149     if (!ParamInfo.IsReferenced)
150       continue;
151 
152     // Parameter can't be const.
153     if (!ParamInfo.CanBeConst)
154       continue;
155 
156     SmallVector<FixItHint, 8> Fixes;
157     auto *Function =
158         dyn_cast_or_null<const FunctionDecl>(Par->getParentFunctionOrMethod());
159     if (!Function)
160       continue;
161     unsigned Index = Par->getFunctionScopeIndex();
162     for (FunctionDecl *FnDecl : Function->redecls())
163       Fixes.push_back(FixItHint::CreateInsertion(
164           FnDecl->getParamDecl(Index)->getBeginLoc(), "const "));
165 
166     diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const")
167         << Par->getName() << Fixes;
168   }
169 }
170 
markCanNotBeConst(const Expr * E,bool CanNotBeConst)171 void NonConstParameterCheck::markCanNotBeConst(const Expr *E,
172                                                bool CanNotBeConst) {
173   if (!E)
174     return;
175 
176   if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) {
177     // If expression is const then ignore usage.
178     const QualType T = Cast->getType();
179     if (T->isPointerType() && T->getPointeeType().isConstQualified())
180       return;
181   }
182 
183   E = E->IgnoreParenCasts();
184 
185   if (const auto *B = dyn_cast<BinaryOperator>(E)) {
186     if (B->isAdditiveOp()) {
187       // p + 2
188       markCanNotBeConst(B->getLHS(), CanNotBeConst);
189       markCanNotBeConst(B->getRHS(), CanNotBeConst);
190     } else if (B->isAssignmentOp()) {
191       markCanNotBeConst(B->getLHS(), false);
192 
193       // If LHS is not const then RHS can't be const.
194       const QualType T = B->getLHS()->getType();
195       if (T->isPointerType() && !T->getPointeeType().isConstQualified())
196         markCanNotBeConst(B->getRHS(), true);
197     }
198   } else if (const auto *C = dyn_cast<ConditionalOperator>(E)) {
199     markCanNotBeConst(C->getTrueExpr(), CanNotBeConst);
200     markCanNotBeConst(C->getFalseExpr(), CanNotBeConst);
201   } else if (const auto *U = dyn_cast<UnaryOperator>(E)) {
202     if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec ||
203         U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) {
204       if (const auto *SubU =
205               dyn_cast<UnaryOperator>(U->getSubExpr()->IgnoreParenCasts()))
206         markCanNotBeConst(SubU->getSubExpr(), true);
207       markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
208     } else if (U->getOpcode() == UO_Deref) {
209       if (!CanNotBeConst)
210         markCanNotBeConst(U->getSubExpr(), true);
211     } else {
212       markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
213     }
214   } else if (const auto *A = dyn_cast<ArraySubscriptExpr>(E)) {
215     markCanNotBeConst(A->getBase(), true);
216   } else if (const auto *CLE = dyn_cast<CompoundLiteralExpr>(E)) {
217     markCanNotBeConst(CLE->getInitializer(), true);
218   } else if (const auto *Constr = dyn_cast<CXXConstructExpr>(E)) {
219     for (const auto *Arg : Constr->arguments()) {
220       if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(Arg))
221         markCanNotBeConst(cast<Expr>(M->getSubExpr()), CanNotBeConst);
222     }
223   } else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
224     for (unsigned I = 0U; I < ILE->getNumInits(); ++I)
225       markCanNotBeConst(ILE->getInit(I), true);
226   } else if (CanNotBeConst) {
227     // Referencing parameter.
228     if (const auto *D = dyn_cast<DeclRefExpr>(E)) {
229       auto It = Parameters.find(dyn_cast<ParmVarDecl>(D->getDecl()));
230       if (It != Parameters.end())
231         It->second.CanBeConst = false;
232     }
233   }
234 }
235 
236 } // namespace readability
237 } // namespace tidy
238 } // namespace clang
239