clang-tools 23.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 if (M->isVirtual() || M->size_overridden_methods() != 0)
80 return;
81 }
82 }
83 addParm(Parm);
84 } else if (const auto *Ctor =
85 Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor")) {
86 for (const auto *Parm : Ctor->parameters())
87 addParm(Parm);
88 for (const auto *Init : Ctor->inits())
89 markCanNotBeConst(Init->getInit(), true);
90 } else if (const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("Ref")) {
91 setReferenced(Ref);
92 } else if (const auto *S = Result.Nodes.getNodeAs<Stmt>("Mark")) {
93 if (const auto *B = dyn_cast<BinaryOperator>(S)) {
94 if (B->isAssignmentOp())
95 markCanNotBeConst(B, false);
96 } else if (const auto *CE = dyn_cast<CallExpr>(S)) {
97 // Typically, if a parameter is const then it is fine to make the data
98 // const. But sometimes the data is written even though the parameter
99 // is const. Mark all data passed by address to the function.
100 for (const auto *Arg : CE->arguments())
101 markCanNotBeConst(Arg->IgnoreParenCasts(), true);
102
103 // Data passed by nonconst reference should not be made const.
104 if (const FunctionDecl *FD = CE->getDirectCallee()) {
105 unsigned ArgNr = 0U;
106 for (const auto *Par : FD->parameters()) {
107 if (ArgNr >= CE->getNumArgs())
108 break;
109 const Expr *Arg = CE->getArg(ArgNr++);
110 // Is this a non constant reference parameter?
111 const Type *ParType = Par->getType().getTypePtr();
112 if (!ParType->isReferenceType() || Par->getType().isConstQualified())
113 continue;
114 markCanNotBeConst(Arg->IgnoreParenCasts(), false);
115 }
116 }
117 } else if (const auto *CE = dyn_cast<CXXConstructExpr>(S)) {
118 for (const auto *Arg : CE->arguments())
119 markCanNotBeConst(Arg->IgnoreParenCasts(), true);
120 // Data passed by nonconst reference should not be made const.
121 unsigned ArgNr = 0U;
122 if (const auto *CD = CE->getConstructor()) {
123 for (const auto *Par : CD->parameters()) {
124 if (ArgNr >= CE->getNumArgs())
125 break;
126 const Expr *Arg = CE->getArg(ArgNr++);
127 // Is this a non constant reference parameter?
128 const Type *ParType = Par->getType().getTypePtr();
129 if (!ParType->isReferenceType() || Par->getType().isConstQualified())
130 continue;
131 markCanNotBeConst(Arg->IgnoreParenCasts(), false);
132 }
133 }
134 } else if (const auto *CE = dyn_cast<CXXUnresolvedConstructExpr>(S)) {
135 markCanNotBeConst(CE, true);
136 } else if (const auto *R = dyn_cast<ReturnStmt>(S)) {
137 markCanNotBeConst(R->getRetValue(), true);
138 } else if (const auto *U = dyn_cast<UnaryOperator>(S)) {
139 markCanNotBeConst(U, true);
140 }
141 } else if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("Mark")) {
142 const QualType T = VD->getType();
143 if (T->isDependentType()) {
144 const Expr *Init = VD->getInit()->IgnoreParenCasts();
145 if (const auto *U = dyn_cast<UnaryOperator>(Init);
146 U && U->getOpcode() == UO_Deref) {
147 markCanNotBeConst(U->getSubExpr(), true);
148 } else if (const auto *PLE = dyn_cast<ParenListExpr>(Init)) {
149 for (const Expr *E : PLE->exprs()) {
150 E = E->IgnoreParenCasts();
151 if (const auto *U = dyn_cast<UnaryOperator>(E);
152 U && U->getOpcode() == UO_Deref)
153 markCanNotBeConst(U->getSubExpr(), true);
154 else
155 markCanNotBeConst(E, true);
156 }
157 } else {
158 markCanNotBeConst(Init, true);
159 }
160 } else if ((T->isPointerType() &&
161 !T->getPointeeType().isConstQualified()) ||
162 T->isArrayType() || T->isRecordType()) {
163 markCanNotBeConst(VD->getInit(), true);
164 } else if (T->isLValueReferenceType() &&
165 !T->getPointeeType().isConstQualified()) {
166 markCanNotBeConst(VD->getInit(), false);
167 }
168 }
169}
170
171void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) {
172 // Only add nonconst integer/float pointer parameters.
173 const QualType T = Parm->getType();
174 if (!T->isPointerType() || T->getPointeeType().isConstQualified() ||
175 !(T->getPointeeType()->isIntegerType() ||
176 T->getPointeeType()->isFloatingType()))
177 return;
178
179 auto [It, Inserted] = Parameters.try_emplace(Parm);
180 if (!Inserted)
181 return;
182
183 It->second.IsReferenced = false;
184 It->second.CanBeConst = true;
185}
186
187void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) {
188 auto It = Parameters.find(dyn_cast<ParmVarDecl>(Ref->getDecl()));
189 if (It != Parameters.end())
190 It->second.IsReferenced = true;
191}
192
194 diagnoseNonConstParameters();
195}
196
197void NonConstParameterCheck::diagnoseNonConstParameters() {
198 for (const auto &It : Parameters) {
199 const ParmVarDecl *Par = It.first;
200 const ParmInfo &ParamInfo = It.second;
201
202 // Unused parameter => there are other warnings about this.
203 if (!ParamInfo.IsReferenced)
204 continue;
205
206 // Parameter can't be const.
207 if (!ParamInfo.CanBeConst)
208 continue;
209
211 auto *Function =
212 dyn_cast_or_null<const FunctionDecl>(Par->getParentFunctionOrMethod());
213 if (!Function)
214 continue;
215 const unsigned Index = Par->getFunctionScopeIndex();
216 if (wouldConflictWithExistingDecl(*Function, Index))
217 continue;
218
219 for (FunctionDecl *FnDecl : Function->redecls()) {
220 if (FnDecl->getNumParams() <= Index)
221 continue;
222 Fixes.push_back(FixItHint::CreateInsertion(
223 FnDecl->getParamDecl(Index)->getBeginLoc(), "const "));
224 }
225
226 diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const")
227 << Par->getName() << Fixes;
228 }
229}
230
231void NonConstParameterCheck::markCanNotBeConst(const Expr *E,
232 bool CanNotBeConst) {
233 if (!E)
234 return;
235
236 if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) {
237 // If expression is const then ignore usage.
238 const QualType T = Cast->getType();
239 if (T->isPointerType() && T->getPointeeType().isConstQualified())
240 return;
241 }
242
243 E = E->IgnoreParenCasts();
244
245 if (const auto *B = dyn_cast<BinaryOperator>(E)) {
246 if (B->isAdditiveOp()) {
247 // p + 2
248 markCanNotBeConst(B->getLHS(), CanNotBeConst);
249 markCanNotBeConst(B->getRHS(), CanNotBeConst);
250 } else if (B->isAssignmentOp()) {
251 markCanNotBeConst(B->getLHS(), false);
252
253 // If LHS is not const then RHS can't be const.
254 const QualType T = B->getLHS()->getType();
255 if (T->isPointerType() && !T->getPointeeType().isConstQualified())
256 markCanNotBeConst(B->getRHS(), true);
257 }
258 } else if (const auto *C = dyn_cast<ConditionalOperator>(E)) {
259 markCanNotBeConst(C->getTrueExpr(), CanNotBeConst);
260 markCanNotBeConst(C->getFalseExpr(), CanNotBeConst);
261 } else if (const auto *U = dyn_cast<UnaryOperator>(E)) {
262 if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec ||
263 U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) {
264 if (const auto *SubU =
265 dyn_cast<UnaryOperator>(U->getSubExpr()->IgnoreParenCasts()))
266 markCanNotBeConst(SubU->getSubExpr(), true);
267 markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
268 } else if (U->getOpcode() == UO_Deref) {
269 if (!CanNotBeConst)
270 markCanNotBeConst(U->getSubExpr(), true);
271 } else {
272 markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
273 }
274 } else if (const auto *A = dyn_cast<ArraySubscriptExpr>(E)) {
275 if (A->isInstantiationDependent()) {
276 markCanNotBeConst(A->getLHS(), true);
277 markCanNotBeConst(A->getRHS(), true);
278 } else {
279 markCanNotBeConst(A->getBase(), true);
280 }
281 } else if (const auto *CLE = dyn_cast<CompoundLiteralExpr>(E)) {
282 markCanNotBeConst(CLE->getInitializer(), true);
283 } else if (const auto *Constr = dyn_cast<CXXConstructExpr>(E)) {
284 for (const auto *Arg : Constr->arguments())
285 if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(Arg))
286 markCanNotBeConst(cast<Expr>(M->getSubExpr()), CanNotBeConst);
287 else
288 markCanNotBeConst(Arg, CanNotBeConst);
289 } else if (const auto *CE = dyn_cast<CXXUnresolvedConstructExpr>(E)) {
290 for (const auto *Arg : CE->arguments())
291 markCanNotBeConst(Arg, CanNotBeConst);
292 } else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
293 for (unsigned I = 0U; I < ILE->getNumInits(); ++I)
294 markCanNotBeConst(ILE->getInit(I), CanNotBeConst);
295 } else if (const auto *PLE = dyn_cast<ParenListExpr>(E)) {
296 for (unsigned I = 0U; I < PLE->getNumExprs(); ++I)
297 markCanNotBeConst(PLE->getExpr(I), CanNotBeConst);
298 } else if (CanNotBeConst) {
299 // Referencing parameter.
300 if (const auto *D = dyn_cast<DeclRefExpr>(E)) {
301 auto It = Parameters.find(dyn_cast<ParmVarDecl>(D->getDecl()));
302 if (It != Parameters.end())
303 It->second.CanBeConst = false;
304 }
305 }
306}
307
308} // 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)