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