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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp