Skip to content

Commit 353f538

Browse files
committed
[clang-tidy] support pointee mutation check in misc-const-correctness
1 parent 5cfc37b commit 353f538

File tree

9 files changed

+207
-59
lines changed

9 files changed

+207
-59
lines changed

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

Lines changed: 101 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "clang/AST/ASTContext.h"
1414
#include "clang/ASTMatchers/ASTMatchFinder.h"
1515
#include "clang/ASTMatchers/ASTMatchers.h"
16+
#include "llvm/Support/Casting.h"
17+
#include <cassert>
1618

1719
using namespace clang::ast_matchers;
1820

@@ -39,34 +41,47 @@ ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
3941
: ClangTidyCheck(Name, Context),
4042
AnalyzeValues(Options.get("AnalyzeValues", true)),
4143
AnalyzeReferences(Options.get("AnalyzeReferences", true)),
44+
AnalyzePointers(Options.get("AnalyzePointers", true)),
4245
WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
46+
WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),
4347
TransformValues(Options.get("TransformValues", true)),
4448
TransformReferences(Options.get("TransformReferences", true)),
4549
TransformPointersAsValues(
4650
Options.get("TransformPointersAsValues", false)),
51+
TransformPointersAsPointers(
52+
Options.get("TransformPointersAsPointers", true)),
4753
AllowedTypes(
4854
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {
49-
if (AnalyzeValues == false && AnalyzeReferences == false)
55+
if (AnalyzeValues == false && AnalyzeReferences == false &&
56+
AnalyzePointers == false)
5057
this->configurationDiag(
5158
"The check 'misc-const-correctness' will not "
52-
"perform any analysis because both 'AnalyzeValues' and "
53-
"'AnalyzeReferences' are false.");
59+
"perform any analysis because both 'AnalyzeValues', "
60+
"'AnalyzeReferences' and 'AnalyzePointers' are false.");
5461
}
5562

5663
void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
5764
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
5865
Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
66+
Options.store(Opts, "AnalyzePointers", AnalyzePointers);
5967
Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
68+
Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);
6069

6170
Options.store(Opts, "TransformValues", TransformValues);
6271
Options.store(Opts, "TransformReferences", TransformReferences);
6372
Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
73+
Options.store(Opts, "TransformPointersAsPointers",
74+
TransformPointersAsPointers);
6475
Options.store(Opts, "AllowedTypes",
6576
utils::options::serializeStringList(AllowedTypes));
6677
}
6778

6879
void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
69-
const auto ConstType = hasType(isConstQualified());
80+
const auto ConstType = hasType(
81+
qualType(isConstQualified(),
82+
// pointee check will check the const pointer and const array
83+
unless(pointerType()), unless(arrayType())));
84+
7085
const auto ConstReference = hasType(references(isConstQualified()));
7186
const auto RValueReference = hasType(
7287
referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
@@ -124,6 +139,11 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
124139
const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
125140
const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
126141
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
142+
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
143+
// It can not be guaranteed that the variable is declared isolated,
144+
// therefore a transformation might effect the other variables as well and
145+
// be incorrect.
146+
const bool CanBeFixIt = VarDeclStmt != nullptr && VarDeclStmt->isSingleDecl();
127147

128148
/// If the variable was declared in a template it might be analyzed multiple
129149
/// times. Only one of those instantiations shall emit a warning. NOTE: This
@@ -145,64 +165,90 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
145165
if (ArrayT->getElementType()->isPointerType())
146166
VC = VariableCategory::Pointer;
147167

148-
// Each variable can only be in one category: Value, Pointer, Reference.
149-
// Analysis can be controlled for every category.
150-
if (VC == VariableCategory::Reference && !AnalyzeReferences)
151-
return;
152-
153-
if (VC == VariableCategory::Reference &&
154-
Variable->getType()->getPointeeType()->isPointerType() &&
155-
!WarnPointersAsValues)
156-
return;
157-
158-
if (VC == VariableCategory::Pointer && !WarnPointersAsValues)
159-
return;
160-
161-
if (VC == VariableCategory::Value && !AnalyzeValues)
162-
return;
163-
164-
// The scope is only registered if the analysis shall be run.
165-
registerScope(LocalScope, Result.Context);
166-
167-
// Offload const-analysis to utility function.
168-
if (ScopesCache[LocalScope]->isMutated(Variable))
169-
return;
170-
171-
auto Diag = diag(Variable->getBeginLoc(),
172-
"variable %0 of type %1 can be declared 'const'")
173-
<< Variable << Variable->getType();
174-
if (IsNormalVariableInTemplate)
175-
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
168+
auto CheckValue = [&]() {
169+
// The scope is only registered if the analysis shall be run.
170+
registerScope(LocalScope, Result.Context);
171+
172+
// Offload const-analysis to utility function.
173+
if (ScopesCache[LocalScope]->isMutated(Variable))
174+
return;
175+
176+
auto Diag = diag(Variable->getBeginLoc(),
177+
"variable %0 of type %1 can be declared 'const'")
178+
<< Variable << VT;
179+
if (IsNormalVariableInTemplate)
180+
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
181+
if (!CanBeFixIt)
182+
return;
183+
using namespace utils::fixit;
184+
if (VC == VariableCategory::Value && TransformValues) {
185+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
186+
Qualifiers::Const, QualifierTarget::Value,
187+
QualifierPolicy::Right);
188+
// FIXME: Add '{}' for default initialization if no user-defined default
189+
// constructor exists and there is no initializer.
190+
return;
191+
}
176192

177-
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
193+
if (VC == VariableCategory::Reference && TransformReferences) {
194+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
195+
Qualifiers::Const, QualifierTarget::Value,
196+
QualifierPolicy::Right);
197+
return;
198+
}
178199

