clang-tools 22.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} // namespace
37
39 ClangTidyContext *Context)
40 : ClangTidyCheck(Name, Context),
41 AnalyzePointers(Options.get("AnalyzePointers", true)),
42 AnalyzeReferences(Options.get("AnalyzeReferences", true)),
43 AnalyzeValues(Options.get("AnalyzeValues", true)),
44
45 WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),
46 WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
47
48 TransformPointersAsPointers(
49 Options.get("TransformPointersAsPointers", true)),
50 TransformPointersAsValues(
51 Options.get("TransformPointersAsValues", false)),
52 TransformReferences(Options.get("TransformReferences", true)),
53 TransformValues(Options.get("TransformValues", true)),
54
55 AllowedTypes(
56 utils::options::parseStringList(Options.get("AllowedTypes", ""))) {
57 if (AnalyzeValues == false && AnalyzeReferences == false &&
58 AnalyzePointers == false)
59 this->configurationDiag(
60 "The check 'misc-const-correctness' will not "
61 "perform any analysis because 'AnalyzeValues', "
62 "'AnalyzeReferences' and 'AnalyzePointers' are false.");
63}
64
66 Options.store(Opts, "AnalyzePointers", AnalyzePointers);
67 Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
68 Options.store(Opts, "AnalyzeValues", AnalyzeValues);
69
70 Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);
71 Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
72
73 Options.store(Opts, "TransformPointersAsPointers",
74 TransformPointersAsPointers);
75 Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
76 Options.store(Opts, "TransformReferences", TransformReferences);
77 Options.store(Opts, "TransformValues", TransformValues);
78
79 Options.store(Opts, "AllowedTypes",
81}
82
83void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
84 const auto ConstType =
85 hasType(qualType(isConstQualified(),
86 // pointee check will check the constness of pointer
87 unless(pointerType())));
88
89 const auto ConstReference = hasType(references(isConstQualified()));
90 const auto RValueReference = hasType(
91 referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
92
93 const auto TemplateType = anyOf(
94 hasType(hasCanonicalType(templateTypeParmType())),
95 hasType(substTemplateTypeParmType()), hasType(isDependentType()),
96 // References to template types, their substitutions or typedefs to
97 // template types need to be considered as well.
98 hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))),
99 hasType(referenceType(pointee(substTemplateTypeParmType()))));
100
101 auto AllowedTypeDecl = namedDecl(anyOf(
102 matchers::matchesAnyListedRegexName(AllowedTypes), usingShadowDecl()));
103
104 const auto AllowedType = hasType(qualType(
105 anyOf(hasDeclaration(AllowedTypeDecl), references(AllowedTypeDecl),
106 pointerType(pointee(hasDeclaration(AllowedTypeDecl))))));
107
108 const auto AutoTemplateType = varDecl(
109 anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
110 hasType(pointerType(pointee(autoType())))));
111
112 const auto FunctionPointerRef =
113 hasType(hasCanonicalType(referenceType(pointee(functionType()))));
114
115 // Match local variables which could be 'const' if not modified later.
116 // Example: `int i = 10` would match `int i`.
117 const auto LocalValDecl = varDecl(
118 isLocal(), hasInitializer(anything()),
119 unless(anyOf(ConstType, ConstReference, TemplateType,
120 hasInitializer(isInstantiationDependent()), AutoTemplateType,
121 RValueReference, FunctionPointerRef,
122 hasType(cxxRecordDecl(isLambda())), isImplicit(),
123 AllowedType)));
124
125 // Match the function scope for which the analysis of all local variables
126 // shall be run.
127 const auto FunctionScope =
128 functionDecl(
129 hasBody(stmt(forEachDescendant(
130 declStmt(containsAnyDeclaration(
131 LocalValDecl.bind("local-value")),
132 unless(has(decompositionDecl())))
133 .bind("decl-stmt")))
134 .bind("scope")))
135 .bind("function-decl");
136
137 Finder->addMatcher(FunctionScope, this);
138}
139
140namespace {
141
142/// Classify for a variable in what the Const-Check is interested.
143enum class VariableCategory { Value, Reference, Pointer };
144
145} // namespace
146
147void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
148 const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
149 const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
150 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
151 const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
152 // It can not be guaranteed that the variable is declared isolated,
153 // therefore a transformation might effect the other variables as well and
154 // be incorrect.
155 const bool CanBeFixIt = VarDeclStmt != nullptr && VarDeclStmt->isSingleDecl();
156
157 /// If the variable was declared in a template it might be analyzed multiple
158 /// times. Only one of those instantiations shall emit a warning. NOTE: This
159 /// shall only deduplicate warnings for variables that are not instantiation
160 /// dependent. Variables like 'int x = 42;' in a template that can become
161 /// const emit multiple warnings otherwise.
162 bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
163 if (IsNormalVariableInTemplate &&
164 TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
165 return;
166
167 VariableCategory VC = VariableCategory::Value;
168 const QualType VT = Variable->getType();
169 if (VT->isReferenceType()) {
170 VC = VariableCategory::Reference;
171 } else if (VT->isPointerType()) {
172 VC = VariableCategory::Pointer;
173 } else if (const auto *ArrayT = dyn_cast<ArrayType>(VT)) {
174 if (ArrayT->getElementType()->isPointerType())
175 VC = VariableCategory::Pointer;
176 }
177
178 auto CheckValue = [&]() {
179 // The scope is only registered if the analysis shall be run.
180 registerScope(LocalScope, Result.Context);
181
182 // Offload const-analysis to utility function.
183 if (ScopesCache[LocalScope]->isMutated(Variable))
184 return;
185
186 auto Diag = diag(Variable->getBeginLoc(),
187 "variable %0 of type %1 can be declared 'const'")
188 << Variable << VT;
189 if (IsNormalVariableInTemplate)
190 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
191 if (!CanBeFixIt)
192 return;
193 using namespace utils::fixit;
194 if (VC == VariableCategory::Value && TransformValues) {
195 Diag << addQualifierToVarDecl(*Variable, *Result.Context,
196 Qualifiers::Const, QualifierTarget::Value,
197 QualifierPolicy::Right);
198 // FIXME: Add '{}' for default initialization if no user-defined default
199 // constructor exists and there is no initializer.
200 return;
201 }
202
203 if (VC == VariableCategory::Reference && TransformReferences) {
204 Diag << addQualifierToVarDecl(*Variable, *Result.Context,
205 Qualifiers::Const, QualifierTarget::Value,
206 QualifierPolicy::Right);
207 return;
208 }
209
210 if (VC == VariableCategory::Pointer && TransformPointersAsValues) {
211 Diag << addQualifierToVarDecl(*Variable, *Result.Context,
212 Qualifiers::Const, QualifierTarget::Value,
213 QualifierPolicy::Right);
214 return;
215 }
216 };
217
218 auto CheckPointee = [&]() {
219 assert(VC == VariableCategory::Pointer);
220 registerScope(LocalScope, Result.Context);
221 if (ScopesCache[LocalScope]->isPointeeMutated(Variable))
222 return;
223 auto Diag =
224 diag(Variable->getBeginLoc(),
225 "pointee of variable %0 of type %1 can be declared 'const'")
226 << Variable << VT;
227 if (IsNormalVariableInTemplate)
228 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
229 if (!CanBeFixIt)
230 return;
231 using namespace utils::fixit;
232 if (TransformPointersAsPointers) {
233 Diag << addQualifierToVarDecl(*Variable, *Result.Context,
234 Qualifiers::Const, QualifierTarget::Pointee,
235 QualifierPolicy::Right);
236 }
237 };
238
239 // Each variable can only be in one category: Value, Pointer, Reference.
240 // Analysis can be controlled for every category.
241 if (VC == VariableCategory::Value && AnalyzeValues) {
242 CheckValue();
243 return;
244 }
245 if (VC == VariableCategory::Reference && AnalyzeReferences) {
246 if (VT->getPointeeType()->isPointerType() && !WarnPointersAsValues)
247 return;
248 CheckValue();
249 return;
250 }
251 if (VC == VariableCategory::Pointer && AnalyzePointers) {
252 if (WarnPointersAsValues && !VT.isConstQualified())
253 CheckValue();
254 if (WarnPointersAsPointers) {
255 if (const auto *PT = dyn_cast<PointerType>(VT)) {
256 if (!PT->getPointeeType().isConstQualified() &&
257 !PT->getPointeeType()->isFunctionType())
258 CheckPointee();
259 }
260 if (const auto *AT = dyn_cast<ArrayType>(VT)) {
261 if (!AT->getElementType().isConstQualified()) {
262 assert(AT->getElementType()->isPointerType());
263 CheckPointee();
264 }
265 }
266 }
267 return;
268 }
269}
270
271void ConstCorrectnessCheck::registerScope(const Stmt *LocalScope,
272 ASTContext *Context) {
273 auto &Analyzer = ScopesCache[LocalScope];
274 if (!Analyzer)
275 Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
276}
277
278} // 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)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap