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