Skip to content

Commit 46ae26e

Browse files
committed
[clang-tidy] implement new check 'misc-const-correctness' to add 'const' to unmodified variables
This patch connects the check for const-correctness with the new general utility to add `const` to variables. The code-transformation is only done, if the detected variable for const-ness is not part of a group-declaration. The check allows to control multiple facets of adding `const`, e.g. if pointers themself should be marked as `const` if they are not changed. Reviewed By: njames93 Differential Revision: https://reviews.llvm.org/D54943
1 parent 8f24a56 commit 46ae26e

17 files changed

+1777
-10
lines changed

clang-tools-extra/clang-tidy/misc/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ add_custom_command(
2222
add_custom_target(genconfusable DEPENDS Confusables.inc)
2323

2424
add_clang_library(clangTidyMiscModule
25+
ConstCorrectnessCheck.cpp
2526
DefinitionsInHeadersCheck.cpp
2627
ConfusableIdentifierCheck.cpp
2728
MiscTidyModule.cpp
@@ -42,6 +43,7 @@ add_clang_library(clangTidyMiscModule
4243
UnusedUsingDeclsCheck.cpp
4344

4445
LINK_LIBS
46+
clangAnalysis
4547
clangTidy
4648
clangTidyUtils
4749

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

clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "../ClangTidyModule.h"
1111
#include "../ClangTidyModuleRegistry.h"
1212
#include "ConfusableIdentifierCheck.h"
13+
#include "ConstCorrectnessCheck.h"
1314
#include "DefinitionsInHeadersCheck.h"
1415
#include "MisleadingBidirectional.h"
1516
#include "MisleadingIdentifier.h"
@@ -36,6 +37,8 @@ class MiscModule : public ClangTidyModule {
3637
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
3738
CheckFactories.registerCheck<ConfusableIdentifierCheck>(
3839
"misc-confusable-identifiers");
40+
CheckFactories.registerCheck<ConstCorrectnessCheck>(
41+
"misc-const-correctness");
3942
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
4043
"misc-definitions-in-headers");
4144
CheckFactories.registerCheck<MisleadingBidirectionalCheck>(

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ New checks
137137

138138
Warns when there is an assignment within an if statement condition expression.
139139

140+
- New :doc:`misc-const-correctness
141+
<clang-tidy/checks/misc/const-correctness>` check.
142+
143+
Detects unmodified local variables and suggest adding ``const`` if the transformation is possible.
144+
140145
- New :doc:`modernize-macro-to-enum
141146
<clang-tidy/checks/modernize/macro-to-enum>` check.
142147

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ Clang-Tidy Checks
239239
`llvmlibc-implementation-in-namespace <llvmlibc/implementation-in-namespace.html>`_,
240240
`llvmlibc-restrict-system-libc-headers <llvmlibc/restrict-system-libc-headers.html>`_, "Yes"
241241
`misc-confusable-identifiers <misc/confusable-identifiers.html>`_,
242+
`misc-const-correctness <misc/const-correctness.html>`_, "Yes"
242243
`misc-definitions-in-headers <misc/definitions-in-headers.html>`_, "Yes"
243244
`misc-misleading-bidirectional <misc/misleading-bidirectional.html>`_,
244245
`misc-misleading-identifier <misc/misleading-identifier.html>`_,

0 commit comments

Comments
 (0)