Skip to content

Commit 3d0b4c1

Browse files
committed
[clang-tidy] Add AnalyzeParameters option to misc-const-correctness
1 parent 65dd29b commit 3d0b4c1

File tree

9 files changed

+687
-36
lines changed

9 files changed

+687
-36
lines changed

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

Lines changed: 116 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ AST_MATCHER(ReferenceType, isSpelledAsLValue) {
3333
return Node.isSpelledAsLValue();
3434
}
3535
AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
36+
37+
AST_MATCHER(FunctionDecl, isTemplate) {
38+
return Node.getDescribedFunctionTemplate() != nullptr;
39+
}
40+
41+
AST_MATCHER(FunctionDecl, isFunctionTemplateSpecialization) {
42+
return Node.isFunctionTemplateSpecialization();
43+
}
3644
} // namespace
3745

3846
ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
@@ -41,6 +49,7 @@ ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
4149
AnalyzePointers(Options.get("AnalyzePointers", true)),
4250
AnalyzeReferences(Options.get("AnalyzeReferences", true)),
4351
AnalyzeValues(Options.get("AnalyzeValues", true)),
52+
AnalyzeParameters(Options.get("AnalyzeParameters", true)),
4453

4554
WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),
4655
WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
@@ -66,6 +75,7 @@ void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
6675
Options.store(Opts, "AnalyzePointers", AnalyzePointers);
6776
Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
6877
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
78+
Options.store(Opts, "AnalyzeParameters", AnalyzeParameters);
6979

7080
Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);
7181
Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
@@ -112,15 +122,17 @@ void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
112122
const auto FunctionPointerRef =
113123
hasType(hasCanonicalType(referenceType(pointee(functionType()))));
114124

125+
const auto CommonExcludeTypes =
126+
anyOf(ConstType, ConstReference, RValueReference, TemplateType,
127+
FunctionPointerRef, hasType(cxxRecordDecl(isLambda())),
128+
AutoTemplateType, isImplicit(), AllowedType);
129+
115130
// Match local variables which could be 'const' if not modified later.
116131
// Example: `int i = 10` would match `int i`.
117-
const auto LocalValDecl = varDecl(
118-
isLocal(), hasInitializer(anything()),
119-
unless(anyOf(ConstType, ConstReference, TemplateType,
120-
hasInitializer(isInstantiationDependent()), AutoTemplateType,
121-
RValueReference, FunctionPointerRef,
122-
hasType(cxxRecordDecl(isLambda())), isImplicit(),
123-
AllowedType)));
132+
const auto LocalValDecl =
133+
varDecl(isLocal(), hasInitializer(anything()),
134+
unless(anyOf(CommonExcludeTypes,
135+
hasInitializer(isInstantiationDependent()))));
124136

125137
// Match the function scope for which the analysis of all local variables
126138
// shall be run.
@@ -135,20 +147,83 @@ void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
135147
.bind("function-decl");
136148

137149
Finder->addMatcher(FunctionScope, this);
150+
151+
if (AnalyzeParameters) {
152+
const auto ParamMatcher =
153+
parmVarDecl(unless(CommonExcludeTypes),
154+
anyOf(hasType(referenceType()), hasType(pointerType())))
155+
.bind("param-var");
156+
157+
// Match function parameters which could be 'const' if not modified later.
158+
// Example: `void foo(int* ptr)` would match `int* ptr`.
159+
const auto FunctionWithParams =
160+
functionDecl(
161+
hasBody(stmt().bind("scope-params")),
162+
has(typeLoc(forEach(ParamMatcher))), unless(cxxMethodDecl()),
163+
unless(isFunctionTemplateSpecialization()), unless(isTemplate()))
164+
.bind("function-params");
165+
166+
Finder->addMatcher(FunctionWithParams, this);
167+
}
168+
}
169+
170+
static void addConstFixits(DiagnosticBuilder &Diag, const VarDecl *Variable,
171+
const FunctionDecl *Function, ASTContext &Context,
172+
Qualifiers::TQ Qualifier,
173+
utils::fixit::QualifierTarget Target,
174+
utils::fixit::QualifierPolicy Policy) {
175+
Diag << addQualifierToVarDecl(*Variable, Context, Qualifier, Target, Policy);
176+
177+
// If this is a parameter, also add fixits for corresponding parameters in
178+
// function declarations
179+
if (const auto *ParamDecl = dyn_cast<ParmVarDecl>(Variable)) {
180+
const unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
181+
182+
for (const FunctionDecl *Redecl : Function->redecls()) {
183+
if (Redecl == Function)
184+
continue;
185+
186+
if (ParamIdx < Redecl->getNumParams()) {
187+
const ParmVarDecl *RedeclParam = Redecl->getParamDecl(ParamIdx);
188+
Diag << addQualifierToVarDecl(*RedeclParam, Context, Qualifier, Target,
189+
Policy);
190+
}
191+
}
192+
}
138193
}
139194

140195
/// Classify for a variable in what the Const-Check is interested.
141196
enum class VariableCategory { Value, Reference, Pointer };
142197

