JonasToth updated this revision to Diff 144458.
JonasToth added a comment.
- [Feature] use new statefull const checker
- [Test] add more tests finding false positives
Repository:
rCTE Clang Tools Extra
https://reviews.llvm.org/D45444
Files:
clang-tidy/cppcoreguidelines/CMakeLists.txt
clang-tidy/cppcoreguidelines/ConstCheck.cpp
clang-tidy/cppcoreguidelines/ConstCheck.h
clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
clang-tidy/utils/CMakeLists.txt
clang-tidy/utils/ExprMutationAnalyzer.cpp
clang-tidy/utils/ExprMutationAnalyzer.h
docs/ReleaseNotes.rst
docs/clang-tidy/checks/cppcoreguidelines-const.rst
docs/clang-tidy/checks/list.rst
test/clang-tidy/cppcoreguidelines-const-pointers-as-values.cpp
test/clang-tidy/cppcoreguidelines-const-values.cpp
unittests/clang-tidy/CMakeLists.txt
unittests/clang-tidy/ExprMutationAnalyzerTest.cpp
Index: unittests/clang-tidy/ExprMutationAnalyzerTest.cpp
===================================================================
--- /dev/null
+++ unittests/clang-tidy/ExprMutationAnalyzerTest.cpp
@@ -0,0 +1,528 @@
+//===---------- ExprMutationAnalyzerTest.cpp - clang-tidy -----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../clang-tidy/utils/ExprMutationAnalyzer.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Tooling.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+using namespace clang::ast_matchers;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::ResultOf;
+using ::testing::StartsWith;
+using ::testing::Values;
+
+namespace {
+
+using ExprMatcher = internal::Matcher<Expr>;
+using StmtMatcher = internal::Matcher<Stmt>;
+
+ExprMatcher declRefTo(StringRef Name) {
+ return declRefExpr(to(namedDecl(hasName(Name))));
+}
+
+StmtMatcher withEnclosingCompound(ExprMatcher Matcher) {
+ return expr(Matcher, hasAncestor(compoundStmt().bind("stmt"))).bind("expr");
+}
+
+bool isMutated(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST) {
+ const auto *const S = selectFirst<Stmt>("stmt", Results);
+ const auto *const E = selectFirst<Expr>("expr", Results);
+ return utils::ExprMutationAnalyzer(S, &AST->getASTContext()).isMutated(E);
+}
+
+SmallVector<std::string, 1>
+mutatedBy(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST) {
+ const auto *const S = selectFirst<Stmt>("stmt", Results);
+ SmallVector<std::string, 1> Chain;
+ utils::ExprMutationAnalyzer Analyzer(S, &AST->getASTContext());
+ for (const auto *E = selectFirst<Expr>("expr", Results); E != nullptr;) {
+ const Stmt *By = Analyzer.findMutation(E);
+ std::string buffer;
+ llvm::raw_string_ostream stream(buffer);
+ By->printPretty(stream, nullptr, AST->getASTContext().getPrintingPolicy());
+ Chain.push_back(StringRef(stream.str()).trim().str());
+ E = dyn_cast<DeclRefExpr>(By);
+ }
+ return Chain;
+}
+
+std::string removeSpace(std::string s) {
+ s.erase(std::remove_if(s.begin(), s.end(),
+ [](char c) { return std::isspace(c); }),
+ s.end());
+ return s;
+}
+
+} // namespace
+
+TEST(ExprMutationAnalyzerTest, Trivial) {
+ const auto AST = tooling::buildASTFromCode("void f() { int x; x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+class AssignmentTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(AssignmentTest, AssignmentModifies) {
+ const std::string ModExpr = "x " + GetParam() + " 10";
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr));
+}
+
+INSTANTIATE_TEST_CASE_P(AllAssignmentOperators, AssignmentTest,
+ Values("=", "+=", "-=", "*=", "/=", "%=", "&=", "|=",
+ "^=", "<<=", ">>="), );
+
+class IncDecTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(IncDecTest, IncDecModifies) {
+ const std::string ModExpr = GetParam();
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr));
+}
+
+INSTANTIATE_TEST_CASE_P(AllIncDecOperators, IncDecTest,
+ Values("++x", "--x", "x++", "x--"), );
+
+TEST(ExprMutationAnalyzerTest, NonConstMemberFunc) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { void mf(); }; Foo x; x.mf(); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf()"));
+}
+
+TEST(ExprMutationAnalyzerTest, ConstMemberFunc) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { void mf() const; }; Foo x; x.mf(); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NonConstOperator) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { Foo& operator=(int); }; Foo x; x = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, ConstOperator) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct Foo { int operator()() const; }; Foo x; x(); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByValueArgument) {
+ auto AST =
+ tooling::buildASTFromCode("void g(int); void f() { int x; g(x); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(A, int); A x; x + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(int); }; int x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByNonConstRefArgument) {
+ auto AST =
+ tooling::buildASTFromCode("void g(int&); void f() { int x; g(x); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)"));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(A&, int); A x; x + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x + 1"));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(int&); }; int x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+}
+
+TEST(ExprMutationAnalyzerTest, ByConstRefArgument) {
+ auto AST = tooling::buildASTFromCode(
+ "void g(const int&); void f() { int x; g(x); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(const A&, int); A x; x + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A { A(const int&); }; int x; A y(x); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByNonConstRRefArgument) {
+ auto AST = tooling::buildASTFromCode(
+ "void g(int&&); void f() { int x; g(static_cast<int &&>(x)); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("g(static_cast<int &&>(x))"));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(A&&, int); "
+ "A x; static_cast<A &&>(x) + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("static_cast<A &&>(x) + 1"));
+
+ AST = tooling::buildASTFromCode("void f() { struct A { A(int&&); }; "
+ "int x; A y(static_cast<int &&>(x)); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("static_cast<int &&>(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, ByConstRRefArgument) {
+ auto AST = tooling::buildASTFromCode(
+ "void g(const int&&); void f() { int x; g(static_cast<int&&>(x)); }");
+ auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode(
+ "void f() { struct A {}; A operator+(const A&&, int); "
+ "A x; static_cast<A&&>(x) + 1; }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+
+ AST = tooling::buildASTFromCode("void f() { struct A { A(const int&&); }; "
+ "int x; A y(static_cast<int&&>(x)); }");
+ Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, Move) {
+ // Technically almost the same as ByNonConstRRefArgument, just double checking
+ const auto AST = tooling::buildASTFromCode(
+ "namespace std {"
+ "template<class T> struct remove_reference { typedef T type; };"
+ "template<class T> struct remove_reference<T&> { typedef T type; };"
+ "template<class T> struct remove_reference<T&&> { typedef T type; };"
+ "template<class T> typename std::remove_reference<T>::type&& "
+ "move(T&& t) noexcept; }"
+ "void f() { struct A {}; A x; std::move(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("std::move(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, Forward) {
+ // Technically almost the same as ByNonConstRefArgument, just double checking
+ const auto AST = tooling::buildASTFromCode(
+ "namespace std {"
+ "template<class T> struct remove_reference { typedef T type; };"
+ "template<class T> struct remove_reference<T&> { typedef T type; };"
+ "template<class T> struct remove_reference<T&&> { typedef T type; };"
+ "template<class T> T&& "
+ "forward(typename std::remove_reference<T>::type&) noexcept;"
+ "template<class T> T&& "
+ "forward(typename std::remove_reference<T>::type&&) noexcept;"
+ "void f() { struct A {}; A x; std::forward<A &>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("std::forward<A &>(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsValue) {
+ const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRef) {
+ const auto AST = tooling::buildASTFromCode("int& f() { int x; return x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("return x;"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsConstRef) {
+ const auto AST =
+ tooling::buildASTFromCode("const int& f() { int x; return x; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "int&& f() { int x; return static_cast<int &&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("return static_cast<int &&>(x);"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsConstRRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "const int&& f() { int x; return static_cast<int&&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, TakeAddress) {
+ const auto AST =
+ tooling::buildASTFromCode("void g(int*); void f() { int x; g(&x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("&x"));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayToPointerDecay) {
+ const auto AST =
+ tooling::buildASTFromCode("void g(int*); void f() { int x[2]; g(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; "
+ "int& r3 = r2; r3 = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("r0", "r1", "r2", "r3", "r3 = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowRefNotModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; "
+ "int& r3 = r2; int& r4 = r3; int& r5 = r4;}");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowConditionalRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x, y; bool b; int &r = b ? x : y; r = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("r", "r = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowConditionalRefNotModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x, y; bool b; int& r = b ? x : y; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayElementModified) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x[2]; x[0] = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x[0] = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayElementNotModified) {
+ const auto AST = tooling::buildASTFromCode("void f() { int x[2]; x[0]; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NestedMemberModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct A { int vi; }; struct B { A va; }; "
+ "struct C { B vb; }; C x; x.vb.va.vi = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.vb.va.vi = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, NestedMemberNotModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { struct A { int vi; }; struct B { A va; }; "
+ "struct C { B vb; }; C x; x.vb.va.vi; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToValue) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; static_cast<double>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; static_cast<int &>(x) = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre("static_cast<int &>(x) = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToRefNotModified) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; static_cast<int&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToConstRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x; static_cast<const int&>(x); }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByValue) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [=]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByValue) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [x]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByRef) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [&]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre(ResultOf(removeSpace, "[&](){x=10;}")));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByRef) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x; [&x]() { x = 10; }; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()),
+ ElementsAre(ResultOf(removeSpace, "[&x](){x=10;}")));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByRefModified) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x[2]; for (int& e : x) e = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByRefNotModified) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x[2]; for (int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByValue) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x[2]; for (int e : x) e = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByConstRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "void f() { int x[2]; for (const int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefModified) {
+ const auto AST =
+ tooling::buildASTFromCode("struct V { int* begin(); int* end(); };"
+ "void f() { V x; for (int& e : x) e = 10; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefNotModified) {
+ const auto AST =
+ tooling::buildASTFromCode("struct V { int* begin(); int* end(); };"
+ "void f() { V x; for (int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByValue) {
+ const auto AST = tooling::buildASTFromCode(
+ "struct V { const int* begin() const; const int* end() const; };"
+ "void f() { V x; for (int e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByConstRef) {
+ const auto AST = tooling::buildASTFromCode(
+ "struct V { const int* begin() const; const int* end() const; };"
+ "void f() { V x; for (const int& e : x) e; }");
+ const auto Results =
+ match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+ EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+} // namespace test
+} // namespace tidy
+} // namespace clang
Index: unittests/clang-tidy/CMakeLists.txt
===================================================================
--- unittests/clang-tidy/CMakeLists.txt
+++ unittests/clang-tidy/CMakeLists.txt
@@ -9,6 +9,7 @@
add_extra_unittest(ClangTidyTests
ClangTidyDiagnosticConsumerTest.cpp
ClangTidyOptionsTest.cpp
+ ExprMutationAnalyzerTest.cpp
IncludeInserterTest.cpp
GoogleModuleTest.cpp
LLVMModuleTest.cpp
Index: test/clang-tidy/cppcoreguidelines-const-values.cpp
===================================================================
--- /dev/null
+++ test/clang-tidy/cppcoreguidelines-const-values.cpp
@@ -0,0 +1,518 @@
+// RUN: %check_clang_tidy %s cppcoreguidelines-const %t
+
+// ------- Provide test samples for primitive builtins ---------
+// - every 'p_*' variable is a 'potential_const_*' variable
+// - every 'np_*' variable is a 'non_potential_const_*' variable
+
+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
+
+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
+
+ int np_local0;
+ const int np_local1 = 42;
+
+ unsigned int np_local2 = 3;
+ np_local2 <<= 4;
+
+ int np_local3 = 4;
+ ++np_local3;
+ int np_local4 = 4;
+ np_local4++;
+
+ int np_local5 = 4;
+ --np_local5;
+ int np_local6 = 4;
+ np_local6--;
+}
+
+void nested_scopes() {
+ int p_local0 = 2;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared const
+ int np_local0 = 42;
+
+ {
+ int p_local1 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: variable 'p_local1' of type 'int' can be declared const
+ np_local0 *= 2;
+ }
+}
+
+void some_lambda_environment_capture_all_by_reference(double np_arg0) {
+ int np_local0 = 0;
+ int p_local0 = 1;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared const
+
+ int np_local2;
+ const int np_local3 = 2;
+
+ // Capturing all variables by reference prohibits making them const.
+ [&]() { ++np_local0; };
+
+ int p_local1 = 0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared const
+}
+
+void some_lambda_environment_capture_all_by_value(double np_arg0) {
+ int np_local0 = 0;
+ int p_local0 = 1;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared const
+
+ int np_local1;
+ const int np_local2 = 2;
+
+ // Capturing by value has no influence on them.
+ [=]() { (void)p_local0; };
+
+ np_local0 += 10;
+}
+
+void function_inout_pointer(int *inout);
+void function_in_pointer(const int *in);
+
+void some_pointer_taking(int *out) {
+ int p_local0 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared const
+ const int *const p0_p_local0 = &p_local0;
+ int *const p1_p_local0 = &p_local0;
+
+ // FIXME false positive
+ int np_local0 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local0' of type 'int' can be declared const
+ const int *const p0_np_local0 = &np_local0;
+ int *const p1_np_local0 = &np_local0;
+ *p1_np_local0 = 43;
+
+ int np_local1 = 42;
+ function_inout_pointer(&np_local1);
+
+ // Prevents const.
+ int np_local2 = 42;
+ out = &np_local2; // This returns and invalid address, its just about the AST
+
+ int p_local1 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared const
+ const int *const p0_p_local1 = &p_local1;
+
+ int p_local2 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'int' can be declared const
+ function_in_pointer(&p_local2);
+}
+
+void function_inout_ref(int &inout);
+void function_in_ref(const int &in);
+
+void some_reference_taking() {
+ // FIXME False positive
+ int np_local0 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local0' of type 'int' can be declared const
+ const int &r0_np_local0 = np_local0;
+ int &r1_np_local0 = np_local0;
+ r1_np_local0 = 43;
+ const int &r2_np_local0 = r1_np_local0;
+
+ int np_local1 = 42;
+ function_inout_ref(np_local1);
+
+ int p_local0 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared const
+ const int &r0_p_local0 = p_local0;
+
+ int p_local1 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared const
+ function_in_ref(p_local1);
+}
+
+double *non_const_pointer_return() {
+ double p_local0 = 0.0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared const
+ double np_local0 = 24.4;
+
+ return &np_local0;
+}
+
+const double *const_pointer_return() {
+ double p_local0 = 0.0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared const
+ double p_local1 = 24.4;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double' can be declared const
+ return &p_local1;
+}
+
+double &non_const_ref_return() {
+ double p_local0 = 0.0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared const
+ double np_local0 = 42.42;
+ return np_local0;
+}
+
+const double &const_ref_return() {
+ double p_local0 = 0.0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared const
+ double p_local1 = 24.4;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double' can be declared const
+ return p_local1;
+}
+
+double *&return_non_const_pointer_ref() {
+ double *np_local0 = nullptr;
+ return np_local0;
+}
+
+void overloaded_arguments(const int &in);
+void overloaded_arguments(int &inout);
+void overloaded_arguments(const int *in);
+void overloaded_arguments(int *inout);
+
+void function_calling() {
+ int np_local0 = 42;
+ overloaded_arguments(np_local0);
+
+ const int np_local1 = 42;
+ overloaded_arguments(np_local1);
+
+ int np_local2 = 42;
+ overloaded_arguments(&np_local2);
+
+ const int np_local3 = 42;
+ overloaded_arguments(&np_local3);
+}
+
+template <typename T>
+void define_locals(T np_arg0, T &np_arg1, int np_arg2) {
+ T np_local0 = 0;
+ np_local0 += np_arg0 * np_arg1;
+
+ T np_local1 = 42;
+ np_local0 += np_local1;
+
+ // Used as argument to an overloaded function with const and non-const.
+ T np_local2 = 42;
+ overloaded_arguments(np_local2);
+
+ int np_local4 = 42;
+ // non-template values are ok still.
+ int p_local0 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared const
+ np_local4 += p_local0;
+}
+
+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() {
+ // FIXME False positive
+ ConstNonConstClass np_local0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local0' of type 'ConstNonConstClass' can be declared const
+
+ np_local0.constMethod();
+ np_local0.nonConstMethod();
+
+ ConstNonConstClass p_local0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass' can be declared const
+ p_local0.constMethod();
+
+ ConstNonConstClass p_local1;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'ConstNonConstClass' can be declared const
+ double np_local1;
+ p_local1.modifyingMethod(np_local1);
+
+ double np_local2;
+ ConstNonConstClass p_local2(np_local2);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'ConstNonConstClass' can be declared const
+
+ ConstNonConstClass np_local3;
+ np_local3.NonConstMember = 42.;
+
+ ConstNonConstClass np_local4;
+ np_local4.NonConstMemberRef = 42.;
+
+ ConstNonConstClass p_local3;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'ConstNonConstClass' can be declared const
+ const double val0 = p_local3.NonConstMember;
+ const double val1 = p_local3.NonConstMemberRef;
+ const double val2 = *p_local3.NonConstMemberPtr;
+
+ ConstNonConstClass p_local4;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local4' of type 'ConstNonConstClass' can be declared const
+ *np_local4.NonConstMemberPtr = 42.;
+}
+
+void class_access_array() {
+ // FIXME array access was modifying
+ ConstNonConstClass np_local0[2];
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local0' of type 'ConstNonConstClass [2]' can be declared const
+ np_local0[0].constMethod();
+ np_local0[1].constMethod();
+ np_local0[1].nonConstMethod();
+
+ ConstNonConstClass p_local0[2];
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass [2]' can be declared const
+ p_local0[0].constMethod();
+ np_local0[1].constMethod();
+}
+
+struct OperatorsAsConstAsPossible {
+ OperatorsAsConstAsPossible &operator+=(const OperatorsAsConstAsPossible &rhs);
+ OperatorsAsConstAsPossible operator+(const OperatorsAsConstAsPossible &rhs) const;
+};
+
+struct NonConstOperators {
+};
+NonConstOperators operator+(NonConstOperators &lhs, NonConstOperators &rhs);
+NonConstOperators operator-(NonConstOperators lhs, NonConstOperators rhs);
+
+void internal_operator_calls() {
+ OperatorsAsConstAsPossible np_local0;
+ OperatorsAsConstAsPossible np_local1;
+ OperatorsAsConstAsPossible p_local0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'OperatorsAsConstAsPossible' can be declared const
+ OperatorsAsConstAsPossible p_local1;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'OperatorsAsConstAsPossible' can be declared const
+
+ np_local0 += p_local0;
+ np_local1 = p_local0 + p_local1;
+
+ NonConstOperators np_local2;
+ NonConstOperators np_local3;
+ NonConstOperators np_local4;
+
+ np_local2 = np_local3 + np_local4;
+
+ NonConstOperators p_local2;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'NonConstOperators' can be declared const
+ NonConstOperators p_local3 = p_local2 - p_local2;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'NonConstOperators' can be declared const
+}
+
+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 np_local0[10];
+ np_local0[5] = 42.;
+
+ MyVector np_local1;
+ np_local1[5] = 42.;
+
+ 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
+ double p_local1 = p_local0[5];
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double' can be declared const
+
+ // The following subscript calls suprisingly choose the non-const operator
+ // version.
+ MyVector np_local2;
+ double p_local2 = np_local2[42];
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'double' can be declared const
+
+ MyVector np_local3;
+ const double np_local4 = np_local3[42];
+
+ // This subscript results in const overloaded operator.
+ const MyVector np_local5{};
+ double p_local3 = np_local5[42];
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'double' can be declared const
+}
+
+void const_handle(const double &np_local0);
+void const_handle(const double *np_local0);
+
+void non_const_handle(double &np_local0);
+void non_const_handle(double *np_local0);
+
+void handle_from_array() {
+ // Non-const handle from non-const array forbids declaring the array as const
+ double np_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ double *p_local0 = &np_local0[1]; // Could be `double *const`, but warning deactivated by default
+
+ double np_local1[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ double &non_const_ref = np_local1[1];
+ non_const_ref = 42.;
+
+ double np_local2[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ double *np_local3;
+ np_local3 = &np_local2[5];
+
+ double np_local4[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ non_const_handle(np_local4[2]);
+ double np_local5[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ non_const_handle(&np_local5[2]);
+
+ // Constant handles are ok
+ double p_local1[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double [10]' can be declared const
+ const double *p_local2 = &p_local1[2]; // Could be `const double *const`, but warning deactivated by default
+
+ double p_local3[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'double [10]' can be declared const
+ const double &const_ref = p_local3[2];
+
+ double p_local4[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local4' of type 'double [10]' can be declared const
+ const double *const_ptr;
+ const_ptr = &p_local4[2];
+
+ double p_local5[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local5' of type 'double [10]' can be declared const
+ const_handle(p_local5[2]);
+ double p_local6[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local6' of type 'double [10]' can be declared const
+ const_handle(&p_local6[2]);
+}
+
+void range_for() {
+ int np_local0[2] = {1, 2};
+ for (int &non_const_ref : np_local0) {
+ non_const_ref = 42;
+ }
+
+ int np_local1[2] = {1, 2};
+ for (auto &non_const_ref : np_local1) {
+ non_const_ref = 43;
+ }
+
+ int np_local2[2] = {1, 2};
+ for (auto &&non_const_ref : np_local2) {
+ non_const_ref = 44;
+ }
+
+ // FIXME the warning message is suboptimal. It could be defined as
+ // `int *const np_local3[2]` because the pointers are not reseated.
+ // But this is not easily deducable from the warning.
+ int *np_local3[2] = {&np_local0[0], &np_local0[1]};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local3' of type 'int *[2]' can be declared const
+ for (int *non_const_ptr : np_local3) {
+ *non_const_ptr = 45;
+ }
+
+ // FIXME same as above, but silenced
+ int *const np_local4[2] = {&np_local0[0], &np_local0[1]};
+ for (auto *non_const_ptr : np_local4) {
+ *non_const_ptr = 46;
+ }
+
+ int p_local0[2] = {1, 2};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int [2]' can be declared const
+ for (int value : p_local0) {
+ // CHECK-MESSAGES: [[@LINE-1]]:8: warning: variable 'value' of type 'int' can be declared const
+ }
+
+ int p_local1[2] = {1, 2};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int [2]' can be declared const
+ for (const int &const_ref : p_local1) {
+ }
+
+ int *p_local2[2] = {&np_local0[0], &np_local0[1]};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'int *[2]' can be declared const
+ for (const int *con_ptr : p_local2) {
+ }
+
+ int *p_local3[2] = {nullptr, nullptr};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'int *[2]' can be declared const
+ for (const auto *con_ptr : p_local3) {
+ }
+}
+
+inline void *operator new(decltype(sizeof(void *)), void *p) { return p; }
+
+struct Value {
+};
+void placement_new() {
+ Value Mem;
+ Value *V = new (&Mem) Value;
+}
+
+struct ModifyingConversion {
+ operator int() { return 15; }
+};
+struct NonModifyingConversion {
+ operator int() const { return 15; }
+};
+void conversion_operators() {
+ ModifyingConversion np_local0;
+ NonModifyingConversion p_local0;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'NonModifyingConversion' can be declared const
+
+ int np_local1 = np_local0;
+ np_local1 = p_local0;
+}
+
+void casts() {
+ decltype(sizeof(void *)) p_local0 = 42;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'decltype(sizeof(void *))' (aka 'unsigned long') can be declared const
+ auto np_local0 = reinterpret_cast<void *>(p_local0);
+ np_local0 = nullptr;
+
+ int p_local1 = 43;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared const
+ short p_local2 = static_cast<short>(p_local1);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'short' can be declared const
+
+ int np_local1 = p_local2;
+ int &np_local2 = static_cast<int &>(np_local1);
+ np_local2 = 5;
+}
+
+void ternary_operator() {
+ int np_local0 = 1, np_local1 = 2;
+ int &np_local2 = true ? np_local0 : np_local1;
+ np_local2 = 2;
+
+ int p_local0 = 3, np_local3 = 5;
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared const
+ const int &np_local4 = true ? p_local0 : ++np_local3;
+
+ // FIXME false positive
+ int np_local5[3] = {1, 2, 3};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local5' of type 'int [3]' can be declared const [cppcoreguidelines-const]
+ int &np_local6 = np_local5[1] < np_local5[2] ? np_local5[0] : np_local5[2];
+ np_local6 = 42;
+
+ // FIXME false positive
+ int np_local7[3] = {1, 2, 3};
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local7' of type 'int [3]' can be declared const [cppcoreguidelines-const]
+ int *np_local8 = np_local7[1] < np_local7[2] ? &np_local7[0] : &np_local7[2];
+ *np_local8 = 42;
+}
Index: test/clang-tidy/cppcoreguidelines-const-pointers-as-values.cpp
===================================================================
--- /dev/null
+++ test/clang-tidy/cppcoreguidelines-const-pointers-as-values.cpp
@@ -0,0 +1,11 @@
+// RUN: %check_clang_tidy %s cppcoreguidelines-const %t \
+// RUN: -config='{CheckOptions: \
+// RUN: [{key: "cppcoreguidelines-const.AnalyzeValues", value: 1},\
+// RUN: {key: "cppcoreguidelines-const.WarnPointersAsValues", value: 1}]}' \
+// RUN: --
+
+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
+}
Index: docs/clang-tidy/checks/list.rst
===================================================================
--- docs/clang-tidy/checks/list.rst
+++ docs/clang-tidy/checks/list.rst
@@ -76,6 +76,7 @@
cert-oop11-cpp (redirects to performance-move-constructor-init) <cert-oop11-cpp>
cppcoreguidelines-avoid-goto
cppcoreguidelines-c-copy-assignment-signature (redirects to misc-unconventional-assign-operator) <cppcoreguidelines-c-copy-assignment-signature>
+ cppcoreguidelines-const
cppcoreguidelines-interfaces-global-init
cppcoreguidelines-no-malloc
cppcoreguidelines-owning-memory
Index: docs/clang-tidy/checks/cppcoreguidelines-const.rst
===================================================================
--- /dev/null
+++ docs/clang-tidy/checks/cppcoreguidelines-const.rst
@@ -0,0 +1,6 @@
+.. title:: clang-tidy - cppcoreguidelines-const
+
+cppcoreguidelines-const
+=======================
+
+FIXME: Describe what patterns does the check detect and why. Give examples.
Index: docs/ReleaseNotes.rst
===================================================================
--- docs/ReleaseNotes.rst
+++ docs/ReleaseNotes.rst
@@ -57,6 +57,11 @@
Improvements to clang-tidy
--------------------------
+- New :doc:`cppcoreguidelines-const
+ <clang-tidy/checks/cppcoreguidelines-const>` check
+
+ FIXME: add release notes.
+
- New module `abseil` for checks related to the `Abseil <https://abseil.io>`_
library.
Index: clang-tidy/utils/ExprMutationAnalyzer.h
===================================================================
--- /dev/null
+++ clang-tidy/utils/ExprMutationAnalyzer.h
@@ -0,0 +1,52 @@
+//===---------- ExprMutationAnalyzer.h - clang-tidy -----------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
+
+#include <type_traits>
+
+#include "clang/AST/AST.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/DenseMap.h"
+
+namespace clang {
+namespace tidy {
+namespace utils {
+
+/// Analyzes whether any mutative operations are applied to an expression within
+/// a given statement.
+class ExprMutationAnalyzer {
+public:
+ ExprMutationAnalyzer(const Stmt *Stm, ASTContext *Context)
+ : Stm(Stm), Context(Context) {}
+
+ bool isMutated(const Expr *Exp) { return findMutation(Exp) != nullptr; }
+ const Stmt *findMutation(const Expr *Exp);
+
+private:
+ const Stmt *findExprMutation(ArrayRef<ast_matchers::BoundNodes> Matches);
+ const Stmt *findDeclMutation(ArrayRef<ast_matchers::BoundNodes> Matches);
+
+ const Stmt *findDirectMutation(const Expr *Exp);
+ const Stmt *findMemberMutation(const Expr *Exp);
+ const Stmt *findArrayElementMutation(const Expr *Exp);
+ const Stmt *findCastMutation(const Expr *Exp);
+ const Stmt *findRangeLoopMutation(const Expr *Exp);
+ const Stmt *findReferenceMutation(const Expr *Exp);
+
+ const Stmt *const Stm;
+ ASTContext *const Context;
+ llvm::DenseMap<const Expr *, const Stmt *> Results;
+};
+
+} // namespace utils
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
Index: clang-tidy/utils/ExprMutationAnalyzer.cpp
===================================================================
--- /dev/null
+++ clang-tidy/utils/ExprMutationAnalyzer.cpp
@@ -0,0 +1,208 @@
+//===---------- ExprMutationAnalyzer.cpp - clang-tidy ---------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "ExprMutationAnalyzer.h"
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+namespace clang {
+namespace tidy {
+namespace utils {
+using namespace ast_matchers;
+
+namespace {
+
+AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) {
+ for (const auto *Init : Node.capture_inits()) {
+ if (Init == E)
+ return true;
+ }
+ return false;
+}
+
+AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,
+ ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) {
+ const DeclStmt *const Range = Node.getRangeStmt();
+ return InnerMatcher.matches(*Range, Finder, Builder);
+}
+
+const auto nonConstReferenceType = [] {
+ return referenceType(pointee(unless(isConstQualified())));
+};
+
+} // namespace
+
+const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) {
+ const auto Memoized = Results.find(Exp);
+ if (Memoized != Results.end()) {
+ return Memoized->second;
+ }
+
+ for (const auto &Finder : {&ExprMutationAnalyzer::findDirectMutation,
+ &ExprMutationAnalyzer::findMemberMutation,
+ &ExprMutationAnalyzer::findArrayElementMutation,
+ &ExprMutationAnalyzer::findCastMutation,
+ &ExprMutationAnalyzer::findRangeLoopMutation,
+ &ExprMutationAnalyzer::findReferenceMutation}) {
+ if (const Stmt *S = (this->*Finder)(Exp))
+ return Results[Exp] = S;
+ }
+
+ return Results[Exp] = nullptr;
+}
+
+const Stmt *
+ExprMutationAnalyzer::findExprMutation(ArrayRef<BoundNodes> Matches) {
+ for (const auto &Nodes : Matches) {
+ if (const Stmt *S = findMutation(Nodes.getNodeAs<Expr>("expr")))
+ return S;
+ }
+ return nullptr;
+}
+
+const Stmt *
+ExprMutationAnalyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) {
+ for (const auto &DeclNodes : Matches) {
+ const auto Refs = match(
+ findAll(declRefExpr(to(equalsNode(DeclNodes.getNodeAs<Decl>("decl"))))
+ .bind("expr")),
+ *Stm, *Context);
+ for (const auto &RefNodes : Refs) {
+ const auto *E = RefNodes.getNodeAs<Expr>("expr");
+ if (findMutation(E))
+ return E;
+ }
+ }
+ return nullptr;
+}
+
+const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
+ // LHS of any assignment operators.
+ const auto AsAssignmentLhs =
+ binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp)));
+
+ // Operand of increment/decrement operators.
+ const auto AsIncDecOperand =
+ unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),
+ hasUnaryOperand(equalsNode(Exp)));
+
+ // Invoking non-const member function.
+ const auto NonConstMethod = cxxMethodDecl(unless(isConst()));
+ const auto AsNonConstThis =
+ expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))),
+ cxxOperatorCallExpr(callee(NonConstMethod),
+ hasArgument(0, equalsNode(Exp)))));
+
+ // Taking address of 'Exp'.
+ // We're assuming 'Exp' is mutated as soon as its address is taken, though in
+ // theory we can follow the pointer and see whether it escaped `Stm` or is
+ // dereferenced and then mutated. This is left for future improvements.
+ const auto AsAmpersandOperand =
+ unaryOperator(hasOperatorName("&"),
+ // A NoOp implicit cast is adding const.
+ unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))),
+ hasUnaryOperand(equalsNode(Exp)));
+ const auto AsPointerFromArrayDecay =
+ castExpr(hasCastKind(CK_ArrayToPointerDecay),
+ unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp)));
+
+ // Used as non-const-ref argument when calling a function.
+ const auto NonConstRefParam = forEachArgumentWithParam(
+ equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType())));
+ const auto AsNonConstRefArg =
+ anyOf(callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam));
+
+ // Captured by a lambda by reference.
+ // If we're initializing a capture with 'Exp' directly then we're initializing
+ // a reference capture.
+ // For value captures there will be an ImplicitCastExpr <LValueToRValue>.
+ const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp));
+
+ // Returned as non-const-ref.
+ // If we're returning 'Exp' directly then it's returned as non-const-ref.
+ // For returning by value there will be an ImplicitCastExpr <LValueToRValue>.
+ // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for
+ // adding const.)
+ const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(Exp)));
+
+ const auto Matches =
+ match(findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,
+ AsAmpersandOperand, AsPointerFromArrayDecay,
+ AsNonConstRefArg, AsLambdaRefCaptureInit,
+ AsNonConstRefReturn))
+ .bind("stmt")),
+ *Stm, *Context);
+ return selectFirst<Stmt>("stmt", Matches);
+}
+
+const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) {
+ // Check whether any member of 'Exp' is mutated.
+ const auto MemberExprs = match(
+ findAll(memberExpr(hasObjectExpression(equalsNode(Exp))).bind("expr")),
+ *Stm, *Context);
+ return findExprMutation(MemberExprs);
+}
+
+const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) {
+ // Check whether any element of an array is mutated.
+ const auto SubscriptExprs = match(
+ findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp))))
+ .bind("expr")),
+ *Stm, *Context);
+ return findExprMutation(SubscriptExprs);
+}
+
+const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) {
+ // If 'Exp' is casted to any non-const reference type, check the castExpr.
+ const auto Casts =
+ match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)),
+ anyOf(explicitCastExpr(hasDestinationType(
+ nonConstReferenceType())),
+ implicitCastExpr(hasImplicitDestinationType(
+ nonConstReferenceType()))))
+ .bind("expr")),
+ *Stm, *Context);
+ return findExprMutation(Casts);
+}
+
+const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) {
+ // If range for looping over 'Exp' with a non-const reference loop variable,
+ // check all declRefExpr of the loop variable.
+ const auto LoopVars =
+ match(findAll(cxxForRangeStmt(
+ hasLoopVariable(
+ varDecl(hasType(nonConstReferenceType())).bind("decl")),
+ hasRangeInit(equalsNode(Exp)))),
+ *Stm, *Context);
+ return findDeclMutation(LoopVars);
+}
+
+const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) {
+ // If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
+ const auto Refs = match(
+ stmt(forEachDescendant(
+ varDecl(
+ hasType(nonConstReferenceType()),
+ hasInitializer(anyOf(equalsNode(Exp),
+ conditionalOperator(anyOf(
+ hasTrueExpression(equalsNode(Exp)),
+ hasFalseExpression(equalsNode(Exp)))))),
+ hasParent(declStmt().bind("stmt")),
+ // Don't follow the reference in range statement, we've handled
+ // that separately.
+ unless(hasParent(declStmt(hasParent(
+ cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt"))))))))
+ .bind("decl"))),
+ *Stm, *Context);
+ return findDeclMutation(Refs);
+}
+
+} // namespace utils
+} // namespace tidy
+} // namespace clang
+
Index: clang-tidy/utils/CMakeLists.txt
===================================================================
--- clang-tidy/utils/CMakeLists.txt
+++ clang-tidy/utils/CMakeLists.txt
@@ -3,6 +3,7 @@
add_clang_library(clangTidyUtils
ASTUtils.cpp
DeclRefExprUtils.cpp
+ ExprMutationAnalyzer.cpp
ExprSequence.cpp
FixItHintUtils.cpp
HeaderFileExtensionsUtils.cpp
Index: clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
===================================================================
--- clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
+++ clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
@@ -12,6 +12,7 @@
#include "../ClangTidyModuleRegistry.h"
#include "../misc/UnconventionalAssignOperatorCheck.h"
#include "AvoidGotoCheck.h"
+#include "ConstCheck.h"
#include "InterfacesGlobalInitCheck.h"
#include "NoMallocCheck.h"
#include "OwningMemoryCheck.h"
@@ -38,6 +39,8 @@
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<AvoidGotoCheck>(
"cppcoreguidelines-avoid-goto");
+ CheckFactories.registerCheck<ConstCheck>(
+ "cppcoreguidelines-const");
CheckFactories.registerCheck<InterfacesGlobalInitCheck>(
"cppcoreguidelines-interfaces-global-init");
CheckFactories.registerCheck<NoMallocCheck>("cppcoreguidelines-no-malloc");
Index: clang-tidy/cppcoreguidelines/ConstCheck.h
===================================================================
--- /dev/null
+++ clang-tidy/cppcoreguidelines/ConstCheck.h
@@ -0,0 +1,59 @@
+//===--- ConstCheck.h - clang-tidy-------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCHECK_H
+
+#include "../ClangTidy.h"
+#include "../utils/ExprMutationAnalyzer.h"
+
+namespace clang {
+namespace tidy {
+
+namespace utils {
+class ExprMutationAnalyzer;
+}
+
+namespace cppcoreguidelines {
+
+/// This check warns for every variable, that could be declared as const, but
+/// isn't.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-const.html
+class ConstCheck : public ClangTidyCheck {
+public:
+ ConstCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ AnalyzeValues(Options.get("AnalyzeValues", 1)),
+ AnalyzeReferences(Options.get("AnalyzeReferences", 1)),
+ WarnPointersAsValues(Options.get("WarnPointersAsValues", 0)) {}
+
+ 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 *Scope, ASTContext *Context);
+ bool usedNonConst(const VarDecl *Variable, const CompoundStmt *Scope,
+ const ast_matchers::MatchFinder::MatchResult &Result);
+
+ using MutationAnalyzer = std::unique_ptr<utils::ExprMutationAnalyzer>;
+ llvm::DenseMap<const CompoundStmt *, MutationAnalyzer> Scopes;
+
+ const bool AnalyzeValues;
+ const bool AnalyzeReferences;
+ const bool WarnPointersAsValues;
+};
+
+} // namespace cppcoreguidelines
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCHECK_H
Index: clang-tidy/cppcoreguidelines/ConstCheck.cpp
===================================================================
--- /dev/null
+++ clang-tidy/cppcoreguidelines/ConstCheck.cpp
@@ -0,0 +1,243 @@
+//===--- ConstCheck.cpp - clang-tidy---------------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ConstCheck.h"
+// #include "../utils/ASTUtils.h"
+// #include "../utils/DeclRefExprUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+#include <iostream>
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace cppcoreguidelines {
+
+/*
+ * General Thoughts
+ * ================
+ *
+ * For now: Only local variables are considered. Globals/namespace variables,
+ * paramters and class members are not analyzed.
+ * Parameters have a check already: readability-non-const-parameter
+ *
+ *
+ * Handle = either a pointer or reference
+ * Value = everything else (Type variable_name;)
+ *
+ * Value Semantic
+ * ==============
+ * - it is neither global nor namespace level + CHECK
+ * - it never gets assigned to after initialization + CHECK
+ * -> every uninitialized variable can not be const + CHECK
+ * - no non-const handle is created with it + CHECK
+ * - no non-const pointer from it + CHECK
+ * - no non-const pointer argument + CHECK
+ * - no non-const reference from it + CHECK
+ * - no non-const reference argument + CHECK
+ * - no non-const capture by reference in a lambda + CHECK
+ * - it is not returned as non-const handle from a function + CHECK
+ * - it address is not assigned to an out pointer parameter + CHECK
+ *
+ * primitive Builtins
+ * ----------------
+ * - it is not modified with an operator (++i,i++,--i,i--) + CHECK
+ * - it is not modified with an operator-assignment + CHECK
+ *
+ * objects
+ * -------
+ * - there is no non-const access to a member
+ * - there is no call to a non-const method + CHECK
+ * - there is no call to an non-const overloaded operator + CHECK
+ * - there is no non-const iterator created from this type + CHECK
+ * (std::begin and friends)
+ *
+ * arrays
+ * ------
+ * - there is no non-const operator[] access + CHECK
+ * - there is no non-const handle creation of one of the elements + CHECK
+ * - there is no non-const iterator created from this type + CHECK
+ * (std::begin and friends)
+ *
+ * templated variables
+ * -------------------
+ * - one can not reason about templated variables, because every sensible
+ * operation is overloadable and different instantiations will result
+ * in types with different const-properties.
+ * - Example: operator+(T& lhs, T& rhs) -> modification might occur for this
+ * type
+ * -> this fordbids `val = val1 + val2` val1 and val2 to be const
+ * - only concepts give possibility to infer constness of templated variables
+ *
+ * Handle Semantic
+ * ===============
+ * - modification of the pointee prohibit constness
+ * - Handles follow the typ of the pointee
+ *
+ * - no assignment to the target of the handle
+ *
+ * pointers
+ * --------
+ * - match both for value and handle semantic
+ *
+ * references
+ * ----------
+ * - only handle semantic applies
+ * - references to templated types suffer from the same problems as templated
+ * values
+ *
+ * forwarding reference
+ * --------------------
+ * - same as references?
+ *
+ * Implementation strategy
+ * =======================
+ *
+ * - Register every declared local variable/constant with value semantic.
+ * (pointers, values)
+ * Store if they can be made const.
+ * (const int i = 10 : no,
+ * int *const = &i : no,
+ * int i = 10 : yes, -> const int i = 10
+ * int *p_i = &i : yes, -> int *const p_i = &i)
+ * - Register every declared local variable/constant with handle semantic.
+ * (pointers, references)
+ * Store if they can be made const, meaning if they can be a const target
+ * (const int *cp_i = &i : no,
+ * const int &cr_i = i : no,
+ * int *p_i = &i : yes, -> const int *p_i = &i
+ * int &r_i = i : yes, -> const int &r_i = i)
+ * - Keep 2 dictionaries for values and handles
+ *
+ * - Match operations/events that forbid values to be const -> mark then 'no'
+ * - Match operations/events that forbid handles to be const -> mark then 'no'
+ *
+ * - once the translation unit is finished, determine what can be const, by
+ * just iterating over all keys and check if they map to 'true'.
+ * - values that can be const -> emit warning for their type and name
+ * - handles that can be const -> emit warning for the pointee type and name
+ * - ignore the rest
+ *
+ */
+
+namespace {
+AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); }
+} // namespace
+
+void ConstCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AnalyzeValues", AnalyzeValues);
+ Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
+ Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
+}
+
+void ConstCheck::registerMatchers(MatchFinder *Finder) {
+ if (!getLangOpts().CPlusPlus)
+ return;
+ const auto ConstType = hasType(isConstQualified());
+ const auto ConstReference = hasType(references(isConstQualified()));
+ const auto TemplateType = anyOf(hasType(templateTypeParmType()),
+ hasType(substTemplateTypeParmType()));
+
+ // Match local variables, that could be const.
+ // Example: `int i = 10`, `int i` (will be used if program is correct)
+ const auto LocalValDecl = varDecl(allOf(
+ isLocal(), hasInitializer(anything()), unless(ConstType),
+ unless(ConstReference), unless(TemplateType), unless(isImplicit())));
+
+ // Match the function scope for which the analysis of all local variables
+ // shall be run.
+ const auto FunctionScope =
+ functionDecl(allOf(hasBody(compoundStmt().bind("scope")),
+ findAll(LocalValDecl.bind("new-local-value"))));
+ Finder->addMatcher(FunctionScope, this);
+}
+
+void ConstCheck::check(const MatchFinder::MatchResult &Result) {
+ if (!getLangOpts().CPlusPlus)
+ return;
+
+ const auto *Scope = Result.Nodes.getNodeAs<CompoundStmt>("scope");
+ registerScope(Scope, Result.Context);
+
+ const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("new-local-value");
+ assert(Variable && "Did not match local variable definition");
+
+ if (Variable->getType()->isPointerType() && !WarnPointersAsValues)
+ return;
+
+ if (Variable->getType()->isReferenceType() && !AnalyzeReferences)
+ return;
+
+ if (usedNonConst(Variable, Scope, Result) || !AnalyzeValues)
+ return;
+
+ diag(Variable->getLocStart(), "variable %0 of type %1 can be declared const")
+ << Variable << Variable->getType();
+#if 0
+ if (const auto *Variable =
+ Result.Nodes.getNodeAs<VarDecl>("new-local-handle")) {
+ assert(AnalyzeHandles && "Matched local handle without analyzing them");
+
+ if (usedNonConst(Variable, Scope, Result))
+ return;
+
+ // if (!utils::decl_ref_expr::isOnlyUsedAsConst(*Variable, *Scope,
+ // *Result.Context))
+ // return;
+
+ // Differentiate between pointers and references.
+ QualType HandleType = Variable->getType();
+ if (HandleType->isReferenceType())
+ diag(Variable->getLocStart(),
+ "reference variable %0 of type %1 can be declared const")
+ << Variable << HandleType;
+ else if (HandleType->isPointerType())
+ diag(Variable->getLocStart(),
+ "pointer variable %0 of type %1 can be declared const")
+ << Variable << HandleType;
+ else
+ llvm_unreachable("Expected handle type");
+ }
+#endif
+}
+
+void ConstCheck::registerScope(const CompoundStmt *Scope, ASTContext *Context) {
+ if (Scopes.find(Scope) == Scopes.end()) {
+ Scopes.insert(std::make_pair(
+ Scope, llvm::make_unique<utils::ExprMutationAnalyzer>(Scope, Context)));
+ }
+}
+
+bool ConstCheck::usedNonConst(const VarDecl *Variable,
+ const CompoundStmt *Scope,
+ const MatchFinder::MatchResult &Result) {
+
+ // find usage of that variable
+ // if a declRefExpr is found -> analyze
+ // else -> constant
+ const auto Usage = match(
+ findAll(declRefExpr(hasDeclaration(equalsNode(Variable))).bind("use")),
+ *Scope, *Result.Context);
+ const auto *UseExpr = selectFirst<Expr>("use", Usage);
+
+ // The declared variables was used in non-const conserving way and can not
+ // be declared as const.
+ if (UseExpr && Scopes[Scope]->isMutated(UseExpr)) {
+ // diag(UseExpr->getLocStart(), "Investigating starting with this use",
+ // DiagnosticIDs::Note);
+ return true;
+ }
+ return false;
+}
+
+} // namespace cppcoreguidelines
+} // namespace tidy
+} // namespace clang
Index: clang-tidy/cppcoreguidelines/CMakeLists.txt
===================================================================
--- clang-tidy/cppcoreguidelines/CMakeLists.txt
+++ clang-tidy/cppcoreguidelines/CMakeLists.txt
@@ -2,6 +2,7 @@
add_clang_library(clangTidyCppCoreGuidelinesModule
AvoidGotoCheck.cpp
+ ConstCheck.cpp
CppCoreGuidelinesTidyModule.cpp
InterfacesGlobalInitCheck.cpp
NoMallocCheck.cpp
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits