clang-tools 20.0.0git
ConstCorrectnessCheck.cpp
Go to the documentation of this file.
1//===--- ConstCorrectnessCheck.cpp - clang-tidy -----------------*- C++ -*-===//
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 "../utils/FixItHintUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::misc {
18
19namespace {
20// FIXME: This matcher exists in some other code-review as well.
21// It should probably move to ASTMatchers.
22AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); }
23AST_MATCHER_P(DeclStmt, containsAnyDeclaration,
24 ast_matchers::internal::Matcher<Decl>, InnerMatcher) {
25 return ast_matchers::internal::matchesFirstInPointerRange(
26 InnerMatcher, Node.decl_begin(), Node.decl_end(), Finder,
27 Builder) != Node.decl_end();
28}
29AST_MATCHER(ReferenceType, isSpelledAsLValue) {
30 return Node.isSpelledAsLValue();
31}
32AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
33} // namespace
34
36 ClangTidyContext *Context)
37 : ClangTidyCheck(Name, Context),
38 AnalyzeValues(Options.get("AnalyzeValues", true)),
39 AnalyzeReferences(Options.get("AnalyzeReferences", true)),
40 WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
41 TransformValues(Options.get("TransformValues", true)),
42 TransformReferences(Options.get("TransformReferences", true)),
43 TransformPointersAsValues(
44 Options.get("TransformPointersAsValues", false)) {
45 if (AnalyzeValues == false && AnalyzeReferences == false)
47 "The check 'misc-const-correctness' will not "
48 "perform any analysis because both 'AnalyzeValues' and "
49 "'AnalyzeReferences' are false.");
50}
51
53 Options.store(Opts, "AnalyzeValues", AnalyzeValues);
54 Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
55 Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
56
57 Options.store(Opts, "TransformValues", TransformValues);
58 Options.store(Opts, "TransformReferences", TransformReferences);
59 Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
60}
61
62void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
63 const auto ConstType = hasType(isConstQualified());
64 const auto ConstReference = hasType(references(isConstQualified()));
65 const auto RValueReference = hasType(
66 referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
67
68 const auto TemplateType = anyOf(
69 hasType(hasCanonicalType(templateTypeParmType())),
70 hasType(substTemplateTypeParmType()), hasType(isDependentType()),
71 // References to template types, their substitutions or typedefs to
72 // template types need to be considered as well.
73 hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))),
74 hasType(referenceType(pointee(substTemplateTypeParmType()))));
75
76 const auto AutoTemplateType = varDecl(
77 anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
78 hasType(pointerType(pointee(autoType())))));
79
80 const auto FunctionPointerRef =
81 hasType(hasCanonicalType(referenceType(pointee(functionType()))));
82
83 // Match local variables which could be 'const' if not modified later.
84 // Example: `int i = 10` would match `int i`.
85 const auto LocalValDecl = varDecl(
86 isLocal(), hasInitializer(anything()),
87 unless(anyOf(ConstType, ConstReference, TemplateType,
88 hasInitializer(isInstantiationDependent()), AutoTemplateType,
89 RValueReference, FunctionPointerRef,
90 hasType(cxxRecordDecl(isLambda())), isImplicit())));
91
92 // Match the function scope for which the analysis of all local variables
93 // shall be run.
94 const auto FunctionScope =
95 functionDecl(
96 hasBody(stmt(forEachDescendant(
97 declStmt(containsAnyDeclaration(
98 LocalValDecl.bind("local-value")),
99 unless(has(decompositionDecl())))
100 .bind("decl-stmt")))
101 .bind("scope")))
102 .bind("function-decl");
103
104 Finder->addMatcher(FunctionScope, this);
105}
106
107/// Classify for a variable in what the Const-Check is interested.
109
110void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
111 const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
112 const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
113 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
114
115 /// If the variable was declared in a template it might be analyzed multiple
116 /// times. Only one of those instantiations shall emit a warning. NOTE: This
117 /// shall only deduplicate warnings for variables that are not instantiation
118 /// dependent. Variables like 'int x = 42;' in a template that can become
119 /// const emit multiple warnings otherwise.
120 bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
121 if (IsNormalVariableInTemplate &&
122 TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
123 return;
124
126 if (Variable->getType()->isReferenceType())
128 if (Variable->getType()->isPointerType())
130 if (Variable->getType()->isArrayType()) {
131 if (const auto *ArrayT = dyn_cast<ArrayType>(Variable->getType())) {
132 if (ArrayT->getElementType()->isPointerType())
134 }
135 }
136
137 // Each variable can only be in one category: Value, Pointer, Reference.
138 // Analysis can be controlled for every category.
139 if (VC == VariableCategory::Reference && !AnalyzeReferences)
140 return;
141
142 if (VC == VariableCategory::Reference &&
143 Variable->getType()->getPointeeType()->isPointerType() &&
144 !WarnPointersAsValues)
145 return;
146
147 if (VC == VariableCategory::Pointer && !WarnPointersAsValues)
148 return;
149
150 if (VC == VariableCategory::Value && !AnalyzeValues)
151 return;
152
153 // The scope is only registered if the analysis shall be run.
154 registerScope(LocalScope, Result.Context);
155
156 // Offload const-analysis to utility function.
157 if (ScopesCache[LocalScope]->isMutated(Variable))
158 return;
159
160 auto Diag = diag(Variable->getBeginLoc(),
161 "variable %0 of type %1 can be declared 'const'")
162 << Variable << Variable->getType();
163 if (IsNormalVariableInTemplate)
164 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
165
166 const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
167
168 // It can not be guaranteed that the variable is declared isolated, therefore
169 // a transformation might effect the other variables as well and be incorrect.
170 if (VarDeclStmt == nullptr || !VarDeclStmt->isSingleDecl())
171 return;
172
173 using namespace utils::fixit;
174 if (VC == VariableCategory::Value && TransformValues) {
175 Diag << addQualifierToVarDecl(*Variable, *Result.Context,
176 DeclSpec::TQ_const, QualifierTarget::Value,
177 QualifierPolicy::Right);
178 // FIXME: Add '{}' for default initialization if no user-defined default
179 // constructor exists and there is no initializer.
180 return;
181 }
182
183 if (VC == VariableCategory::Reference && TransformReferences) {
184 Diag << addQualifierToVarDecl(*Variable, *Result.Context,
185 DeclSpec::TQ_const, QualifierTarget::Value,
186 QualifierPolicy::Right);
187 return;
188 }
189
190 if (VC == VariableCategory::Pointer) {
191 if (WarnPointersAsValues && TransformPointersAsValues) {
192 Diag << addQualifierToVarDecl(*Variable, *Result.Context,
193 DeclSpec::TQ_const, QualifierTarget::Value,
194 QualifierPolicy::Right);
195 }
196 return;
197 }
198}
199
200void ConstCorrectnessCheck::registerScope(const Stmt *LocalScope,
201 ASTContext *Context) {
202 auto &Analyzer = ScopesCache[LocalScope];
203 if (!Analyzer)
204 Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
205}
206
207} // namespace clang::tidy::misc
llvm::SmallString< 256U > Name
CodeCompletionBuilder Builder
NodeType Type
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
ConstCorrectnessCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
AST_MATCHER(Decl, declHasNoReturnAttr)
matches a Decl if it has a "no return" attribute of any kind
VariableCategory
Classify for a variable in what the Const-Check is interested.
llvm::StringMap< ClangTidyValue > OptionMap