143198
void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
144-
const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
145-
const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
146-
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
147-
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
199+
const Stmt *LocalScope = nullptr;
200+
const VarDecl *Variable = nullptr;
201+
const FunctionDecl *Function = nullptr;
202+
const DeclStmt *VarDeclStmt = nullptr;
203+
204+
if (const auto *LocalVariable =
205+
Result.Nodes.getNodeAs<VarDecl>("local-value")) {
206+
Variable = LocalVariable;
207+
LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
208+
Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
209+
VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
210+
} else if (const auto *Parameter =
211+
Result.Nodes.getNodeAs<ParmVarDecl>("param-var")) {
212+
Variable = Parameter;
213+
LocalScope = Result.Nodes.getNodeAs<Stmt>("scope-params");
214+
Function = Result.Nodes.getNodeAs<FunctionDecl>("function-params");
215+
} else {
216+
llvm_unreachable("didn't match local-value or param-var");
217+
}
218+
219+
assert(Variable && LocalScope && Function);
220+
148221
// It can not be guaranteed that the variable is declared isolated,
149222
// therefore a transformation might effect the other variables as well and
150-
// be incorrect.
151-
const bool CanBeFixIt = VarDeclStmt != nullptr && VarDeclStmt->isSingleDecl();
223+
// be incorrect. Parameters don't need this check - they receive values from
224+
// callers
225+
const bool CanBeFixIt = isa<ParmVarDecl>(Variable) ||
226+
(VarDeclStmt && VarDeclStmt->isSingleDecl());
152227

153228
/// If the variable was declared in a template it might be analyzed multiple
154229
/// times. Only one of those instantiations shall emit a warning. NOTE: This
@@ -172,11 +247,8 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
172247
}
173248

174249
auto CheckValue = [&]() {
175-
// The scope is only registered if the analysis shall be run.
176-
registerScope(LocalScope, Result.Context);
177-
178250
// Offload const-analysis to utility function.
179-
if (ScopesCache[LocalScope]->isMutated(Variable))
251+
if (isMutated(Variable, LocalScope, Function, Result.Context))
180252
return;
181253

182254
auto Diag = diag(Variable->getBeginLoc(),
@@ -187,26 +259,27 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
187259
if (!CanBeFixIt)
188260
return;
189261
using namespace utils::fixit;
262+
190263
if (VC == VariableCategory::Value && TransformValues) {
191-
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
192-
Qualifiers::Const, QualifierTarget::Value,
193-
QualifierPolicy::Right);
264+
addConstFixits(Diag, Variable, Function, *Result.Context,
265+
Qualifiers::Const, QualifierTarget::Value,
266+
QualifierPolicy::Right);
194267
// FIXME: Add '{}' for default initialization if no user-defined default
195268
// constructor exists and there is no initializer.
196269
return;
197270
}
198271

199272
if (VC == VariableCategory::Reference && TransformReferences) {
200-
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
201-
Qualifiers::Const, QualifierTarget::Value,
202-
QualifierPolicy::Right);
273+
addConstFixits(Diag, Variable, Function, *Result.Context,
274+
Qualifiers::Const, QualifierTarget::Value,
275+
QualifierPolicy::Right);
203276
return;
204277
}
205278

206279
if (VC == VariableCategory::Pointer && TransformPointersAsValues) {
207-
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
208-
Qualifiers::Const, QualifierTarget::Value,
209-
QualifierPolicy::Right);
280+
addConstFixits(Diag, Variable, Function, *Result.Context,
281+
Qualifiers::Const, QualifierTarget::Value,
282+
QualifierPolicy::Right);
210283
return;
211284
}
212285
};
@@ -226,9 +299,9 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
226299
return;
227300
using namespace utils::fixit;
228301
if (TransformPointersAsPointers) {
229-
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
230-
Qualifiers::Const, QualifierTarget::Pointee,
231-
QualifierPolicy::Right);
302+
addConstFixits(Diag, Variable, Function, *Result.Context,
303+
Qualifiers::Const, QualifierTarget::Pointee,
304+
QualifierPolicy::Right);
232305
}
233306
};
234307

@@ -271,4 +344,18 @@ void ConstCorrectnessCheck::registerScope(const Stmt *LocalScope,
271344
Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
272345
}
273346

347+
bool ConstCorrectnessCheck::isMutated(const VarDecl *Variable,
348+
const Stmt *Scope,
349+
const FunctionDecl *Func,
350+
ASTContext *Context) {
351+
if (const auto *Param = dyn_cast<ParmVarDecl>(Variable)) {
352+
return FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer(
353+
*Func, *Context, ParamMutationAnalyzerMemoized)
354+
->isMutated(Param);
355+
}
356+
357+
registerScope(Scope, Context);
358+
return ScopesCache[Scope]->isMutated(Variable);
359+
}
360+
274361
} // namespace clang::tidy::misc

clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@ class ConstCorrectnessCheck : public ClangTidyCheck {
3434
private:
3535
void registerScope(const Stmt *LocalScope, ASTContext *Context);
3636

37+
bool isMutated(const VarDecl *Variable, const Stmt *Scope,
38+
const FunctionDecl *Func, ASTContext *Context);
3739
using MutationAnalyzer = std::unique_ptr<ExprMutationAnalyzer>;
3840
llvm::DenseMap<const Stmt *, MutationAnalyzer> ScopesCache;
3941
llvm::DenseSet<SourceLocation> TemplateDiagnosticsCache;
42+
ExprMutationAnalyzer::Memoized ParamMutationAnalyzerMemoized;
4043

4144
const bool AnalyzePointers;
4245
const bool AnalyzeReferences;
4346
const bool AnalyzeValues;
47+
const bool AnalyzeParameters;
4448

4549
const bool WarnPointersAsPointers;
4650
const bool WarnPointersAsValues;

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,8 @@ Changes in existing checks
471471
and avoid false positives of function pointer and fix false
472472
positives on return of non-const pointer and fix false positives on
473473
pointer-to-member operator and avoid false positives when the address
474-
of a variable is taken to be passed to a function.
474+
of a variable is taken to be passed to a function. Added support for
475+
analyzing function parameters with the `AnalyzeParameters` option.
475476

476477
- Improved :doc:`misc-coroutine-hostile-raii
477478
<clang-tidy/checks/misc/coroutine-hostile-raii>` check by adding the option

clang-tools-extra/docs/clang-tidy/checks/misc/const-correctness.rst

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
misc-const-correctness
44
======================
55

6-
This check implements detection of local variables which could be declared as
7-
``const`` but are not. Declaring variables as ``const`` is required or
8-
recommended by many coding guidelines, such as:
6+
This check implements detection of local variables and function parameters
7+
which could be declared as ``const`` but are not. Declaring variables as
8+
``const`` is required or recommended by many coding guidelines, such as:
99
`ES.25 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es25-declare-an-object-const-or-constexpr-unless-you-want-to-modify-its-value-later-on>`_
1010
from the C++ Core Guidelines.
1111

@@ -58,9 +58,9 @@ Limitations
5858

5959
The check does not run on `C` code.
6060

61-
The check will not analyze templated variables or variables that are
62-
instantiation dependent. Different instantiations can result in
63-
different ``const`` correctness properties and in general it is not
61+
The check will not analyze templated variables and template functions or
62+
variables that are instantiation dependent. Different instantiations can result
63+
in different ``const`` correctness properties and in general it is not
6464
possible to find all instantiations of a template. The template might
6565
be used differently in an independent translation unit.
6666

@@ -104,6 +104,20 @@ Options
104104
:option:`WarnPointersAsValues` and :option:`WarnPointersAsPointers`.
105105
Default is `true`.
106106

107+
.. option:: AnalyzeParameters
108+
109+
Enable or disable the analysis of function parameters, like
110+
``void foo(int* ptr)``. Only reference and pointer parameters are analyzed.
111+
As of current implementation, member function (including constructors) and
112+
lambdas are excluded from the analysis. Default is `true`.
113+
114+
.. code-block:: c++
115+
116+
// Warning
117+
void function(int& param) {}
118+
// No warning
119+
void function(const int& param) {}
120+
107121
.. option:: WarnPointersAsValues
108122

109123
This option enables the suggestion for ``const`` of the pointer itself.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
struct S {
2+
void method() const;
3+
int value;
4+
};
5+
6+
void func_with_ref_param(S const& s);
7+
8+
void func_mixed_params(int x, S const& readonly, S& mutated);
9+
10+
void multi_decl_func(S const& s);
11+
void multi_decl_func(S const& s);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
struct S {
2+
void method() const;
3+
int value;
4+
};
5+
6+
void func_with_ref_param(S& s);
7+
8+
void func_mixed_params(int x, S& readonly, S& mutated);
9+
10+
void multi_decl_func(S& s);
11+
void multi_decl_func(S& s);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir %t
3+
// RUN: cp %S/Inputs/const-correctness/correctness.h %t/correctness.h
4+
// RUN: %check_clang_tidy %s misc-const-correctness %t/temp -- \
5+
// RUN: -- -I%t -fno-delayed-template-parsing
6+
// RUN: diff %t/correctness.h %S/Inputs/const-correctness/correctness-fixed.h
7+
8+
#include "correctness.h"
9+
10+
// CHECK-MESSAGES: :[[@LINE+1]]:26: warning: variable 's' of type 'S &' can be declared 'const'
11+
void func_with_ref_param(S& s) {
12+
// CHECK-FIXES: void func_with_ref_param(S const& s) {
13+
s.method();
14+
}
15+
16+
// CHECK-MESSAGES: :[[@LINE+1]]:31: warning: variable 'readonly' of type 'S &' can be declared 'const'
17+
void func_mixed_params(int x, S& readonly, S& mutated) {
18+
// CHECK-FIXES: void func_mixed_params(int x, S const& readonly, S& mutated) {
19+
readonly.method();
20+
mutated.value = x;
21+
}
22+
23+
// CHECK-MESSAGES: :[[@LINE+1]]:22: warning: variable 's' of type 'S &' can be declared 'const'
24+
void multi_decl_func(S& s) {
25+
// CHECK-FIXES: void multi_decl_func(S const& s) {
26+
s.method();
27+
}

0 commit comments

Comments
 (0)