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