forked from OSchip/llvm-project
[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:
parent
8f24a56a3a
commit
46ae26e7eb
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>(
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>`_,
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
}
|
|
@ -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>();
|
||||
}
|
|
@ -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];
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue