Skip to content

[clang-tidy] support pointee mutation check in misc-const-correctness #130494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 118 additions & 64 deletions clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include <cassert>

using namespace clang::ast_matchers;

Expand All @@ -37,36 +38,54 @@ AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AnalyzeValues(Options.get("AnalyzeValues", true)),
AnalyzePointers(Options.get("AnalyzePointers", true)),
AnalyzeReferences(Options.get("AnalyzeReferences", true)),
AnalyzeValues(Options.get("AnalyzeValues", true)),

WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),
WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
TransformValues(Options.get("TransformValues", true)),
TransformReferences(Options.get("TransformReferences", true)),

TransformPointersAsPointers(
Options.get("TransformPointersAsPointers", true)),
TransformPointersAsValues(
Options.get("TransformPointersAsValues", false)),
TransformReferences(Options.get("TransformReferences", true)),
TransformValues(Options.get("TransformValues", true)),

AllowedTypes(
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {
if (AnalyzeValues == false && AnalyzeReferences == false)
if (AnalyzeValues == false && AnalyzeReferences == false &&
AnalyzePointers == false)
this->configurationDiag(
"The check 'misc-const-correctness' will not "
"perform any analysis because both 'AnalyzeValues' and "
"'AnalyzeReferences' are false.");
"perform any analysis because 'AnalyzeValues', "
"'AnalyzeReferences' and 'AnalyzePointers' are false.");
}

void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
Options.store(Opts, "AnalyzePointers", AnalyzePointers);
Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
Options.store(Opts, "AnalyzeValues", AnalyzeValues);

Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);
Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);

Options.store(Opts, "TransformValues", TransformValues);
Options.store(Opts, "TransformReferences", TransformReferences);
Options.store(Opts, "TransformPointersAsPointers",
TransformPointersAsPointers);
Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
Options.store(Opts, "TransformReferences", TransformReferences);
Options.store(Opts, "TransformValues", TransformValues);

Options.store(Opts, "AllowedTypes",
utils::options::serializeStringList(AllowedTypes));
}

void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
const auto ConstType = hasType(isConstQualified());
const auto ConstType = hasType(
qualType(isConstQualified(),
// pointee check will check the const pointer and const array
unless(pointerType()), unless(arrayType())));

const auto ConstReference = hasType(references(isConstQualified()));
const auto RValueReference = hasType(
referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
Expand Down Expand Up @@ -124,6 +143,11 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
// It can not be guaranteed that the variable is declared isolated,
// therefore a transformation might effect the other variables as well and
// be incorrect.
const bool CanBeFixIt = VarDeclStmt != nullptr && VarDeclStmt->isSingleDecl();

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

VariableCategory VC = VariableCategory::Value;
const QualType VT = Variable->getType();
if (VT->isReferenceType())
if (VT->isReferenceType()) {
VC = VariableCategory::Reference;
else if (VT->isPointerType())
} else if (VT->isPointerType()) {
VC = VariableCategory::Pointer;
else if (const auto *ArrayT = dyn_cast<ArrayType>(VT))
} else if (const auto *ArrayT = dyn_cast<ArrayType>(VT)) {
if (ArrayT->getElementType()->isPointerType())
VC = VariableCategory::Pointer;
}

// Each variable can only be in one category: Value, Pointer, Reference.
// Analysis can be controlled for every category.
if (VC == VariableCategory::Reference && !AnalyzeReferences)
return;

if (VC == VariableCategory::Reference &&
Variable->getType()->getPointeeType()->isPointerType() &&
!WarnPointersAsValues)
return;

if (VC == VariableCategory::Pointer && !WarnPointersAsValues)
return;

if (VC == VariableCategory::Value && !AnalyzeValues)
return;

// The scope is only registered if the analysis shall be run.
registerScope(LocalScope, Result.Context);

// Offload const-analysis to utility function.
if (ScopesCache[LocalScope]->isMutated(Variable))
return;

auto Diag = diag(Variable->getBeginLoc(),
"variable %0 of type %1 can be declared 'const'")
<< Variable << Variable->getType();
if (IsNormalVariableInTemplate)
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
auto CheckValue = [&]() {
// The scope is only registered if the analysis shall be run.
registerScope(LocalScope, Result.Context);

// Offload const-analysis to utility function.
if (ScopesCache[LocalScope]->isMutated(Variable))
return;

auto Diag = diag(Variable->getBeginLoc(),
"variable %0 of type %1 can be declared 'const'")
<< Variable << VT;
if (IsNormalVariableInTemplate)
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
if (!CanBeFixIt)
return;
using namespace utils::fixit;
if (VC == VariableCategory::Value && TransformValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
Qualifiers::Const, QualifierTarget::Value,
QualifierPolicy::Right);
// FIXME: Add '{}' for default initialization if no user-defined default
// constructor exists and there is no initializer.
return;
}

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

// It can not be guaranteed that the variable is declared isolated, therefore
// a transformation might effect the other variables as well and be incorrect.
if (VarDeclStmt == nullptr || !VarDeclStmt->isSingleDecl())
return;
if (VC == VariableCategory::Pointer && TransformPointersAsValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
Qualifiers::Const, QualifierTarget::Value,
QualifierPolicy::Right);
return;
}
};

