1 //===--- ConstCorrectnessCheck.cpp - clang-tidy -----------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::misc
{
20 // FIXME: This matcher exists in some other code-review as well.
21 // It should probably move to ASTMatchers.
22 AST_MATCHER(VarDecl
, isLocal
) { return Node
.isLocalVarDecl(); }
23 AST_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();
29 AST_MATCHER(ReferenceType
, isSpelledAsLValue
) {
30 return Node
.isSpelledAsLValue();
32 AST_MATCHER(Type
, isDependentType
) { return Node
.isDependentType(); }
35 ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name
,
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)
46 this->configurationDiag(
47 "The check 'misc-const-correctness' will not "
48 "perform any analysis because both 'AnalyzeValues' and "
49 "'AnalyzeReferences' are false.");
52 void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
53 Options
.store(Opts
, "AnalyzeValues", AnalyzeValues
);
54 Options
.store(Opts
, "AnalyzeReferences", AnalyzeReferences
);
55 Options
.store(Opts
, "WarnPointersAsValues", WarnPointersAsValues
);
57 Options
.store(Opts
, "TransformValues", TransformValues
);
58 Options
.store(Opts
, "TransformReferences", TransformReferences
);
59 Options
.store(Opts
, "TransformPointersAsValues", TransformPointersAsValues
);
62 void 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()))));
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()))));
76 const auto AutoTemplateType
= varDecl(
77 anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
78 hasType(pointerType(pointee(autoType())))));
80 const auto FunctionPointerRef
=
81 hasType(hasCanonicalType(referenceType(pointee(functionType()))));
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())));
92 // Match the function scope for which the analysis of all local variables
94 const auto FunctionScope
=
96 hasBody(stmt(forEachDescendant(
97 declStmt(containsAnyDeclaration(
98 LocalValDecl
.bind("local-value")),
99 unless(has(decompositionDecl())))
102 .bind("function-decl");
104 Finder
->addMatcher(FunctionScope
, this);
107 /// Classify for a variable in what the Const-Check is interested.
108 enum class VariableCategory
{ Value
, Reference
, Pointer
};
110 void 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");
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()))
125 VariableCategory VC
= VariableCategory::Value
;
126 if (Variable
->getType()->isReferenceType())
127 VC
= VariableCategory::Reference
;
128 if (Variable
->getType()->isPointerType())
129 VC
= VariableCategory::Pointer
;
130 if (Variable
->getType()->isArrayType()) {
131 if (const auto *ArrayT
= dyn_cast
<ArrayType
>(Variable
->getType())) {
132 if (ArrayT
->getElementType()->isPointerType())
133 VC
= VariableCategory::Pointer
;
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
)
142 if (VC
== VariableCategory::Reference
&&
143 Variable
->getType()->getPointeeType()->isPointerType() &&
144 !WarnPointersAsValues
)
147 if (VC
== VariableCategory::Pointer
&& !WarnPointersAsValues
)
150 if (VC
== VariableCategory::Value
&& !AnalyzeValues
)
153 // The scope is only registered if the analysis shall be run.
154 registerScope(LocalScope
, Result
.Context
);
156 // Offload const-analysis to utility function.
157 if (ScopesCache
[LocalScope
]->isMutated(Variable
))
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());
166 const auto *VarDeclStmt
= Result
.Nodes
.getNodeAs
<DeclStmt
>("decl-stmt");
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())
173 using namespace utils::fixit
;
174 if (VC
== VariableCategory::Value
&& TransformValues
) {
175 Diag
<< addQualifierToVarDecl(*Variable
, *Result
.Context
, Qualifiers::Const
,
176 QualifierTarget::Value
,
177 QualifierPolicy::Right
);
178 // FIXME: Add '{}' for default initialization if no user-defined default
179 // constructor exists and there is no initializer.
183 if (VC
== VariableCategory::Reference
&& TransformReferences
) {
184 Diag
<< addQualifierToVarDecl(*Variable
, *Result
.Context
, Qualifiers::Const
,
185 QualifierTarget::Value
,
186 QualifierPolicy::Right
);
190 if (VC
== VariableCategory::Pointer
) {
191 if (WarnPointersAsValues
&& TransformPointersAsValues
) {
192 Diag
<< addQualifierToVarDecl(*Variable
, *Result
.Context
,
193 Qualifiers::Const
, QualifierTarget::Value
,
194 QualifierPolicy::Right
);
200 void ConstCorrectnessCheck::registerScope(const Stmt
*LocalScope
,
201 ASTContext
*Context
) {
202 auto &Analyzer
= ScopesCache
[LocalScope
];
204 Analyzer
= std::make_unique
<ExprMutationAnalyzer
>(*LocalScope
, *Context
);
207 } // namespace clang::tidy::misc