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