clang-tools 23.0.0git
ConstCorrectnessCheck.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
11#include "../utils/Matchers.h"
13#include "clang/AST/ASTContext.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
16#include <cassert>
17
18using namespace clang::ast_matchers;
19
20namespace clang::tidy::misc {
21
22namespace {
23// FIXME: This matcher exists in some other code-review as well.
24// It should probably move to ASTMatchers.
25AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); }
26// FIXME: The matcher 'hasName(Name)' asserts that its argument 'Name' is
27// nonempty. Perhaps remove that assertion and replace 'isUnnamed()' with
28// 'hasName("")'.
29AST_MATCHER(VarDecl, isUnnamed) {
30 return Node.getDeclName().isIdentifier() && Node.getName().empty();
31}
32AST_MATCHER_P(DeclStmt, containsAnyDeclaration,
33 ast_matchers::internal::Matcher<Decl>, InnerMatcher) {
34 return ast_matchers::internal::matchesFirstInPointerRange(
35 InnerMatcher, Node.decl_begin(), Node.decl_end(), Finder,
36 Builder) != Node.decl_end();
37}
38AST_MATCHER(ReferenceType, isSpelledAsLValue) {
39 return Node.isSpelledAsLValue();
40}
41AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
42
43AST_MATCHER(FunctionDecl, isTemplate) {
44 return Node.getDescribedFunctionTemplate() != nullptr;
45}
46
47AST_MATCHER(FunctionDecl, isFunctionTemplateSpecialization) {
48 return Node.isFunctionTemplateSpecialization();
49}
50} // namespace
51
53 ClangTidyContext *Context)
54 : ClangTidyCheck(Name, Context),
55 AnalyzePointers(Options.get("AnalyzePointers", true)),
56 AnalyzeReferences(Options.get("AnalyzeReferences", true)),
57 AnalyzeValues(Options.get("AnalyzeValues", true)),
58 AnalyzeParameters(Options.get("AnalyzeParameters", true)),
59
60 WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),
61 WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
62
63 TransformPointersAsPointers(
64 Options.get("TransformPointersAsPointers", true)),
65 TransformPointersAsValues(
66 Options.get("TransformPointersAsValues", false)),
67 TransformReferences(Options.get("TransformReferences", true)),
68 TransformValues(Options.get("TransformValues", true)),
69
70 AllowedTypes(
71 utils::options::parseStringList(Options.get("AllowedTypes", ""))) {
72 if (AnalyzeValues == false && AnalyzeReferences == false &&
73 AnalyzePointers == false)
74 this->configurationDiag(
75 "The check 'misc-const-correctness' will not "
76 "perform any analysis because 'AnalyzeValues', "
77 "'AnalyzeReferences' and 'AnalyzePointers' are false.");
78}
79
81 Options.store(Opts, "AnalyzePointers", AnalyzePointers);
82 Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
83 Options.store(Opts, "AnalyzeValues", AnalyzeValues);
84 Options.store(Opts, "AnalyzeParameters", AnalyzeParameters);
85
86 Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);
87 Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
88
89 Options.store(Opts, "TransformPointersAsPointers",
90 TransformPointersAsPointers);
91 Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
92 Options.store(Opts, "TransformReferences", TransformReferences);
93 Options.store(Opts, "TransformValues", TransformValues);
94
95 Options.store(Opts, "AllowedTypes",
97}
98
99void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
100 const auto ConstType =
101 hasType(qualType(isConstQualified(),
102 // pointee check will check the constness of pointer
103 unless(pointerType())));
104
105 const auto ConstReference = hasType(references(isConstQualified()));
106 const auto RValueReference = hasType(
107 referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
108
109 const auto TemplateType = anyOf(
110 hasType(hasCanonicalType(templateTypeParmType())),
111 hasType(substTemplateTypeParmType()), hasType(isDependentType()),
112 // References to template types, their substitutions or typedefs to
113 // template types need to be considered as well.
114 hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))),
115 hasType(referenceType(pointee(substTemplateTypeParmType()))));
116
117 auto AllowedTypeDecl = namedDecl(anyOf(
118 matchers::matchesAnyListedRegexName(AllowedTypes), usingShadowDecl()));
119
120 const auto AllowedType = hasType(qualType(
121 anyOf(hasDeclaration(AllowedTypeDecl), references(AllowedTypeDecl),
122 pointerType(pointee(hasDeclaration(AllowedTypeDecl))))));
123
124 const auto AutoTemplateType = varDecl(
125 anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
126 hasType(pointerType(pointee(autoType())))));
127
128 const auto FunctionPointerRef =
129 hasType(hasCanonicalType(referenceType(pointee(functionType()))));
130
131 const auto CommonExcludeTypes =
132 anyOf(ConstType, ConstReference, RValueReference, TemplateType,
133 FunctionPointerRef, hasType(cxxRecordDecl(isLambda())),
134 AutoTemplateType, isImplicit(), AllowedType);
135
136 // Match local variables which could be 'const' if not modified later.
137 // Example: `int i = 10` would match `int i`.
138 const auto LocalValDecl =
139 varDecl(isLocal(), hasInitializer(unless(isInstantiationDependent())),
140 unless(CommonExcludeTypes));
141
142 // Match the function scope for which the analysis of all local variables
143 // shall be run.
144 const auto FunctionScope =
145 functionDecl(hasBody(stmt(forEachDescendant(
146 declStmt(containsAnyDeclaration(
147 LocalValDecl.bind("value")),
148 unless(has(decompositionDecl())))
149 .bind("decl-stmt")))
150 .bind("scope")))
151 .bind("function-decl");
152
153 Finder->addMatcher(FunctionScope, this);
154
155 if (AnalyzeParameters) {
156 const auto ParamMatcher =
157 parmVarDecl(unless(CommonExcludeTypes), unless(isUnnamed()),
158 anyOf(hasType(referenceType()), hasType(pointerType())))
159 .bind("value");
160
161 // Match function parameters which could be 'const' if not modified later.
162 // Example: `void foo(int* ptr)` would match `int* ptr`.
163 const auto FunctionWithParams =
164 functionDecl(
165 hasBody(stmt().bind("scope")), has(typeLoc(forEach(ParamMatcher))),
166 unless(cxxMethodDecl()), unless(isFunctionTemplateSpecialization()),
167 unless(isTemplate()))
168 .bind("function-decl");
169
170 Finder->addMatcher(FunctionWithParams, this);
171 }
172}
173
174static void addConstFixits(const DiagnosticBuilder &Diag,
175 const VarDecl *Variable,
176 const FunctionDecl *Function,
177 const ASTContext &Context, Qualifiers::TQ Qualifier,
180 // If this is a parameter, also add fixits for corresponding parameters in
181 // function declarations
182 if (const auto *ParamDecl = dyn_cast<ParmVarDecl>(Variable)) {
183 const unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
184 // Skip if all fix-its can not be applied properly due to 'using'/'typedef'
185 if (llvm::any_of(
186 Function->redecls(), [ParamIdx](const FunctionDecl *Redecl) {
187 const QualType Type = Redecl->getParamDecl(ParamIdx)->getType();
188 return Type->isTypedefNameType() || Type->getAs<UsingType>();
189 }))
190 return;
191
192 for (const FunctionDecl *Redecl : Function->redecls()) {
193 Diag << addQualifierToVarDecl(*Redecl->getParamDecl(ParamIdx), Context,
194 Qualifier, Target, Policy);
195 }
196 } else {
197 Diag << addQualifierToVarDecl(*Variable, Context, Qualifier, Target,
198 Policy);
199 }
200}
201
202namespace {
203
204/// Classify for a variable in what the Const-Check is interested.
205enum class VariableCategory { Value, Reference, Pointer };
206
207} // namespace
208
209void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
210 const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
211 const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("value");
212 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
213 const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
214
215 assert(Variable && LocalScope && Function);
216
217 // It can not be guaranteed that the variable is declared isolated,
218 // therefore a transformation might effect the other variables as well and
219 // be incorrect. Parameters don't need this check - they receive values from
220 // callers.
221 const bool CanBeFixIt = isa<ParmVarDecl>(Variable) ||
222 (VarDeclStmt && VarDeclStmt->isSingleDecl());
223
224 /// If the variable was declared in a template it might be analyzed multiple
225 /// times. Only one of those instantiations shall emit a warning. NOTE: This
226 /// shall only deduplicate warnings for variables that are not instantiation
227 /// dependent. Variables like 'int x = 42;' in a template that can become
228 /// const emit multiple warnings otherwise.
229 bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
230 if (IsNormalVariableInTemplate &&
231 TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
232 return;
233
234 VariableCategory VC = VariableCategory::Value;
235 const QualType VT = Variable->getType();
236 if (VT->isReferenceType()) {
237 VC = VariableCategory::Reference;
238 } else if (VT->isPointerType()) {
239 VC = VariableCategory::Pointer;
240 } else if (const auto *ArrayT = dyn_cast<ArrayType>(VT)) {
241 if (ArrayT->getElementType()->isPointerType())
242 VC = VariableCategory::Pointer;
243 }
244
245 auto CheckValue = [&]() {
246 // Offload const-analysis to utility function.
247 if (isMutated(Variable, LocalScope, Function, Result.Context))
248 return;
249
250 auto Diag = diag(Variable->getBeginLoc(),
251 "variable %0 of type %1 can be declared 'const'")
252 << Variable << VT;
253 if (IsNormalVariableInTemplate)
254 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
255 if (!CanBeFixIt)
256 return;
257 using namespace utils::fixit;
258
259 if (VC == VariableCategory::Value && TransformValues) {
260 addConstFixits(Diag, Variable, Function, *Result.Context,
261 Qualifiers::Const, QualifierTarget::Value,
262 QualifierPolicy::Right);
263 // FIXME: Add '{}' for default initialization if no user-defined default
264 // constructor exists and there is no initializer.
265 return;
266 }
267
268 if (VC == VariableCategory::Reference && TransformReferences) {
269 addConstFixits(Diag, Variable, Function, *Result.Context,
270 Qualifiers::Const, QualifierTarget::Value,
271 QualifierPolicy::Right);
272 return;
273 }
274
275 if (VC == VariableCategory::Pointer && TransformPointersAsValues) {
276 addConstFixits(Diag, Variable, Function, *Result.Context,
277 Qualifiers::Const, QualifierTarget::Value,
278 QualifierPolicy::Right);
279 return;
280 }
281 };
282
283 auto CheckPointee = [&]() {
284 assert(VC == VariableCategory::Pointer);
285 registerScope(LocalScope, Result.Context);
286 if (ScopesCache[LocalScope]->isPointeeMutated(Variable))
287 return;
288 auto Diag =
289 diag(Variable->getBeginLoc(),
290 "pointee of variable %0 of type %1 can be declared 'const'")
291 << Variable << VT;
292 if (IsNormalVariableInTemplate)
293 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
294 if (!CanBeFixIt)
295 return;
296 using namespace utils::fixit;
297 if (TransformPointersAsPointers) {
298 addConstFixits(Diag, Variable, Function, *Result.Context,
299 Qualifiers::Const, QualifierTarget::Pointee,
300 QualifierPolicy::Right);
301 }
302 };
303
304 // Each variable can only be in one category: Value, Pointer, Reference.
305 // Analysis can be controlled for every category.
306 if (VC == VariableCategory::Value && AnalyzeValues) {
307 CheckValue();
308 return;
309 }
310 if (VC == VariableCategory::Reference && AnalyzeReferences) {
311 if (VT->getPointeeType()->isPointerType() && !WarnPointersAsValues)
312 return;
313 CheckValue();
314 return;
315 }
316 if (VC == VariableCategory::Pointer && AnalyzePointers) {
317 if (WarnPointersAsValues && !VT.isConstQualified())
318 CheckValue();
319 if (WarnPointersAsPointers) {
320 if (const auto *PT = dyn_cast<PointerType>(VT)) {
321 if (!PT->getPointeeType().isConstQualified() &&
322 !PT->getPointeeType()->isFunctionType())
323 CheckPointee();
324 }
325 if (const auto *AT = dyn_cast<ArrayType>(VT)) {
326 assert(AT->getElementType()->isPointerType());
327 if (!AT->getElementType()->getPointeeType().isConstQualified())
328 CheckPointee();
329 }
330 }
331 return;
332 }
333}
334
335void ConstCorrectnessCheck::registerScope(const Stmt *LocalScope,
336 ASTContext *Context) {
337 auto &Analyzer = ScopesCache[LocalScope];
338 if (!Analyzer)
339 Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
340}
341
342bool ConstCorrectnessCheck::isMutated(const VarDecl *Variable,
343 const Stmt *Scope,
344 const FunctionDecl *Func,
345 ASTContext *Context) {
346 if (const auto *Param = dyn_cast<ParmVarDecl>(Variable)) {
347 return FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer(
348 *Func, *Context, ParamMutationAnalyzerMemoized)
349 ->isMutated(Param);
350 }
351
352 registerScope(Scope, Context);
353 return ScopesCache[Scope]->isMutated(Variable);
354}
355
356} // namespace clang::tidy::misc
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ConstCorrectnessCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedRegexName(llvm::ArrayRef< StringRef > NameList)
static void addConstFixits(const DiagnosticBuilder &Diag, const VarDecl *Variable, const FunctionDecl *Function, const ASTContext &Context, Qualifiers::TQ Qualifier, utils::fixit::QualifierTarget Target, utils::fixit::QualifierPolicy Policy)
QualifierTarget
This enum defines which entity is the target for adding the qualifier. This makes only a difference f...
QualifierPolicy
This enum defines where the qualifier shall be preferably added.
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap