[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
This commit is contained in:
Jonas Toth 2022-07-24 19:35:52 +02:00
parent 8f24a56a3a
commit 46ae26e7eb
17 changed files with 1777 additions and 10 deletions

View file

@ -22,6 +22,7 @@ add_custom_command(
add_custom_target(genconfusable DEPENDS Confusables.inc)
add_clang_library(clangTidyMiscModule
ConstCorrectnessCheck.cpp
DefinitionsInHeadersCheck.cpp
ConfusableIdentifierCheck.cpp
MiscTidyModule.cpp
@ -42,6 +43,7 @@ add_clang_library(clangTidyMiscModule
UnusedUsingDeclsCheck.cpp
LINK_LIBS
clangAnalysis
clangTidy
clangTidyUtils

View file

@ -0,0 +1,208 @@
//===--- ConstCorrectnessCheck.cpp - clang-tidy -----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ConstCorrectnessCheck.h"
#include "../utils/FixItHintUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include <iostream>
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace misc {
namespace {
// FIXME: This matcher exists in some other code-review as well.
// It should probably move to ASTMatchers.
AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); }
AST_MATCHER_P(DeclStmt, containsAnyDeclaration,
ast_matchers::internal::Matcher<Decl>, InnerMatcher) {
return ast_matchers::internal::matchesFirstInPointerRange(
InnerMatcher, Node.decl_begin(), Node.decl_end(), Finder,
Builder) != Node.decl_end();
}
AST_MATCHER(ReferenceType, isSpelledAsLValue) {
return Node.isSpelledAsLValue();
}
AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
} // namespace
ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AnalyzeValues(Options.get("AnalyzeValues", true)),
AnalyzeReferences(Options.get("AnalyzeReferences", true)),
WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
TransformValues(Options.get("TransformValues", true)),
TransformReferences(Options.get("TransformReferences", true)),
TransformPointersAsValues(
Options.get("TransformPointersAsValues", false)) {
if (AnalyzeValues == false && AnalyzeReferences == false)
this->configurationDiag(
"The check 'misc-const-correctness' will not "
"perform any analysis because both 'AnalyzeValues' and "
"'AnalyzeReferences' are false.");
}
void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
Options.store(Opts, "TransformValues", TransformValues);
Options.store(Opts, "TransformReferences", TransformReferences);
Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
}
void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
const auto ConstType = hasType(isConstQualified());
const auto ConstReference = hasType(references(isConstQualified()));
const auto RValueReference = hasType(
referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
const auto TemplateType = anyOf(
hasType(hasCanonicalType(templateTypeParmType())),
hasType(substTemplateTypeParmType()), hasType(isDependentType()),
// References to template types, their substitutions or typedefs to
// template types need to be considered as well.
hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))),
hasType(referenceType(pointee(substTemplateTypeParmType()))));
const auto AutoTemplateType = varDecl(
anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
hasType(pointerType(pointee(autoType())))));
const auto FunctionPointerRef =
hasType(hasCanonicalType(referenceType(pointee(functionType()))));
// Match local variables which could be 'const' if not modified later.
// Example: `int i = 10` would match `int i`.
const auto LocalValDecl = varDecl(
allOf(isLocal(), hasInitializer(anything()),
unless(anyOf(ConstType, ConstReference, TemplateType,
hasInitializer(isInstantiationDependent()),
AutoTemplateType, RValueReference, FunctionPointerRef,
hasType(cxxRecordDecl(isLambda())), isImplicit()))));
// Match the function scope for which the analysis of all local variables
// shall be run.
const auto FunctionScope =
functionDecl(
hasBody(
compoundStmt(forEachDescendant(
declStmt(containsAnyDeclaration(
LocalValDecl.bind("local-value")),
unless(has(decompositionDecl())))
.bind("decl-stmt")))
.bind("scope")))
.bind("function-decl");
Finder->addMatcher(FunctionScope, this);
}
/// Classify for a variable in what the Const-Check is interested.
enum class VariableCategory { Value, Reference, Pointer };
void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
const auto *LocalScope = Result.Nodes.getNodeAs<CompoundStmt>("scope");
const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("local-value");
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
/// 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
/// shall only deduplicate warnings for variables that are not instantiation
/// dependent. Variables like 'int x = 42;' in a template that can become
/// const emit multiple warnings otherwise.
bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
if (IsNormalVariableInTemplate &&
TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
return;
VariableCategory VC = VariableCategory::Value;
if (Variable->getType()->isReferenceType())
VC = VariableCategory::Reference;
if (Variable->getType()->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());
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.
if (VarDeclStmt == nullptr || !VarDeclStmt->isSingleDecl())
return;
using namespace utils::fixit;
if (VC == VariableCategory::Value && TransformValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
DeclSpec::TQ_const, QualifierTarget::Value,
QualifierPolicy::Right);
// FIXME: Add '{}' for default initialization if no user-defined default
// constructor exists and there is no initializer.
return;
}
if (VC == VariableCategory::Reference && TransformReferences) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
DeclSpec::TQ_const, QualifierTarget::Value,
QualifierPolicy::Right);
return;
}
if (VC == VariableCategory::Pointer) {
if (WarnPointersAsValues && TransformPointersAsValues) {
Diag << addQualifierToVarDecl(*Variable, *Result.Context,
DeclSpec::TQ_const, QualifierTarget::Value,
QualifierPolicy::Right);
}
return;
}
}
void ConstCorrectnessCheck::registerScope(const CompoundStmt *LocalScope,
ASTContext *Context) {
auto &Analyzer = ScopesCache[LocalScope];
if (!Analyzer)
Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
}
} // namespace misc
} // namespace tidy
} // namespace clang

View file

@ -0,0 +1,57 @@
//===--- ConstCorrectnessCheck.h - clang-tidy -------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CONSTCORRECTNESSCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CONSTCORRECTNESSCHECK_H
#include "../ClangTidyCheck.h"
#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
#include "llvm/ADT/DenseSet.h"
namespace clang {
namespace tidy {
namespace misc {
/// This check warns on variables which could be declared const but are not.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/misc-const-correctness.html
class ConstCorrectnessCheck : public ClangTidyCheck {
public:
ConstCorrectnessCheck(StringRef Name, ClangTidyContext *Context);
// The rules for C and 'const' are different and incompatible for this check.
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
void registerScope(const CompoundStmt *LocalScope, ASTContext *Context);
using MutationAnalyzer = std::unique_ptr<ExprMutationAnalyzer>;
llvm::DenseMap<const CompoundStmt *, MutationAnalyzer> ScopesCache;
llvm::DenseSet<SourceLocation> TemplateDiagnosticsCache;
const bool AnalyzeValues;
const bool AnalyzeReferences;
const bool WarnPointersAsValues;
const bool TransformValues;
const bool TransformReferences;
const bool TransformPointersAsValues;
};
} // namespace misc
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_CONSTCORRECTNESSCHECK_H

View file

@ -10,6 +10,7 @@
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "ConfusableIdentifierCheck.h"
#include "ConstCorrectnessCheck.h"
#include "DefinitionsInHeadersCheck.h"
#include "MisleadingBidirectional.h"
#include "MisleadingIdentifier.h"
@ -36,6 +37,8 @@ public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<ConfusableIdentifierCheck>(
"misc-confusable-identifiers");
CheckFactories.registerCheck<ConstCorrectnessCheck>(
"misc-const-correctness");
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
"misc-definitions-in-headers");
CheckFactories.registerCheck<MisleadingBidirectionalCheck>(

View file

@ -137,6 +137,11 @@ New checks
Warns when there is an assignment within an if statement condition expression.
- New :doc:`misc-const-correctness
<clang-tidy/checks/misc/const-correctness>` check.
Detects unmodified local variables and suggest adding ``const`` if the transformation is possible.
- New :doc:`modernize-macro-to-enum
<clang-tidy/checks/modernize/macro-to-enum>` check.

View file

@ -239,6 +239,7 @@ Clang-Tidy Checks
`llvmlibc-implementation-in-namespace <llvmlibc/implementation-in-namespace.html>`_,
`llvmlibc-restrict-system-libc-headers <llvmlibc/restrict-system-libc-headers.html>`_, "Yes"
`misc-confusable-identifiers <misc/confusable-identifiers.html>`_,
`misc-const-correctness <misc/const-correctness.html>`_, "Yes"
`misc-definitions-in-headers <misc/definitions-in-headers.html>`_, "Yes"
`misc-misleading-bidirectional <misc/misleading-bidirectional.html>`_,
`misc-misleading-identifier <misc/misleading-identifier.html>`_,

View file

@ -0,0 +1,150 @@
.. title:: clang-tidy - misc-const-correctness
misc-const-correctness
======================
This check implements detection of local variables which could be declared as
``const``, but are not. Declaring variables as ``const`` is required or recommended by many
coding guidelines, such as:
`CppCoreGuidelines ES.25 <https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es25-declare-an-object-const-or-constexpr-unless-you-want-to-modify-its-value-later-on>`_
and `AUTOSAR C++14 Rule A7-1-1 (6.7.1 Specifiers) <https://www.autosar.org/fileadmin/user_upload/standards/adaptive/17-03/AUTOSAR_RS_CPP14Guidelines.pdf>`_.
Please note that this analysis is type-based only. Variables that are not modified
but used to create a non-const handle that might escape the scope are not diagnosed
as potential ``const``.
.. code-block:: c++
// Declare a variable, which is not ``const`` ...
int i = 42;
// but use it as read-only. This means that `i` can be declared ``const``.
int result = i * i;
The check can analyzes values, pointers and references but not (yet) pointees:
.. code-block:: c++
// Normal values like built-ins or objects.
int potential_const_int = 42; // 'const int potential_const_int = 42' suggestion.
int copy_of_value = potential_const_int;
MyClass could_be_const; // 'const MyClass could_be_const' suggestion;
could_be_const.const_qualified_method();
// References can be declared const as well.
int &reference_value = potential_const_int; // 'const int &reference_value' suggestion.
int another_copy = reference_value;
// The similar semantics of pointers are not (yet) analyzed.
int *pointer_variable = &potential_const_int; // Not 'const int *pointer_variable' suggestion.
int last_copy = *pointer_variable;
The automatic code transformation is only applied to variables that are declared in single
declarations. You may want to prepare your code base with
`readability-isolate-declaration <readability-isolate-declaration.html>`_ first.
Note that there is the check
`cppcoreguidelines-avoid-non-const-global-variables <cppcoreguidelines-avoid-non-const-global-variables.html>`_
to enforce ``const`` correctness on all globals.
Known Limitations
-----------------
The check will not analyze templated variables or variables that are instantiation dependent.
Different instantiations can result in different ``const`` correctness properties and in general it
is not possible to find all instantiations of a template. It might be used differently in an
independent translation unit.
Pointees can not be analyzed for constness yet. The following code is 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
-------
.. option:: AnalyzeValues (default = 1)
Enable or disable the analysis of ordinary value variables, like ``int i = 42;``
.. option:: AnalyzeReferences (default = 1)
Enable or disable the analysis of reference variables, like ``int &ref = i;``
.. option:: WarnPointersAsValues (default = 0)
This option enables the suggestion for ``const`` of the pointer itself.
Pointer values have two possibilities to be ``const``, the pointer
and the value pointing to.
.. code-block:: c++
const int value = 42;
const int * const pointer_variable = &value;
// The following operations are forbidden for `pointer_variable`.
// *pointer_variable = 44;
// pointer_variable = nullptr;
.. option:: TransformValues (default = 1)
Provides fixit-hints for value types that automatically adds ``const`` if its a single declaration.
.. code-block:: c++
// Emits a hint for 'value' to become 'const int value = 42;'.
int value = 42;
// Result is modified later in its life-time. No diagnostic and fixit hint will be emitted.
int result = value * 3;
result -= 10;
.. option:: TransformReferences (default = 1)
Provides fixit-hints for reference types that automatically adds ``const`` if its a single
declaration.
.. code-block:: c++
// This variable could still be a constant. But because there is a non-const reference to
// it, it can not be transformed (yet).
int value = 42;
// The reference 'ref_value' is not modified and can be made 'const int &ref_value = value;'
int &ref_value = value;
// Result is modified later in its life-time. No diagnostic and fixit hint will be emitted.
int result = ref_value * 3;
result -= 10;
.. option:: TransformPointersAsValues (default = 0)
Provides fixit-hints for pointers if their pointee is not changed. This does not analyze if the
value-pointed-to is unchanged!
Requires 'WarnPointersAsValues' to be 1.
.. code-block:: c++
int value = 42;
// Emits a hint that 'ptr_value' may become 'int *const ptr_value = &value' because its pointee
// is not changed.
int *ptr_value = &value;
int result = 100 * (*ptr_value);
// This modification of the pointee is still allowed and not analyzed/diagnosed.
*ptr_value = 0;
// The following pointer may not become a 'int *const'.
int *changing_pointee = &value;
changing_pointee = &result;

View file

@ -0,0 +1,55 @@
// RUN: %check_clang_tidy %s misc-const-correctness %t -- -- -std=c++17 -fno-delayed-template-parsing
template <typename L, typename R>
struct MyPair {
L left;
R right;
MyPair(const L &ll, const R &rr) : left{ll}, right{rr} {}
};
void f() {
// FIXME: Decomposition Decls need special treatment, because they require to use 'auto'
// and the 'const' should only be added if all elements can be const.
// The issue is similar to multiple declarations in one statement.
// Simply bail for now.
auto [np_local0, np_local1] = MyPair<int, int>(42, 42);
np_local0++;
np_local1++;
// CHECK-FIXES-NOT: auto const [np_local0, np_local1]
auto [np_local2, p_local0] = MyPair<double, double>(42., 42.);
np_local2++;
// CHECK-FIXES-NOT: auto const [np_local2, p_local0]
auto [p_local1, np_local3] = MyPair<double, double>(42., 42.);
np_local3++;
// CHECK-FIXES-NOT: auto const [p_local1, np_local3]
auto [p_local2, p_local3] = MyPair<double, double>(42., 42.);
// CHECK-FIXES-NOT: auto const [p_local2, p_local3]
}
void g() {
int p_local0 = 42;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const'
// CHECK-FIXES: int const p_local0 = 42;
}
template <typename SomeValue>
struct DoGooder {
DoGooder(void *accessor, SomeValue value) {
}
};
struct Bingus {
static constexpr auto someRandomConstant = 99;
};
template <typename Foo>
struct HardWorker {
HardWorker() {
const DoGooder<int> anInstanceOf(nullptr, Foo::someRandomConstant);
}
};
struct TheContainer {
HardWorker<Bingus> m_theOtherInstance;
// CHECK-FIXES-NOT: HardWorker<Bingus> const m_theOtherInstance
};

View file

@ -0,0 +1,13 @@
// RUN: %check_clang_tidy %s misc-const-correctness %t \
// RUN: -config='{CheckOptions: \
// RUN: [{key: "misc-const-correctness.AnalyzeValues", value: true},\
// RUN: {key: "misc-const-correctness.WarnPointersAsValues", value: true},\
// RUN: {key: "misc-const-correctness.TransformPointersAsValues", value: true}]}' \
// RUN: -- -fno-delayed-template-parsing
void potential_const_pointer() {
double np_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
double *p_local0 = &np_local0[1];
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double *' can be declared 'const'
// CHECK-FIXES: double *const p_local0
}

View file

@ -0,0 +1,22 @@
// RUN: %check_clang_tidy %s misc-const-correctness %t -- \
// RUN: -config="{CheckOptions: [\
// RUN: {key: 'misc-const-correctness.TransformValues', value: true}, \
// RUN: {key: 'misc-const-correctness.TransformReferences', value: true}, \
// RUN: {key: 'misc-const-correctness.WarnPointersAsValues', value: false}, \
// RUN: {key: 'misc-const-correctness.TransformPointersAsValues', value: false}, \
// RUN: ]}" -- -fno-delayed-template-parsing
template <typename T>
void type_dependent_variables() {
T value = 42;
auto &ref = value;
T &templateRef = value;
int value_int = 42;
// CHECK-MESSAGES:[[@LINE-1]]:3: warning: variable 'value_int' of type 'int' can be declared 'const'
// CHECK-FIXES: int const value_int
}
void instantiate_template_cases() {
type_dependent_variables<int>();
type_dependent_variables<float>();
}

View file

@ -0,0 +1,13 @@
// RUN: %check_clang_tidy %s misc-const-correctness %t \
// RUN: -config='{CheckOptions: \
// RUN: [{key: "misc-const-correctness.AnalyzeValues", value: true},\
// RUN: {key: "misc-const-correctness.WarnPointersAsValues", value: true}, \
// RUN: {key: "misc-const-correctness.TransformPointersAsValues", value: true},\
// RUN: ]}' -- -fno-delayed-template-parsing
void potential_const_pointer() {
double np_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
double *p_local0 = &np_local0[1];
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double *' can be declared 'const'
// CHECK-FIXES: double *const p_local0 = &np_local0[1];
}

View file

@ -0,0 +1,175 @@
// RUN: %check_clang_tidy %s misc-const-correctness %t -- \
// RUN: -config="{CheckOptions: [\
// RUN: {key: 'misc-const-correctness.TransformValues', value: true},\
// RUN: {key: 'misc-const-correctness.WarnPointersAsValues', value: false}, \
// RUN: {key: 'misc-const-correctness.TransformPointersAsValues', value: false}, \
// RUN: ]}" -- -fno-delayed-template-parsing
bool global;
char np_global = 0; // globals can't be known to be const
namespace foo {
int scoped;
float np_scoped = 1; // namespace variables are like globals
} // namespace foo
// Lambdas should be ignored, because they do not follow the normal variable
// semantic (e.g. the type is only known to the compiler).
void lambdas() {
auto Lambda = [](int i) { return i < 0; };
}
void some_function(double, wchar_t);
void some_function(double np_arg0, wchar_t np_arg1) {
int p_local0 = 2;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const'
// CHECK-FIXES: int const p_local0 = 2;
}
void nested_scopes() {
{
int p_local1 = 42;
// CHECK-MESSAGES: [[@LINE-1]]:5: warning: variable 'p_local1' of type 'int' can be declared 'const'
// CHECK-FIXES: int const p_local1 = 42;
}
}
template <typename T>
void define_locals(T np_arg0, T &np_arg1, int np_arg2) {
T np_local0 = 0;
int p_local1 = 42;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared 'const'
// CHECK-FIXES: int const p_local1 = 42;
}
void template_instantiation() {
const int np_local0 = 42;
int np_local1 = 42;
define_locals(np_local0, np_local1, np_local0);
define_locals(np_local1, np_local1, np_local1);
}
struct ConstNonConstClass {
ConstNonConstClass();
ConstNonConstClass(double &np_local0);
double nonConstMethod() {}
double constMethod() const {}
double modifyingMethod(double &np_arg0) const;
double NonConstMember;
const double ConstMember;
double &NonConstMemberRef;
const double &ConstMemberRef;
double *NonConstMemberPtr;
const double *ConstMemberPtr;
};
void direct_class_access() {
ConstNonConstClass p_local0;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass' can be declared 'const'
// CHECK-FIXES: ConstNonConstClass const p_local0;
p_local0.constMethod();
}
void class_access_array() {
ConstNonConstClass p_local0[2];
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass[2]' can be declared 'const'
// CHECK-FIXES: ConstNonConstClass const p_local0[2];
p_local0[0].constMethod();
}
struct MyVector {
double *begin();
const double *begin() const;
double *end();
const double *end() const;
double &operator[](int index);
double operator[](int index) const;
double values[100];
};
void vector_usage() {
double p_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double[10]' can be declared 'const'
// CHECK-FIXES: double const p_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
}
void range_for() {
int np_local0[2] = {1, 2};
// The transformation is not possible because the range-for-loop mutates the array content.
int *const np_local1[2] = {&np_local0[0], &np_local0[1]};
for (int *non_const_ptr : np_local1) {
*non_const_ptr = 45;
}
int *np_local2[2] = {&np_local0[0], &np_local0[1]};
for (int *non_const_ptr : np_local2) {
*non_const_ptr = 45;
}
}
void decltype_declaration() {
decltype(sizeof(void *)) p_local0 = 42;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'decltype(sizeof(void *))'
// CHECK-FIXES: decltype(sizeof(void *)) const p_local0 = 42;
}
// Taken from libcxx/include/type_traits and improved readability.
template <class Tp, Tp v>
struct integral_constant {
static constexpr const Tp value = v;
using value_type = Tp;
using type = integral_constant;
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; }
};
template <typename T>
struct is_integral : integral_constant<bool, false> {};
template <>
struct is_integral<int> : integral_constant<bool, true> {};
template <typename T>
struct not_integral : integral_constant<bool, false> {};
template <>
struct not_integral<double> : integral_constant<bool, true> {};
template <bool, typename Tp = void>
struct enable_if {};
template <typename Tp>
struct enable_if<true, Tp> { using type = Tp; };
template <typename T>
struct TMPClass {
T alwaysConst() const { return T{}; }
template <typename T2 = T, typename = typename enable_if<is_integral<T2>::value>::type>
T sometimesConst() const { return T{}; }
template <typename T2 = T, typename = typename enable_if<not_integral<T2>::value>::type>
T sometimesConst() { return T{}; }
};
void meta_type() {
TMPClass<int> p_local0;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'TMPClass<int>' can be declared 'const'
// CHECK-FIXES: TMPClass<int> const p_local0;
p_local0.alwaysConst();
p_local0.sometimesConst();
TMPClass<double> p_local1;
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'TMPClass<double>' can be declared 'const'
// CHECK-FIXES: TMPClass<double> const p_local1;
p_local1.alwaysConst();
TMPClass<double> p_local2; // Don't attempt to make this const
p_local2.sometimesConst();
}

View file

@ -0,0 +1,19 @@
// RUN: %check_clang_tidy %s misc-const-correctness %t -- \
// RUN: -config="{CheckOptions: [\
// RUN: {key: 'misc-const-correctness.TransformValues', value: true}, \
// RUN: {key: 'misc-const-correctness.WarnPointersAsValues', value: false}, \
// RUN: {key: 'misc-const-correctness.TransformPointersAsValues', value: false}, \
// RUN: ]}" -- -fno-delayed-template-parsing -fms-extensions
struct S {};
void f(__unaligned S *);
void scope() {
// FIXME: This is a bug in the analysis, that is confused by '__unaligned'.
// https://bugs.llvm.org/show_bug.cgi?id=51756
S s;
// CHECK-MESSAGES:[[@LINE-1]]:3: warning: variable 's' of type 'S' can be declared 'const'
// CHECK-FIXES: S const s;
f(&s);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
// RUN: %check_clang_tidy %s misc-const-correctness %t \
// RUN: -config='{CheckOptions: \
// RUN: [{key: "misc-const-correctness.AnalyzeValues", value: false},\
// RUN: {key: "misc-const-correctness.AnalyzeReferences", value: false},\
// RUN: ]}' -- -fno-delayed-template-parsing
// CHECK-MESSAGES: warning: The check 'misc-const-correctness' will not perform any analysis because both 'AnalyzeValues' and 'AnalyzeReferences' are false. [clang-tidy-config]
void g() {
int p_local0 = 42;
// CHECK-FIXES-NOT: int const p_local0 = 42;
}

View file

@ -455,14 +455,16 @@ const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) {
// array is considered modified if the loop-variable is a non-const reference.
const auto DeclStmtToNonRefToArray = declStmt(hasSingleDecl(varDecl(hasType(
hasUnqualifiedDesugaredType(referenceType(pointee(arrayType())))))));
const auto RefToArrayRefToElements = match(
findAll(stmt(cxxForRangeStmt(
hasLoopVariable(varDecl(hasType(nonConstReferenceType()))
.bind(NodeID<Decl>::value)),
hasRangeStmt(DeclStmtToNonRefToArray),
hasRangeInit(canResolveToExpr(equalsNode(Exp)))))
.bind("stmt")),
Stm, Context);
const auto RefToArrayRefToElements =
match(findAll(stmt(cxxForRangeStmt(
hasLoopVariable(
varDecl(anyOf(hasType(nonConstReferenceType()),
hasType(nonConstPointerType())))
.bind(NodeID<Decl>::value)),
hasRangeStmt(DeclStmtToNonRefToArray),
hasRangeInit(canResolveToExpr(equalsNode(Exp)))))
.bind("stmt")),
Stm, Context);
if (const auto *BadRangeInitFromArray =
selectFirst<Stmt>("stmt", RefToArrayRefToElements))

View file

@ -1251,13 +1251,13 @@ TEST(ExprMutationAnalyzerTest, RangeForArrayByValue) {
AST =
buildASTFromCode("void f() { int* x[2]; for (int* e : x) e = nullptr; }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_FALSE(isMutated(Results, AST.get()));
EXPECT_TRUE(isMutated(Results, AST.get()));
AST = buildASTFromCode(
"typedef int* IntPtr;"
"void f() { int* x[2]; for (IntPtr e : x) e = nullptr; }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_FALSE(isMutated(Results, AST.get()));
EXPECT_TRUE(isMutated(Results, AST.get()));
}
TEST(ExprMutationAnalyzerTest, RangeForArrayByConstRef) {