clang-tools 24.0.0git
NonConstParameterCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12
13using namespace clang::ast_matchers;
14
16
17namespace {
18AST_MATCHER_P(VarDecl, hasOwnInitializer, ast_matchers::internal::Matcher<Expr>,
19 InnerMatcher) {
20 const Expr *Initializer = Node.getInit();
21 return Initializer != nullptr &&
22 InnerMatcher.matches(*Initializer, Finder, Builder);
23}
24} // namespace
25
26static bool wouldConflictWithExistingDecl(const FunctionDecl &Function,
27 unsigned ParamIndex) {
28 ASTContext &Context = Function.getASTContext();
29 const auto *Proto = Function.getType()->getAs<FunctionProtoType>();
30 if (!Proto)
31 return false;
32
33 // Simulate applying the fix-it to compare against existing overloads.
34 SmallVector<QualType> ParamTypes(Proto->getParamTypes());
35 ParamTypes[ParamIndex] = Context.getPointerType(
36 ParamTypes[ParamIndex]->getPointeeType().withConst());
37
38 return llvm::any_of(
39 Function.getParent()->lookup(Function.getDeclName()), [&](const Decl *D) {
40 if (const auto *Using = dyn_cast<UsingShadowDecl>(D))
41 D = Using->getTargetDecl();
42 const FunctionDecl *Overload = D->getAsFunction();
43 if (!Overload ||
44 Overload->getCanonicalDecl() == Function.getCanonicalDecl())
45 return false;
46
47 const QualType ConstParamFunctionType = Context.getFunctionType(
48 Overload->getReturnType(), ParamTypes, Proto->getExtProtoInfo());
49 return Context.hasSameFunctionTypeIgnoringExceptionSpec(
50 ConstParamFunctionType, Overload->getType());
51 });
52}
53
55 // Add parameters to Parameters.
56 Finder->addMatcher(parmVarDecl().bind("Parm"), this);
57
58 // C++ constructor.
59 Finder->addMatcher(cxxConstructorDecl().bind("Ctor"), this);
60
61 // Track unused parameters, there is Wunused-parameter about unused
62 // parameters.
63 Finder->addMatcher(declRefExpr().bind("Ref"), this);
64
65 // Analyse parameter usage in function.
66 Finder->addMatcher(
67 stmt(anyOf(unaryOperator(hasAnyOperatorName("++", "--")),
68 binaryOperator(), callExpr(), returnStmt(), cxxConstructExpr(),
69 cxxUnresolvedConstructExpr()))
70 .bind("Mark"),
71 this);
72 Finder->addMatcher(varDecl(hasOwnInitializer(anything())).bind("Mark"), this);
73}
74
75void NonConstParameterCheck::check(const MatchFinder::MatchResult &Result) {
76 if (const auto *Parm = Result.Nodes.getNodeAs<ParmVarDecl>("Parm")) {
77 if (const DeclContext *D = Parm->getParentFunctionOrMethod()) {
78 if (const auto *M = dyn_cast<CXXMethodDecl>(D);
79 M && (M->isVirtual() || M->size_overridden_methods() != 0))
80 return;
81 }
82 addParm(Parm);
83 } else if (const auto *Ctor =
84 Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor")) {
85 for (const auto *Parm : Ctor->parameters())
86 addParm(Parm);
87 for (const auto *Init : Ctor->inits())
88 markCanNotBeConst(Init->getInit(), true);
89 } else if (const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("Ref")) {
90 setReferenced(Ref);
91 } else if (const auto *S = Result.Nodes.getNodeAs<Stmt>("Mark")) {
92 if (const auto *B = dyn_cast<BinaryOperator>(S)) {
93 if (B->isAssignmentOp())
94 markCanNotBeConst(B, false);
95 } else if (const auto *CE = dyn_cast<CallExpr>(S)) {
96 // Typically, if a parameter is const then it is fine to make the data
97 // const. But sometimes the data is written even though the parameter
98 // is const. Mark all data passed by address to the function.
99 for (const auto *Arg : CE->arguments())
100 markCanNotBeConst(Arg->IgnoreParenCasts(), true);
101
102 // Data passed by nonconst reference should not be made const.
103 if (const FunctionDecl *FD = CE->getDirectCallee()) {
104 unsigned ArgNr = 0U;
105 for (const auto *Par : FD->parameters()) {
106 if (ArgNr >= CE->getNumArgs())
107 break;
108 const Expr *Arg = CE->getArg(ArgNr++);
109 // Is this a non constant reference parameter?
110 const Type *ParType = Par->getType().getTypePtr();
111 if (!ParType->isReferenceType() || Par->getType().isConstQualified())
112 continue;
113 markCanNotBeConst(Arg->IgnoreParenCasts(), false);
114 }
115 }
116 } else if (const auto *CE = dyn_cast<CXXConstructExpr>(S)) {
117 for (const auto *Arg : CE->arguments())
118 markCanNotBeConst(Arg->IgnoreParenCasts(), true);
119 // Data passed by nonconst reference should not be made const.
120 unsigned ArgNr = 0U;
121 if (const auto *CD = CE->getConstructor()) {
122 for (const auto *Par : CD->parameters()) {
123 if (ArgNr >= CE->getNumArgs())
124 break;
125 const Expr *Arg = CE->getArg(ArgNr++);
126 // Is this a non constant reference parameter?
127 const Type *ParType = Par->getType().getTypePtr();
128 if (!ParType->isReferenceType() || Par->getType().isConstQualified())
129 continue;
130 markCanNotBeConst(Arg->IgnoreParenCasts(), false);
131 }
132 }
133 } else if (const auto *CE = dyn_cast<CXXUnresolvedConstructExpr>(S)) {
134 markCanNotBeConst(CE, true);
135 } else if (const auto *R = dyn_cast<ReturnStmt>(S)) {
136 markCanNotBeConst(R->getRetValue(), true);
137 } else if (const auto *U = dyn_cast<UnaryOperator>(S)) {
138 markCanNotBeConst(U, true);
139 }
140 } else if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("Mark")) {
141 const QualType T = VD->getType();
142 if (T->isDependentType()) {
143 const Expr *Init = VD->getInit()->IgnoreParenCasts();
144 if (const auto *U = dyn_cast<UnaryOperator>(Init);
145 U && U->getOpcode() == UO_Deref) {
146 markCanNotBeConst(U->getSubExpr(), true);
147 } else if (const auto *PLE = dyn_cast<ParenListExpr>(Init)) {
148 for (const Expr *E : PLE->exprs()) {
149 E = E->IgnoreParenCasts();
150 if (const auto *U = dyn_cast<UnaryOperator>(E);
151 U && U->getOpcode() == UO_Deref)
152 markCanNotBeConst(U->getSubExpr(), true);
153 else
154 markCanNotBeConst(E, true);
155 }
156 } else {
157 markCanNotBeConst(Init, true);
158 }
159 } else if ((T->isPointerType() &&
160 !T->getPointeeType().isConstQualified()) ||
161 T->isArrayType() || T->isRecordType()) {
162 markCanNotBeConst(VD->getInit(), true);
163 } else if (T->isLValueReferenceType() &&
164 !T->getPointeeType().isConstQualified()) {
165 markCanNotBeConst(VD->getInit(), false);
166 }
167 }
168}
169
170void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) {
171 // Only add nonconst integer/float pointer parameters.
172 const QualType T = Parm->getType();
173 if (!T->isPointerType() || T->getPointeeType().isConstQualified() ||
174 !(T->getPointeeType()->isIntegerType() ||
175 T->getPointeeType()->isFloatingType()))
176 return;
177
178 auto [It, Inserted] = Parameters.try_emplace(Parm);
179 if (!Inserted)
180 return;
181
182 It->second.IsReferenced = false;
183 It->second.CanBeConst = true;
184}
185
186void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) {
187 const auto It = Parameters.find(dyn_cast<ParmVarDecl>(Ref->getDecl()));
188 if (It != Parameters.end())
189 It->second.IsReferenced = true;
190}
191
193 diagnoseNonConstParameters();
194}
195
196void NonConstParameterCheck::diagnoseNonConstParameters() {
197 for (const auto &It : Parameters) {
198 const ParmVarDecl *Par = It.first;
199 const ParmInfo &ParamInfo = It.second;
200
201 // Unused parameter => there are other warnings about this.
202 if (!ParamInfo.IsReferenced)
203 continue;
204
205 // Parameter can't be const.
206 if (!ParamInfo.CanBeConst)
207 continue;
208
210 auto *Function =
211 dyn_cast_or_null<const FunctionDecl>(Par->getParentFunctionOrMethod());
212 if (!Function)
213 continue;
214 const unsigned Index = Par->getFunctionScopeIndex();
215 if (wouldConflictWithExistingDecl(*Function, Index))
216 continue;
217
218 for (FunctionDecl *FnDecl : Function->redecls()) {
219 if (FnDecl->getNumParams() <= Index)
220 continue;
221 Fixes.push_back(FixItHint::CreateInsertion(
222 FnDecl->getParamDecl(Index)->getBeginLoc(), "const "));
223 }
224
225 diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const")
226 << Par->getName() << Fixes;
227 }
228}
229
230void NonConstParameterCheck::markCanNotBeConst(const Expr *E,
231 bool CanNotBeConst) {
232 if (!E)
233 return;
234
235 if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) {
236 // If expression is const then ignore usage.
237 const QualType T = Cast->getType();
238 if (T->isPointerType() && T->getPointeeType().isConstQualified())
239 return;
240 }
241
242 E = E->IgnoreParenCasts();
243
244 if (const auto *B = dyn_cast<BinaryOperator>(E)) {
245 if (B->isAdditiveOp()) {
246 // p + 2
247 markCanNotBeConst(B->getLHS(), CanNotBeConst);
248 markCanNotBeConst(B->getRHS(), CanNotBeConst);
249 } else if (B->isAssignmentOp()) {
250 markCanNotBeConst(B->getLHS(), false);
251
252 // If LHS is not const then RHS can't be const.
253 const QualType T = B->getLHS()->getType();
254 if (T->isPointerType() && !T->getPointeeType().isConstQualified())
255 markCanNotBeConst(B->getRHS(), true);
256 }
257 } else if (const auto *C = dyn_cast<ConditionalOperator>(E)) {
258 markCanNotBeConst(C->getTrueExpr(), CanNotBeConst);
259 markCanNotBeConst(C->getFalseExpr(), CanNotBeConst);
260 } else if (const auto *U = dyn_cast<UnaryOperator>(E)) {
261 if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec ||
262 U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) {
263 if (const auto *SubU =
264 dyn_cast<UnaryOperator>(U->getSubExpr()->IgnoreParenCasts()))
265 markCanNotBeConst(SubU->getSubExpr(), true);
266 markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
267 } else if (U->getOpcode() == UO_Deref) {
268 if (!CanNotBeConst)
269 markCanNotBeConst(U->getSubExpr(), true);
270 } else {
271 markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
272 }
273 } else if (const auto *A = dyn_cast<ArraySubscriptExpr>(E)) {
274 if (A->isInstantiationDependent()) {
275 markCanNotBeConst(A->getLHS(), true);
276 markCanNotBeConst(A->getRHS(), true);
277 } else {
278 markCanNotBeConst(A->getBase(), true);
279 }
280 } else if (const auto *CLE = dyn_cast<CompoundLiteralExpr>(E)) {
281 markCanNotBeConst(CLE->getInitializer(), true);
282 } else if (const auto *Constr = dyn_cast<CXXConstructExpr>(E)) {
283 for (const auto *Arg : Constr->arguments())
284 if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(Arg))
285 markCanNotBeConst(M->getSubExpr(), CanNotBeConst);
286 else
287 markCanNotBeConst(Arg, CanNotBeConst);
288 } else if (const auto *CE = dyn_cast<CXXUnresolvedConstructExpr>(E)) {
289 for (const auto *Arg : CE->arguments())
290 markCanNotBeConst(Arg, CanNotBeConst);
291 } else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
292 for (unsigned I = 0U; I < ILE->getNumInits(); ++I)
293 markCanNotBeConst(ILE->getInit(I), CanNotBeConst);
294 } else if (const auto *PLE = dyn_cast<ParenListExpr>(E)) {
295 for (unsigned I = 0U; I < PLE->getNumExprs(); ++I)
296 markCanNotBeConst(PLE->getExpr(I), CanNotBeConst);
297 } else if (CanNotBeConst) {
298 // Referencing parameter.
299 if (const auto *D = dyn_cast<DeclRefExpr>(E)) {
300 const auto It = Parameters.find(dyn_cast<ParmVarDecl>(D->getDecl()));
301 if (It != Parameters.end())
302 It->second.CanBeConst = false;
303 }
304 }
305}
306
307} // namespace clang::tidy::readability
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
static bool wouldConflictWithExistingDecl(const FunctionDecl &Function, unsigned ParamIndex)