xref: /llvm-project/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp (revision 1b0fcf1e42e05611ec37aa7956988ae6317ad116)
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::tidy::misc {
18 
19 namespace {
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();
28 }
29 AST_MATCHER(ReferenceType, isSpelledAsLValue) {
30   return Node.isSpelledAsLValue();
31 }
32 AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
33 } // namespace
34 
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.");
50 }
51 
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);
56 
57   Options.store(Opts, "TransformValues", TransformValues);
58   Options.store(Opts, "TransformReferences", TransformReferences);
59   Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
60 }
61 
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()))));
67 
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()))));
75 
76   const auto AutoTemplateType = varDecl(
77       anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
78             hasType(pointerType(pointee(autoType())))));
79 
80   const auto FunctionPointerRef =
81       hasType(hasCanonicalType(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(anything()),
87       unless(anyOf(ConstType, ConstReference, TemplateType,
88                    hasInitializer(isInstantiationDependent()), AutoTemplateType,
89                    RValueReference, FunctionPointerRef,
90                    hasType(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(stmt(forEachDescendant(
97                            declStmt(containsAnyDeclaration(
98                                         LocalValDecl.bind("local-value")),
99                                     unless(has(decompositionDecl())))
100                                .bind("decl-stmt")))
101                       .bind("scope")))
102           .bind("function-decl");
103 
104   Finder->addMatcher(FunctionScope, this);
105 }
106 
107 /// Classify for a variable in what the Const-Check is interested.
108 enum class VariableCategory { Value, Reference, Pointer };
109 
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");
114 
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()))
123     return;
124 
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;
134     }
135   }
136 
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)
140     return;
141 
142   if (VC == VariableCategory::Reference &&
143       Variable->getType()->getPointeeType()->isPointerType() &&
144       !WarnPointersAsValues)
145     return;
146 
147   if (VC == VariableCategory::Pointer && !WarnPointersAsValues)
148     return;
149 
150   if (VC == VariableCategory::Value && !AnalyzeValues)
151     return;
152 
153   // The scope is only registered if the analysis shall be run.
154   registerScope(LocalScope, Result.Context);
155 
156   // Offload const-analysis to utility function.
157   if (ScopesCache[LocalScope]->isMutated(Variable))
158     return;
159 
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());
165 
166   const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
167 
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())
171     return;
172 
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.
180     return;
181   }
182 
183   if (VC == VariableCategory::Reference && TransformReferences) {
184     Diag << addQualifierToVarDecl(*Variable, *Result.Context, Qualifiers::Const,
185                                   QualifierTarget::Value,
186                                   QualifierPolicy::Right);
187     return;
188   }
189 
190   if (VC == VariableCategory::Pointer) {
191     if (WarnPointersAsValues && TransformPointersAsValues) {
192       Diag << addQualifierToVarDecl(*Variable, *Result.Context,
193                                     Qualifiers::Const, QualifierTarget::Value,
194                                     QualifierPolicy::Right);
195     }
196     return;
197   }
198 }
199 
200 void ConstCorrectnessCheck::registerScope(const Stmt *LocalScope,
201                                           ASTContext *Context) {
202   auto &Analyzer = ScopesCache[LocalScope];
203   if (!Analyzer)
204     Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
205 }
206 
207 } // namespace clang::tidy::misc
208