auto CheckPointee = [&]() {
assert(VC == VariableCategory::Pointer);
registerScope(LocalScope, Result.Context);
if (ScopesCache[LocalScope]->isPointeeMutated(Variable))
return;
auto Diag =
diag(Variable->getBeginLoc(),
"pointee of variable %0 of type %1 can be declared 'const'")
<< Variable << VT;
if (IsNormalVariableInTemplate)
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
if (!CanBeFixIt)
return;
using namespace utils::fixit;
if (TransformPointersAsPointers) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
Qualifiers::Const, QualifierTarget::Pointee,
QualifierPolicy::Right);
}
};

using namespace utils::fixit;
if (VC == VariableCategory::Value && TransformValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context, Qualifiers::Const,
QualifierTarget::Value,
QualifierPolicy::Right);
// FIXME: Add '{}' for default initialization if no user-defined default
// constructor exists and there is no initializer.
// Each variable can only be in one category: Value, Pointer, Reference.
// Analysis can be controlled for every category.
if (VC == VariableCategory::Value && AnalyzeValues) {
CheckValue();
return;
}

if (VC == VariableCategory::Reference && TransformReferences) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context, Qualifiers::Const,
QualifierTarget::Value,
QualifierPolicy::Right);
if (VC == VariableCategory::Reference && AnalyzeReferences) {
if (VT->getPointeeType()->isPointerType() && !WarnPointersAsValues)
return;
CheckValue();
return;
}

