Skip to content

Commit 1a68269

Browse files
[clang-tidy] support pointee mutation check in misc-const-correctness (#130494)
Co-authored-by: Baranov Victor <[email protected]>
1 parent 20b7f59 commit 1a68269

13 files changed

+256
-91
lines changed

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

Lines changed: 118 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "clang/AST/ASTContext.h"
1414
#include "clang/ASTMatchers/ASTMatchFinder.h"
1515
#include "clang/ASTMatchers/ASTMatchers.h"
16+
#include <cassert>
1617

1718
using namespace clang::ast_matchers;
1819

@@ -37,36 +38,54 @@ AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
3738
ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
3839
ClangTidyContext *Context)
3940
: ClangTidyCheck(Name, Context),
40-
AnalyzeValues(Options.get("AnalyzeValues", true)),
41+
AnalyzePointers(Options.get("AnalyzePointers", true)),
4142
AnalyzeReferences(Options.get("AnalyzeReferences", true)),
43+
AnalyzeValues(Options.get("AnalyzeValues", true)),
44+
45+
WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),
4246
WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
43-
TransformValues(Options.get("TransformValues", true)),
44-
TransformReferences(Options.get("TransformReferences", true)),
47+
48+
TransformPointersAsPointers(
49+
Options.get("TransformPointersAsPointers", true)),
4550
TransformPointersAsValues(
4651
Options.get("TransformPointersAsValues", false)),
52+
TransformReferences(Options.get("TransformReferences", true)),
53+
TransformValues(Options.get("TransformValues", true)),
54+
4755
AllowedTypes(
4856
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {
49-
if (AnalyzeValues == false && AnalyzeReferences == false)
57+
if (AnalyzeValues == false && AnalyzeReferences == false &&
58+
AnalyzePointers == false)
5059
this->configurationDiag(
5160
"The check 'misc-const-correctness' will not "
52-
"perform any analysis because both 'AnalyzeValues' and "
53-
"'AnalyzeReferences' are false.");
61+
"perform any analysis because 'AnalyzeValues', "
62+
"'AnalyzeReferences' and 'AnalyzePointers' are false.");
5463
}
5564

5665
void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
57-
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
66+
Options.store(Opts, "AnalyzePointers", AnalyzePointers);
5867
Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
68+
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
69+
70+
Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);
5971
Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
6072

61-
Options.store(Opts, "TransformValues", TransformValues);
62-
Options.store(Opts, "TransformReferences", TransformReferences);
73+
Options.store(Opts, "TransformPointersAsPointers",
74+
TransformPointersAsPointers);
6375
Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
76+
Options.store(Opts, "TransformReferences", TransformReferences);
77+
Options.store(Opts, "TransformValues", TransformValues);
78+
6479
Options.store(Opts, "AllowedTypes",
6580
utils::options::serializeStringList(AllowedTypes));
6681
}
6782

6883
void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
69-
const auto ConstType = hasType(isConstQualified());
84+
const auto ConstType = hasType(
85+
qualType(isConstQualified(),
86+
// pointee check will check the const pointer and const array
87+
unless(pointerType()), unless(arrayType())));
88+
7089
const auto ConstReference = hasType(references(isConstQualified()));
7190
const auto RValueReference = hasType(
7291
referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
@@ -124,6 +143,11 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
124143
const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
125144
const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
126145
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
146+
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
147+
// It can not be guaranteed that the variable is declared isolated,
148+
// therefore a transformation might effect the other variables as well and
149+
// be incorrect.
150+
const bool CanBeFixIt = VarDeclStmt != nullptr && VarDeclStmt->isSingleDecl();
127151

128152
/// If the variable was declared in a template it might be analyzed multiple
129153
/// times. Only one of those instantiations shall emit a warning. NOTE: This
@@ -137,72 +161,102 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
137161

138162
VariableCategory VC = VariableCategory::Value;
139163
const QualType VT = Variable->getType();
140-
if (VT->isReferenceType())
164+
if (VT->isReferenceType()) {
141165
VC = VariableCategory::Reference;
142-
else if (VT->isPointerType())
166+
} else if (VT->isPointerType()) {
143167
VC = VariableCategory::Pointer;
144-
else if (const auto *ArrayT = dyn_cast<ArrayType>(VT))
168+
} else if (const auto *ArrayT = dyn_cast<ArrayType>(VT)) {
145169
if (ArrayT->getElementType()->isPointerType())
146170
VC = VariableCategory::Pointer;
171+
}
147172

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());
173+
auto CheckValue = [&]() {
174+
// The scope is only registered if the analysis shall be run.
175+
registerScope(LocalScope, Result.Context);
176+
177+
// Offload const-analysis to utility function.
178+
if (ScopesCache[LocalScope]->isMutated(Variable))
179+
return;
180+
181+
auto Diag = diag(Variable->getBeginLoc(),
182+
"variable %0 of type %1 can be declared 'const'")
183+
<< Variable << VT;
184+
if (IsNormalVariableInTemplate)
185+
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
186+
if (!CanBeFixIt)
187+
return;
188+
using namespace utils::fixit;
189+
if (VC == VariableCategory::Value && TransformValues) {
190+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
191+
Qualifiers::Const, QualifierTarget::Value,
192+
QualifierPolicy::Right);
193+
// FIXME: Add '{}' for default initialization if no user-defined default
194+
// constructor exists and there is no initializer.
195+
return;
196+
}
176197

177-
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
198+
if (VC == VariableCategory::Reference && TransformReferences) {
199+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
200+
Qualifiers::Const, QualifierTarget::Value,
201+
QualifierPolicy::Right);
202+
return;
203+
}
178204

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;
205+
if (VC == VariableCategory::Pointer && TransformPointersAsValues) {
206+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
207+
Qualifiers::Const, QualifierTarget::Value,
208+
QualifierPolicy::Right);
209+
return;
210+
}
211+
};
212+
213+
auto CheckPointee = [&]() {
214+
assert(VC == VariableCategory::Pointer);
215+
registerScope(LocalScope, Result.Context);
216+
if (ScopesCache[LocalScope]->isPointeeMutated(Variable))
217+
return;
218+
auto Diag =
219+
diag(Variable->getBeginLoc(),
220+
"pointee of variable %0 of type %1 can be declared 'const'")
221+
<< Variable << VT;
222+
if (IsNormalVariableInTemplate)
223+
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
224+
if (!CanBeFixIt)
225+
return;
226+
using namespace utils::fixit;
227+
if (TransformPointersAsPointers) {
228+
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
229+
Qualifiers::Const, QualifierTarget::Pointee,
230+
QualifierPolicy::Right);
231+
}
232+
};
183233

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.
234+
// Each variable can only be in one category: Value, Pointer, Reference.
235+
// Analysis can be controlled for every category.
236+
if (VC == VariableCategory::Value && AnalyzeValues) {
237+
CheckValue();
191238
return;
192239
}
193-
194-
if (VC == VariableCategory::Reference && TransformReferences) {
195-
Diag << addQualifierToVarDecl(*Variable, *Result.Context, Qualifiers::Const,
196-
QualifierTarget::Value,
197-
QualifierPolicy::Right);
240+
if (VC == VariableCategory::Reference && AnalyzeReferences) {
241+
if (VT->getPointeeType()->isPointerType() && !WarnPointersAsValues)
242+
return;
243+
CheckValue();
198244
return;
199245
}
200-
201-
if (VC == VariableCategory::Pointer) {
202-
if (WarnPointersAsValues && TransformPointersAsValues) {
203-
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
204-
Qualifiers::Const, QualifierTarget::Value,
205-
QualifierPolicy::Right);
246+
if (VC == VariableCategory::Pointer && AnalyzePointers) {
247+
if (WarnPointersAsValues && !VT.isConstQualified())
248+
CheckValue();
249+
if (WarnPointersAsPointers) {
250+
if (const auto *PT = dyn_cast<PointerType>(VT)) {
251+
if (!PT->getPointeeType().isConstQualified())
252+
CheckPointee();
253+
}
254+
if (const auto *AT = dyn_cast<ArrayType>(VT)) {
255+
if (!AT->getElementType().isConstQualified()) {
256+
assert(AT->getElementType()->isPointerType());
257+
CheckPointee();
258+
}
259+
}
206260
}
207261
return;
208262
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,18 @@ class ConstCorrectnessCheck : public ClangTidyCheck {
3838
llvm::DenseMap<const Stmt *, MutationAnalyzer> ScopesCache;
3939
llvm::DenseSet<SourceLocation> TemplateDiagnosticsCache;
4040

41-
const bool AnalyzeValues;
41+
const bool AnalyzePointers;
4242
const bool AnalyzeReferences;
43+
const bool AnalyzeValues;
44+
45+
const bool WarnPointersAsPointers;
4346
const bool WarnPointersAsValues;
4447

45-
const bool TransformValues;
46-
const bool TransformReferences;
48+
const bool TransformPointersAsPointers;
4749
const bool TransformPointersAsValues;
50+
const bool TransformReferences;
51+
const bool TransformValues;
52+
4853
const std::vector<StringRef> AllowedTypes;
4954
};
5055

clang-tools-extra/docs/ReleaseNotes.rst

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

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

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

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ as potential ``const``.
2121
int result = i * i; // Before transformation
2222
int const result = i * i; // After transformation
2323

24-
The check can analyze values, pointers and references but not (yet) pointees:
24+
The check can analyze values, pointers and references and pointees:
2525

2626
.. code-block:: c++
2727

@@ -39,8 +39,9 @@ The check can analyze values, pointers and references but not (yet) pointees:
3939
int const& reference_value = potential_const_int; // After transformation
4040
int another_copy = reference_value;
4141

42-
// The similar semantics of pointers are not (yet) analyzed.
43-
int *pointer_variable = &potential_const_int; // _NO_ 'const int *pointer_variable' suggestion.
42+
// The similar semantics of pointers are analyzed.
43+
int *pointer_variable = &potential_const_int; // Before transformation
44+
int const*const pointer_variable = &potential_const_int; // After transformation, both pointer itself and pointee are supported.
4445
int last_copy = *pointer_variable;
4546
4647
The automatic code transformation is only applied to variables that are declared in single
@@ -61,22 +62,6 @@ Different instantiations can result in different ``const`` correctness propertie
6162
is not possible to find all instantiations of a template. The template might be used differently in
6263
an independent translation unit.
6364

64-
Pointees can not be analyzed for constness yet. The following code shows this limitation.
65-
66-
.. code-block:: c++
67-
68-
// Declare a variable that will not be modified.
69-
int constant_value = 42;
70-
71-
// Declare a pointer to that variable, that does not modify either, but misses 'const'.
72-
// Could be 'const int *pointer_to_constant = &constant_value;'
73-
int *pointer_to_constant = &constant_value;
74-
75-
// Usage:
76-
int result = 520 * 120 * (*pointer_to_constant);
77-
78-
This limitation affects the capability to add ``const`` to methods which is not possible, too.
79-
8065
Options
8166
-------
8267

@@ -110,6 +95,13 @@ Options
11095
// No warning
11196
int const& ref = i;
11297

98+
.. option:: AnalyzePointers
99+
100+
Enable or disable the analysis of pointers variables, like
101+
``int *ptr = &i;``. For specific checks, see
102+
:option:`WarnPointersAsValues` and :option:`WarnPointersAsPointers`.
103+
Default is `true`.
104+
113105
.. option:: WarnPointersAsValues
114106

115107
This option enables the suggestion for ``const`` of the pointer itself.
@@ -125,6 +117,22 @@ Options
125117
// No warning
126118
const int *const pointer_variable = &value;
127119
120+
.. option:: WarnPointersAsPointers
121+
122+
This option enables the suggestion for ``const`` of the value pointing to.
123+
Default is `true`.
124+
125+
Requires :option:`AnalyzePointers` to be `true`.
126+
127+
.. code-block:: c++
128+
129+
int value = 42;
130+
131+
// No warning
132+
const int *const pointer_variable = &value;
133+
// Warning
134+
int *const pointer_variable = &value;
135+
128136
.. option:: TransformValues
129137

130138
Provides fixit-hints for value types that automatically add ``const`` if
@@ -200,6 +208,27 @@ Options
200208
int *changing_pointee = &value;
201209
changing_pointee = &result;
202210
211+
.. option:: TransformPointersAsPointers
212+
213+
Provides fix-it hints for pointers if the value it pointing to is not changed.
214+
Default is `false`.
215+
216+
Requires :option:`WarnPointersAsPointers` to be `true`.
217+
218+
.. code-block:: c++
219+
220+
int value = 42;
221+
222+
// Before
223+
int * pointer_variable = &value;
224+
// After
225+
const int * pointer_variable = &value;
226+
227+
// Before
228+
int * a[] = {&value, &value};
229+
// After
230+
const int * a[] = {&value, &value};
231+
203232
.. option:: AllowedTypes
204233

205234
A semicolon-separated list of names of types that will be excluded from

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
// RUN: misc-const-correctness.AllowedTypes: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$;qualified::Type;::fully::QualifiedType;ConstTemplate', \
44
// RUN: misc-const-correctness.TransformPointersAsValues: true, \
55
// RUN: misc-const-correctness.TransformReferences: true, \
6-
// RUN: misc-const-correctness.WarnPointersAsValues: true } \
6+
// RUN: misc-const-correctness.WarnPointersAsValues: true, \
7+
// RUN: misc-const-correctness.WarnPointersAsPointers: false } \
78
// RUN: }" -- -fno-delayed-template-parsing
89

910
struct SmartPointer {

0 commit comments

Comments
 (0)