179-
// It can not be guaranteed that the variable is declared isolated, therefore
180-
// a transformation might effect the other variables as well and be incorrect.
181-
if (VarDeclStmt == nullptr || !VarDeclStmt->isSingleDecl())
182-
return;
200+
if (VC == VariableCategory::Pointer && TransformPointersAsValues) {
201+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
202+
Qualifiers::Const, QualifierTarget::Value,
203+
QualifierPolicy::Right);
204+
return;
205+
}
206+
};
207+
208+
auto CheckPointee = [&]() {
209+
assert(VC == VariableCategory::Pointer);
210+
registerScope(LocalScope, Result.Context);
211+
if (ScopesCache[LocalScope]->isPointeeMutated(Variable))
212+
return;
213+
auto Diag = diag(Variable->getBeginLoc(),
214+
"variable %0 of type %1 can be declared 'const'")
215+
<< Variable << VT;
216+
if (IsNormalVariableInTemplate)
217+
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
218+
if (!CanBeFixIt)
219+
return;
220+
using namespace utils::fixit;
221+
if (TransformPointersAsPointers) {
222+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
223+
Qualifiers::Const, QualifierTarget::Pointee,
224+
QualifierPolicy::Right);
225+
}
226+
};
183227

184-
using namespace utils::fixit;
185-
if (VC == VariableCategory::Value && TransformValues) {
186-
Diag << addQualifierToVarDecl(*Variable, *Result.Context, Qualifiers::Const,
187-
QualifierTarget::Value,
188-
QualifierPolicy::Right);
189-
// FIXME: Add '{}' for default initialization if no user-defined default
190-
// constructor exists and there is no initializer.
228+
// Each variable can only be in one category: Value, Pointer, Reference.
229+
// Analysis can be controlled for every category.
230+
if (VC == VariableCategory::Value && AnalyzeValues) {
231+
CheckValue();
191232
return;
192233
}
193-
194-
if (VC == VariableCategory::Reference && TransformReferences) {
195-
Diag << addQualifierToVarDecl(*Variable, *Result.Context, Qualifiers::Const,
196-
QualifierTarget::Value,
197-
QualifierPolicy::Right);
234+
if (VC == VariableCategory::Reference && AnalyzeReferences) {
235+
if (VT->getPointeeType()->isPointerType() && !WarnPointersAsValues)
236+
return;
237+
CheckValue();
198238
return;
199239
}
200-
201-
if (VC == VariableCategory::Pointer) {
202-
if (WarnPointersAsValues && TransformPointersAsValues) {
203-
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
204-
Qualifiers::Const, QualifierTarget::Value,
205-
QualifierPolicy::Right);
240+
if (VC == VariableCategory::Pointer && AnalyzePointers) {
241+
if (WarnPointersAsValues && !VT.isConstQualified())
242+
CheckValue();
243+
if (WarnPointersAsPointers) {
244+
if (const auto *PT = dyn_cast<PointerType>(VT))
245+
if (!PT->getPointeeType().isConstQualified())
246+
CheckPointee();
247+
if (const auto *AT = dyn_cast<ArrayType>(VT))
248+
if (!AT->getElementType().isConstQualified()) {
249+
assert(AT->getElementType()->isPointerType());
250+
CheckPointee();
251+
}
206252
}
207253
return;
208254
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ class ConstCorrectnessCheck : public ClangTidyCheck {
4040

4141
const bool AnalyzeValues;
4242
const bool AnalyzeReferences;
43+
const bool AnalyzePointers;
4344
const bool WarnPointersAsValues;
45+
const bool WarnPointersAsPointers;
4446

4547
const bool TransformValues;
4648
const bool TransformReferences;
4749
const bool TransformPointersAsValues;
50+
const bool TransformPointersAsPointers;
4851
const std::vector<StringRef> AllowedTypes;
4952
};
5053

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ Changes in existing checks
132132
<clang-tidy/checks/misc/const-correctness>` check by adding the option
133133
`AllowedTypes`, that excludes specified types from const-correctness
134134
checking and fixing false positives when modifying variant by ``operator[]``
135-
with template in parameters.
135+
with template in parameters and supporting to check pointee mutation by
136+
`AnalyzePointers` option.
136137

137138
- Improved :doc:`misc-redundant-expression
138139
<clang-tidy/checks/misc/redundant-expression>` check by providing additional

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ Options
110110
// No warning
111111
int const& ref = i;
112112

113+
.. option:: AnalyzePointers
114+
115+
Enable or disable the analysis of pointers variables, like
116+
``int *ptr = &i;``. For specific checks, see options
117+
`WarnPointersAsValues` and `WarnPointersAsPointers`.
118+
Default is `true`.
119+
113120
.. option:: WarnPointersAsValues
114121

115122
This option enables the suggestion for ``const`` of the pointer itself.
@@ -125,6 +132,22 @@ Options
125132
// No warning
126133
const int *const pointer_variable = &value;
127134
135+
.. option:: WarnPointersAsPointers
136+
137+
This option enables the suggestion for ``const`` of the value pointing.
138+
Default is `true`.
139+
140+
Requires 'AnalyzePointers' to be 'true'.
141+
142+
.. code-block:: c++
143+
144+
int value = 42;
145+
146+
// No warning
147+
const int *const pointer_variable = &value;
148+
// Warning
149+
int *const pointer_variable = &value;
150+
128151
.. option:: TransformValues
129152

130153
Provides fixit-hints for value types that automatically add ``const`` if
@@ -200,6 +223,27 @@ Options
200223
int *changing_pointee = &value;
201224
changing_pointee = &result;
202225
226+
.. option:: TransformPointersAsPointers
227+
228+
Provides fixit-hints for pointers if the value it pointing to is not changed.
229+
Default is `false`.
230+
231+
Requires 'WarnPointersAsPointers' to be 'true'.
232+
233+
.. code-block:: c++
234+
235+
int value = 42;
236+
237+
// Before
238+
int * pointer_variable = &value;
239+
// After
240+
const int * pointer_variable = &value;
241+
242+
// Before
243+
int * a[] = {&value, &value};
244+
// After
245+
const int * a[] = {&value, &value};
246+
203247
.. option:: AllowedTypes
204248

205249
A semicolon-separated list of names of types that will be excluded from
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %check_clang_tidy %s misc-const-correctness %t \
2+
// RUN: -config='{CheckOptions: {\
3+
// RUN: misc-const-correctness.AnalyzeValues: false,\
4+
// RUN: misc-const-correctness.AnalyzeReferences: false,\
5+
// RUN: misc-const-correctness.AnalyzePointers: true,\
6+
// RUN: misc-const-correctness.WarnPointersAsValues: false,\
7+
// RUN: misc-const-correctness.WarnPointersAsPointers: true,\
8+
// RUN: misc-const-correctness.TransformPointersAsValues: false,\
9+
// RUN: misc-const-correctness.TransformPointersAsPointers: true\
10+
// RUN: }}' \
11+
// RUN: -- -fno-delayed-template-parsing
12+
13+
void pointee_to_const() {
14+
int a[] = {1, 2};
15+
int *p_local0 = &a[0];
16+
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int *' can be declared 'const'
17+
// CHECK-FIXES: int const*p_local0
18+
p_local0 = &a[1];
19+
}
20+
21+
void array_of_pointer_to_const() {
22+
int a[] = {1, 2};
23+
int *p_local0[1] = {&a[0]};
24+
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int *[1]' can be declared 'const'
25+
// CHECK-FIXES: int const*p_local0[1]
26+
p_local0[0] = &a[1];
27+
}
28+
29+
template<class T>
30+
void template_fn() {
31+
T a[] = {1, 2};
32+
T *p_local0 = &a[0];
33+
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'char *' can be declared 'const'
34+
// CHECK-FIXES: T const*p_local0
35+
p_local0 = &a[1];
36+
}
37+
38+
void instantiate() {
39+
template_fn<char>();
40+
template_fn<int>();
41+
template_fn<char const>();
42+
}
43+
44+
using const_int = int const;
45+
void ignore_const_alias() {
46+
const_int a[] = {1, 2};
47+
const_int *p_local0 = &a[0];
48+
p_local0 = &a[1];
49+
}
50+

clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-transform-values.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// RUN: -config="{CheckOptions: {\
33
// RUN: misc-const-correctness.TransformValues: true,\
44
// RUN: misc-const-correctness.WarnPointersAsValues: false, \
5+
// RUN: misc-const-correctness.WarnPointersAsPointers: false, \
56
// RUN: misc-const-correctness.TransformPointersAsValues: false} \
67
// RUN: }" -- -fno-delayed-template-parsing
78

clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-values-before-cxx23.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// RUN: -config="{CheckOptions: {\
33
// RUN: misc-const-correctness.TransformValues: true, \
44
// RUN: misc-const-correctness.WarnPointersAsValues: false, \
5+
// RUN: misc-const-correctness.WarnPointersAsPointers: false, \
56
// RUN: misc-const-correctness.TransformPointersAsValues: false \
67
// RUN: }}" -- -fno-delayed-template-parsing
78

clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-values.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// RUN: -config="{CheckOptions: {\
33
// RUN: misc-const-correctness.TransformValues: true, \
44
// RUN: misc-const-correctness.WarnPointersAsValues: false, \
5+
// RUN: misc-const-correctness.WarnPointersAsPointers: false, \
56
// RUN: misc-const-correctness.TransformPointersAsValues: false \
67
// RUN: }}" -- -fno-delayed-template-parsing -fexceptions
78

clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-wrong-config.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// RUN: %check_clang_tidy %s misc-const-correctness %t \
22
// RUN: -config='{CheckOptions: \
33
// RUN: {"misc-const-correctness.AnalyzeValues": false,\
4-
// RUN: "misc-const-correctness.AnalyzeReferences": false}\
5-
// RUN: }' -- -fno-delayed-template-parsing
4+
// RUN: "misc-const-correctness.AnalyzeReferences": false,\
5+
// RUN: "misc-const-correctness.AnalyzePointers": false\
6+
// RUN: }}' -- -fno-delayed-template-parsing
67

7-
// CHECK-MESSAGES: warning: The check 'misc-const-correctness' will not perform any analysis because both 'AnalyzeValues' and 'AnalyzeReferences' are false. [clang-tidy-config]
8+
// CHECK-MESSAGES: warning: The check 'misc-const-correctness' will not perform any analysis because both 'AnalyzeValues', 'AnalyzeReferences' and 'AnalyzePointers' are false. [clang-tidy-config]
89

910
void g() {
1011
int p_local0 = 42;

0 commit comments

Comments
 (0)