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(), atomicExpr()))
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 *AE = dyn_cast<AtomicExpr>(S)) {
117 // Atomic builtins may write through their pointer operands, such as the
118 // 'expected' operand of a compare-exchange, which receives the old value
119 // when the exchange fails.
120 for (const Expr *SubExpr :
121 llvm::ArrayRef(AE->getSubExprs(), AE->getNumSubExprs()))
122 markCanNotBeConst(SubExpr->IgnoreParenCasts(), true);
123 } else if (const auto *CE = dyn_cast<CXXConstructExpr>(S)) {
124 for (const auto *Arg : CE->arguments())
125 markCanNotBeConst(Arg->IgnoreParenCasts(), true);
126 // Data passed by nonconst reference should not be made const.
127 unsigned ArgNr = 0U;
128 if (const auto *CD = CE->getConstructor()) {
129 for (const auto *Par : CD->parameters()) {
130 if (ArgNr >= CE->getNumArgs())
131 break;
132 const Expr *Arg = CE->getArg(ArgNr++);
133 // Is this a non constant reference parameter?
134 const Type *ParType = Par->getType().getTypePtr();
135 if (!ParType->isReferenceType() || Par->getType().isConstQualified())
136 continue;
137 markCanNotBeConst(Arg->IgnoreParenCasts(), false);
138 }
139 }
140 } else if (const auto *CE = dyn_cast<CXXUnresolvedConstructExpr>(S)) {
141 markCanNotBeConst(CE, true);
142 } else if (const auto *R = dyn_cast<ReturnStmt>(S)) {
143 markCanNotBeConst(R->getRetValue(), true);
144 } else if (const auto *U = dyn_cast<UnaryOperator>(S)) {
145 markCanNotBeConst(U, true);
146 }
147 } else if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("Mark")) {
148 const QualType T = VD->getType();
149 if (T->isDependentType()) {
150 const Expr *Init = VD->getInit()->IgnoreParenCasts();
151 if (const auto *U = dyn_cast<UnaryOperator>(Init);
152 U && U->getOpcode() == UO_Deref) {
153 markCanNotBeConst(U->getSubExpr(), true);
154 } else if (const auto *PLE = dyn_cast<ParenListExpr>(Init)) {
155 for (const Expr *E : PLE->exprs()) {
156 E = E->IgnoreParenCasts();
157 if (const auto *U = dyn_cast<UnaryOperator>(E);
158 U && U->getOpcode() == UO_Deref)
159 markCanNotBeConst(U->getSubExpr(), true);
160 else
161 markCanNotBeConst(E, true);
162 }
163 } else {
164 markCanNotBeConst(Init, true);
165 }
166 } else if ((T->isPointerType() &&
167 !T->getPointeeType().isConstQualified()) ||
168 T->isArrayType() || T->isRecordType()) {
169 markCanNotBeConst(VD->getInit(), true);
170 } else if (T->isLValueReferenceType() &&
171 !T->getPointeeType().isConstQualified()) {
172 markCanNotBeConst(VD->getInit(), false);
173 }
174 }
175}
176
177void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) {
178 // Only add nonconst integer/float pointer parameters.
179 const QualType T = Parm->getType();
180 if (!T->isPointerType() || T->getPointeeType().isConstQualified() ||
181 !(T->getPointeeType()->isIntegerType() ||
182 T->getPointeeType()->isFloatingType()))
183 return;
184
185 auto [It, Inserted] = Parameters.try_emplace(Parm);
186 if (!Inserted)
187 return;
188
189 It->second.IsReferenced = false;
190 It->second.CanBeConst = true;
191}
192
193void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) {
194 const auto It = Parameters.find(dyn_cast<ParmVarDecl>(Ref->getDecl()));
195 if (It != Parameters.end())
196 It->second.IsReferenced = true;
197}
198
200 diagnoseNonConstParameters();
201}
202
203void NonConstParameterCheck::diagnoseNonConstParameters() {
204 for (const auto &It : Parameters) {
205 const ParmVarDecl *Par = It.first;
206 const ParmInfo &ParamInfo = It.second;
207
208 // Unused parameter => there are other warnings about this.
209 if (!ParamInfo.IsReferenced)
210 continue;
211
212 // Parameter can't be const.
213 if (!ParamInfo.CanBeConst)
214 continue;
215
217 auto *Function =
218 dyn_cast_or_null<const FunctionDecl>(Par->getParentFunctionOrMethod());
219 if (!Function)
220 continue;
221 const unsigned Index = Par->getFunctionScopeIndex();
222 if (wouldConflictWithExistingDecl(*Function, Index))
223 continue;
224
225 for (FunctionDecl *FnDecl : Function->redecls()) {
226 if (FnDecl->getNumParams() <= Index)
227 continue;
228 Fixes.push_back(FixItHint::CreateInsertion(
229 FnDecl->getParamDecl(Index)->getBeginLoc(), "const "));
230 }
231
232 diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const")
233 << Par->getName() << Fixes;
234 }
235}
236
237void NonConstParameterCheck::markCanNotBeConst(const Expr *E,
238 bool CanNotBeConst) {
239 if (!E)
240 return;
241
242 if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) {
243 // If expression is const then ignore usage.
244 const QualType T = Cast->getType();
245 if (T->isPointerType() && T->getPointeeType().isConstQualified())
246 return;
247 }
248
249 E = E->IgnoreParenCasts();
250
251 if (const auto *B = dyn_cast<BinaryOperator>(E)) {
252 if (B->isAdditiveOp()) {
253 // p + 2
254 markCanNotBeConst(B->getLHS(), CanNotBeConst);
255 markCanNotBeConst(B->getRHS(), CanNotBeConst);
256 } else if (B->isAssignmentOp()) {
257 markCanNotBeConst(B->getLHS(), false);
258
259 // If LHS is not const then RHS can't be const.
260 const QualType T = B->getLHS()->getType();
261 if (T->isPointerType() && !T->getPointeeType().isConstQualified())
262 markCanNotBeConst(B->getRHS(), true);
263 }
264 } else if (const auto *C = dyn_cast<ConditionalOperator>(E)) {
265 markCanNotBeConst(C->getTrueExpr(), CanNotBeConst);
266 markCanNotBeConst(C->getFalseExpr(), CanNotBeConst);
267 } else if (const auto *U = dyn_cast<UnaryOperator>(E)) {
268 if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec ||
269 U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) {
270 if (const auto *SubU =
271 dyn_cast<UnaryOperator>(U->getSubExpr()->IgnoreParenCasts()))
272 markCanNotBeConst(SubU->getSubExpr(), true);
273 markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
274 } else if (U->getOpcode() == UO_Deref) {
275 if (!CanNotBeConst)
276 markCanNotBeConst(U->getSubExpr(), true);
277 } else {
278 markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
279 }
280 } else if (const auto *A = dyn_cast<ArraySubscriptExpr>(E)) {
281 if (A->isInstantiationDependent()) {
282 markCanNotBeConst(A->getLHS(), true);
283 markCanNotBeConst(A->getRHS(), true);
284 } else {
285 markCanNotBeConst(A->getBase(), true);
286 }
287 } else if (const auto *CLE = dyn_cast<CompoundLiteralExpr>(E)) {
288 markCanNotBeConst(CLE->getInitializer(), true);
289 } else if (const auto *Constr = dyn_cast<CXXConstructExpr>(E)) {
290 for (const auto *Arg : Constr->arguments())
291 if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(Arg))
292 markCanNotBeConst(M->getSubExpr(), CanNotBeConst);
293 else
294 markCanNotBeConst(Arg, CanNotBeConst);
295 } else if (const auto *CE = dyn_cast<CXXUnresolvedConstructExpr>(E)) {
296 for (const auto *Arg : CE->arguments())
297 markCanNotBeConst(Arg, CanNotBeConst);
298 } else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
299 for (unsigned I = 0U; I < ILE->getNumInits(); ++I)
300 markCanNotBeConst(ILE->getInit(I), CanNotBeConst);
301 } else if (const auto *PLE = dyn_cast<ParenListExpr>(E)) {
302 for (unsigned I = 0U; I < PLE->getNumExprs(); ++I)
303 markCanNotBeConst(PLE->getExpr(I), CanNotBeConst);
304 } else if (CanNotBeConst) {
305 // Referencing parameter.
306 if (const auto *D = dyn_cast<DeclRefExpr>(E)) {
307 const auto It = Parameters.find(dyn_cast<ParmVarDecl>(D->getDecl()));
308 if (It != Parameters.end())
309 It->second.CanBeConst = false;
310 }
311 }
312}
313
314} // 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)