if (VC == VariableCategory::Pointer) {
if (WarnPointersAsValues && TransformPointersAsValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
Qualifiers::Const, QualifierTarget::Value,
QualifierPolicy::Right);
if (VC == VariableCategory::Pointer && AnalyzePointers) {
if (WarnPointersAsValues && !VT.isConstQualified())
CheckValue();
if (WarnPointersAsPointers) {
if (const auto *PT = dyn_cast<PointerType>(VT)) {
if (!PT->getPointeeType().isConstQualified())
CheckPointee();
}
if (const auto *AT = dyn_cast<ArrayType>(VT)) {
if (!AT->getElementType().isConstQualified()) {
assert(AT->getElementType()->isPointerType());
CheckPointee();
}
}
}
return;
}
Expand Down
11 changes: 8 additions & 3 deletions clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,18 @@ class ConstCorrectnessCheck : public ClangTidyCheck {
llvm::DenseMap<const Stmt *, MutationAnalyzer> ScopesCache;
llvm::DenseSet<SourceLocation> TemplateDiagnosticsCache;

const bool AnalyzeValues;
const bool AnalyzePointers;
const bool AnalyzeReferences;
const bool AnalyzeValues;

const bool WarnPointersAsPointers;
const bool WarnPointersAsValues;

const bool TransformValues;
const bool TransformReferences;
const bool TransformPointersAsPointers;
const bool TransformPointersAsValues;
const bool TransformReferences;
const bool TransformValues;

const std::vector<StringRef> AllowedTypes;
};

Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ Changes in existing checks
<clang-tidy/checks/misc/const-correctness>` check by adding the option
`AllowedTypes`, that excludes specified types from const-correctness
checking and fixing false positives when modifying variant by ``operator[]``
with template in parameters.
with template in parameters and supporting to check pointee mutation by
`AnalyzePointers` option.

- Improved :doc:`misc-redundant-expression
<clang-tidy/checks/misc/redundant-expression>` check by providing additional
Expand Down
67 changes: 48 additions & 19 deletions clang-tools-extra/docs/clang-tidy/checks/misc/const-correctness.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ as potential ``const``.
int result = i * i; // Before transformation
int const result = i * i; // After transformation

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

.. code-block:: c++

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

// The similar semantics of pointers are not (yet) analyzed.
int *pointer_variable = &potential_const_int; // _NO_ 'const int *pointer_variable' suggestion.
// The similar semantics of pointers are analyzed.
int *pointer_variable = &potential_const_int; // Before transformation
int const*const pointer_variable = &potential_const_int; // After transformation, both pointer itself and pointee are supported.
int last_copy = *pointer_variable;

The automatic code transformation is only applied to variables that are declared in single
Expand All @@ -61,22 +62,6 @@ Different instantiations can result in different ``const`` correctness propertie
is not possible to find all instantiations of a template. The template might be used differently in
an independent translation unit.

Pointees can not be analyzed for constness yet. The following code shows this limitation.

.. code-block:: c++

// Declare a variable that will not be modified.
int constant_value = 42;

// Declare a pointer to that variable, that does not modify either, but misses 'const'.
// Could be 'const int *pointer_to_constant = &constant_value;'
int *pointer_to_constant = &constant_value;

// Usage:
int result = 520 * 120 * (*pointer_to_constant);

This limitation affects the capability to add ``const`` to methods which is not possible, too.

Options
-------

Expand Down Expand Up @@ -110,6 +95,13 @@ Options
// No warning
int const& ref = i;

.. option:: AnalyzePointers

Enable or disable the analysis of pointers variables, like
``int *ptr = &i;``. For specific checks, see
:option:`WarnPointersAsValues` and :option:`WarnPointersAsPointers`.
Default is `true`.

.. option:: WarnPointersAsValues

This option enables the suggestion for ``const`` of the pointer itself.
Expand All @@ -125,6 +117,22 @@ Options
// No warning
const int *const pointer_variable = &value;

.. option:: WarnPointersAsPointers

This option enables the suggestion for ``const`` of the value pointing to.
Default is `true`.

Requires :option:`AnalyzePointers` to be `true`.

.. code-block:: c++

int value = 42;

// No warning
const int *const pointer_variable = &value;
// Warning
int *const pointer_variable = &value;

.. option:: TransformValues

Provides fixit-hints for value types that automatically add ``const`` if
Expand Down Expand Up @@ -200,6 +208,27 @@ Options
int *changing_pointee = &value;
changing_pointee = &result;

.. option:: TransformPointersAsPointers

Provides fix-it hints for pointers if the value it pointing to is not changed.
Default is `false`.

Requires :option:`WarnPointersAsPointers` to be `true`.

.. code-block:: c++

int value = 42;

// Before
int * pointer_variable = &value;
// After
const int * pointer_variable = &value;

// Before
int * a[] = {&value, &value};
// After
const int * a[] = {&value, &value};

.. option:: AllowedTypes

A semicolon-separated list of names of types that will be excluded from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// RUN: misc-const-correctness.AllowedTypes: '[Pp]ointer$;[Pp]tr$;[Rr]ef(erence)?$;qualified::Type;::fully::QualifiedType;ConstTemplate', \
// RUN: misc-const-correctness.TransformPointersAsValues: true, \
// RUN: misc-const-correctness.TransformReferences: true, \
// RUN: misc-const-correctness.WarnPointersAsValues: true } \
// RUN: misc-const-correctness.WarnPointersAsValues: true, \
// RUN: misc-const-correctness.WarnPointersAsPointers: false } \
// RUN: }" -- -fno-delayed-template-parsing

struct SmartPointer {
Expand Down
Loading