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