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 "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 Matcher: InnerMatcher, Start: Node.decl_begin(), End: 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
35ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
36 ClangTidyContext *Context)
37 : ClangTidyCheck(Name, Context),
38 AnalyzeValues(Options.get(LocalName: "AnalyzeValues", Default: true)),
39 AnalyzeReferences(Options.get(LocalName: "AnalyzeReferences", Default: true)),
40 WarnPointersAsValues(Options.get(LocalName: "WarnPointersAsValues", Default: false)),
41 TransformValues(Options.get(LocalName: "TransformValues", Default: true)),
42 TransformReferences(Options.get(LocalName: "TransformReferences", Default: true)),
43 TransformPointersAsValues(
44 Options.get(LocalName: "TransformPointersAsValues", Default: false)) {
45 if (AnalyzeValues == false && AnalyzeReferences == false)
46 this->configurationDiag(
47 Description: "The check 'misc-const-correctness' will not "
48 "perform any analysis because both 'AnalyzeValues' and "
49 "'AnalyzeReferences' are false.");
50}
51
52void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
53 Options.store(Options&: Opts, LocalName: "AnalyzeValues", Value: AnalyzeValues);
54 Options.store(Options&: Opts, LocalName: "AnalyzeReferences", Value: AnalyzeReferences);
55 Options.store(Options&: Opts, LocalName: "WarnPointersAsValues", Value: WarnPointersAsValues);
56
57 Options.store(Options&: Opts, LocalName: "TransformValues", Value: TransformValues);
58 Options.store(Options&: Opts, LocalName: "TransformReferences", Value: TransformReferences);
59 Options.store(Options&: Opts, LocalName: "TransformPointersAsValues", Value: TransformPointersAsValues);
60}
61
62void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
63 const auto ConstType = hasType(InnerMatcher: isConstQualified());
64 const auto ConstReference = hasType(InnerMatcher: references(InnerMatcher: isConstQualified()));
65 const auto RValueReference = hasType(
66 InnerMatcher: referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
67
68 const auto TemplateType = anyOf(
69 hasType(InnerMatcher: hasCanonicalType(InnerMatcher: templateTypeParmType())),
70 hasType(InnerMatcher: substTemplateTypeParmType()), hasType(InnerMatcher: isDependentType()),
71 // References to template types, their substitutions or typedefs to
72 // template types need to be considered as well.
73 hasType(InnerMatcher: referenceType(pointee(hasCanonicalType(InnerMatcher: templateTypeParmType())))),
74 hasType(InnerMatcher: referenceType(pointee(substTemplateTypeParmType()))));
75
76 const auto AutoTemplateType = varDecl(
77 anyOf(hasType(InnerMatcher: autoType()), hasType(InnerMatcher: referenceType(pointee(autoType()))),
78 hasType(InnerMatcher: pointerType(pointee(autoType())))));
79
80 const auto FunctionPointerRef =
81 hasType(InnerMatcher: hasCanonicalType(InnerMatcher: 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(InnerMatcher: anything()),
87 unless(anyOf(ConstType, ConstReference, TemplateType,
88 hasInitializer(InnerMatcher: isInstantiationDependent()), AutoTemplateType,
89 RValueReference, FunctionPointerRef,
90 hasType(InnerMatcher: 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(
97 InnerMatcher: compoundStmt(forEachDescendant(
98 declStmt(containsAnyDeclaration(
99 InnerMatcher: LocalValDecl.bind(ID: "local-value")),
100 unless(has(decompositionDecl())))
101 .bind(ID: "decl-stmt")))
102 .bind(ID: "scope")))
103 .bind(ID: "function-decl");
104
105 Finder->addMatcher(NodeMatch: FunctionScope, Action: this);
106}
107
108/// Classify for a variable in what the Const-Check is interested.
109enum class VariableCategory { Value, Reference, Pointer };
110
111void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
112 const auto *LocalScope = Result.Nodes.getNodeAs<CompoundStmt>(ID: "scope");
113 const auto *Variable = Result.Nodes.getNodeAs<VarDecl>(ID: "local-value");
114 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(ID: "function-decl");
115
116 /// If the variable was declared in a template it might be analyzed multiple
117 /// times. Only one of those instantiations shall emit a warning. NOTE: This
118 /// shall only deduplicate warnings for variables that are not instantiation
119 /// dependent. Variables like 'int x = 42;' in a template that can become
120 /// const emit multiple warnings otherwise.
121 bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
122 if (IsNormalVariableInTemplate &&
123 TemplateDiagnosticsCache.contains(V: Variable->getBeginLoc()))
124 return;
125
126 VariableCategory VC = VariableCategory::Value;
127 if (Variable->getType()->isReferenceType())
128 VC = VariableCategory::Reference;
129 if (Variable->getType()->isPointerType())
130 VC = VariableCategory::Pointer;
131 if (Variable->getType()->isArrayType()) {
132 if (const auto *ArrayT = dyn_cast<ArrayType>(Variable->getType())) {
133 if (ArrayT->getElementType()->isPointerType())
134 VC = VariableCategory::Pointer;
135 }
136 }
137
138 // Each variable can only be in one category: Value, Pointer, Reference.
139 // Analysis can be controlled for every category.
140 if (VC == VariableCategory::Reference && !AnalyzeReferences)
141 return;
142
143 if (VC == VariableCategory::Reference &&
144 Variable->getType()->getPointeeType()->isPointerType() &&
145 !WarnPointersAsValues)
146 return;
147
148 if (VC == VariableCategory::Pointer && !WarnPointersAsValues)
149 return;
150
151 if (VC == VariableCategory::Value && !AnalyzeValues)
152 return;
153
154 // The scope is only registered if the analysis shall be run.
155 registerScope(LocalScope, Context: Result.Context);
156
157 // Offload const-analysis to utility function.
158 if (ScopesCache[LocalScope]->isMutated(Variable))
159 return;
160
161 auto Diag = diag(Variable->getBeginLoc(),
162 "variable %0 of type %1 can be declared 'const'")
163 << Variable << Variable->getType();
164 if (IsNormalVariableInTemplate)
165 TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
166
167 const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>(ID: "decl-stmt");
168
169 // It can not be guaranteed that the variable is declared isolated, therefore
170 // a transformation might effect the other variables as well and be incorrect.
171 if (VarDeclStmt == nullptr || !VarDeclStmt->isSingleDecl())
172 return;
173
174 using namespace utils::fixit;
175 if (VC == VariableCategory::Value && TransformValues) {
176 Diag << addQualifierToVarDecl(Var: *Variable, Context: *Result.Context,
177 Qualifier: DeclSpec::TQ_const, QualTarget: QualifierTarget::Value,
178 QualPolicy: QualifierPolicy::Right);
179 // FIXME: Add '{}' for default initialization if no user-defined default
180 // constructor exists and there is no initializer.
181 return;
182 }
183
184 if (VC == VariableCategory::Reference && TransformReferences) {
185 Diag << addQualifierToVarDecl(Var: *Variable, Context: *Result.Context,
186 Qualifier: DeclSpec::TQ_const, QualTarget: QualifierTarget::Value,
187 QualPolicy: QualifierPolicy::Right);
188 return;
189 }
190
191 if (VC == VariableCategory::Pointer) {
192 if (WarnPointersAsValues && TransformPointersAsValues) {
193 Diag << addQualifierToVarDecl(Var: *Variable, Context: *Result.Context,
194 Qualifier: DeclSpec::TQ_const, QualTarget: QualifierTarget::Value,
195 QualPolicy: QualifierPolicy::Right);
196 }
197 return;
198 }
199}
200
201void ConstCorrectnessCheck::registerScope(const CompoundStmt *LocalScope,
202 ASTContext *Context) {
203 auto &Analyzer = ScopesCache[LocalScope];
204 if (!Analyzer)
205 Analyzer = std::make_unique<ExprMutationAnalyzer>(args: *LocalScope, args&: *Context);
206}
207
208} // namespace clang::tidy::misc
209

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