Index: clang-tidy/utils/ASTUtils.h =================================================================== --- clang-tidy/utils/ASTUtils.h +++ clang-tidy/utils/ASTUtils.h @@ -27,6 +27,27 @@ bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM, const LangOptions &LangOpts, StringRef FlagName); + +enum ExprModificationKind { + EMK_NotModified, // The Expr is neither modified nor escaped. + EMK_Modified, /// The Expr is directly modified. + EMK_Escaped, /// The Expr escaped the scope being analyzed within. +}; + +/// Checks whether `Exp` is modified within `Stm` or a modifiable reference +/// escaped `Stm`. +/// +/// \param Exp - The `Expr` to be checked. +/// \param Stm - The `Stmt` to perform the check within. +/// \param Context - The ASTContext. +/// \param Chain - If not nullptr and when returning EMK_Modified or +/// EMK_Escaped, will be filled with a list of `Stmt` that leads to the +/// conclusion, with the last element being the modifying expression or the +/// escaping point. +ExprModificationKind isModified(const Expr &Exp, const Stmt &Stm, + ASTContext *Context, + SmallVectorImpl *Chain = nullptr); + } // namespace utils } // namespace tidy } // namespace clang Index: clang-tidy/utils/ASTUtils.cpp =================================================================== --- clang-tidy/utils/ASTUtils.cpp +++ clang-tidy/utils/ASTUtils.cpp @@ -67,6 +67,158 @@ return true; } +namespace { +AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr*, E) { + for (const auto* Init : Node.capture_inits()) { + if (Init == E) return true; + } + return false; +} +} // namespace + +ExprModificationKind isModified(const Expr &Exp, const Stmt &Stm, + ASTContext *Context, + SmallVectorImpl *Chain) { + // 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'. + // In theory we can follow the pointer and see whether the pointer itself + // escaped 'Stm', or is dereferenced and the dereferencing expression is + // modified. This is left for future improvements. + const auto AsAmpersandOperand = + unaryOperator(hasOperatorName("&"), + 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 NonConstRefType = + referenceType(pointee(unless(isConstQualified()))); + const auto NonConstRefParam = forEachArgumentWithParam( + equalsNode(&Exp), parmVarDecl(hasType(qualType(NonConstRefType)))); + 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 . + 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 . + // For returning by const-ref there will be an ImplicitCastExpr (for + // adding const.) + const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(&Exp))); + + // Check whether 'Exp' is directly modified as a whole or escaped. + const auto ModOrEsc = match( + findAll(stmt(anyOf( + expr(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis, + AsAmpersandOperand, AsPointerFromArrayDecay)) + .bind("mod"), + expr(anyOf(AsNonConstRefArg, AsLambdaRefCaptureInit)).bind("esc"), + stmt(AsNonConstRefReturn).bind("esc")))), + Stm, *Context); + if (const auto *S = selectFirst("mod", ModOrEsc)) { + if (Chain != nullptr) + Chain->push_back(S); + return EMK_Modified; + } + if (const auto *S = selectFirst("esc", ModOrEsc)) { + if (Chain != nullptr) + Chain->push_back(S); + return EMK_Escaped; + } + + const auto isExprModified = [&](ArrayRef Results) { + for (const auto &Node : Results) { + SmallVector C; + const auto *E = Node.getNodeAs("expr"); + const auto Kind = isModified(*E, Stm, Context, Chain ? &C : nullptr); + if (Kind != EMK_NotModified) { + if (Chain != nullptr) { + Chain->push_back(E); + Chain->append(C.begin(), C.end()); + } + return Kind; + } + } + return EMK_NotModified; + }; + + // Check whether any member of 'Exp' is modified. + const auto MemberExprs = match( + findAll(memberExpr(hasObjectExpression(equalsNode(&Exp))).bind("expr")), + Stm, *Context); + if (const auto Kind = isExprModified(MemberExprs)) + return Kind; + + // In the case of 'Exp' being an array, check whether any element is modified. + const auto SubscriptExprs = match( + findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(&Exp)))) + .bind("expr")), + Stm, *Context); + if (const auto Kind = isExprModified(SubscriptExprs)) + return Kind; + + // 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(NonConstRefType)), + implicitCastExpr( + hasImplicitDestinationType(NonConstRefType)))) + .bind("expr")), + Stm, *Context); + if (const auto Kind = isExprModified(Casts)) + return Kind; + + // If 'Exp' is bound to a non-const reference, check all declRefExpr to that. + const auto Decls = match( + stmt(forEachDescendant( + varDecl( + hasType(referenceType(pointee(unless(isConstQualified())))), + hasInitializer(anyOf(equalsNode(&Exp), + conditionalOperator(anyOf( + hasTrueExpression(equalsNode(&Exp)), + hasFalseExpression(equalsNode(&Exp)))))), + hasParent(declStmt().bind("stmt"))) + .bind("decl"))), + Stm, *Context); + for (const auto &DeclNode : Decls) { + if (Chain != nullptr) + Chain->push_back(DeclNode.getNodeAs("stmt")); + const auto Exprs = match( + findAll(declRefExpr(to(equalsNode(DeclNode.getNodeAs("decl")))) + .bind("expr")), + Stm, *Context); + if (const auto Kind = isExprModified(Exprs)) + return Kind; + if (Chain != nullptr) + Chain->pop_back(); + } + + return EMK_NotModified; +} + } // namespace utils } // namespace tidy } // namespace clang Index: unittests/clang-tidy/CMakeLists.txt =================================================================== --- unittests/clang-tidy/CMakeLists.txt +++ unittests/clang-tidy/CMakeLists.txt @@ -10,6 +10,7 @@ ClangTidyDiagnosticConsumerTest.cpp ClangTidyOptionsTest.cpp IncludeInserterTest.cpp + IsModifiedTest.cpp GoogleModuleTest.cpp LLVMModuleTest.cpp NamespaceAliaserTest.cpp Index: unittests/clang-tidy/IsModifiedTest.cpp =================================================================== --- /dev/null +++ unittests/clang-tidy/IsModifiedTest.cpp @@ -0,0 +1,473 @@ +//===---- IsModifiedTest.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/ASTUtils.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::Values; + +namespace { + +using ExprMatcher = internal::Matcher; +using StmtMatcher = internal::Matcher; + +ExprMatcher declRefTo(StringRef Name) { + return declRefExpr(to(namedDecl(hasName(Name)))); +} + +StmtMatcher withEnclosingCompound(ExprMatcher Matcher) { + return expr(Matcher, hasAncestor(compoundStmt().bind("stmt"))).bind("expr"); +} + +utils::ExprModificationKind +isModified(const SmallVectorImpl &Results, ASTUnit *AST, + SmallVectorImpl *Chain) { + const auto E = selectFirst("expr", Results); + const auto S = selectFirst("stmt", Results); + SmallVector C; + const auto Kind = utils::isModified(*E, *S, &AST->getASTContext(), &C); + for (const auto *S : C) { + std::string buffer; + llvm::raw_string_ostream stream(buffer); + S->printPretty(stream, nullptr, AST->getASTContext().getPrintingPolicy()); + Chain->push_back(StringRef(stream.str()).trim().str()); + } + return Kind; +} + +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(IsModifiedTest, Trivial) { + const auto AST = tooling::buildASTFromCode("void f() { int x; x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +class AssignmentTest : public ::testing::TestWithParam {}; + +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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre(ModExpr)); +} + +INSTANTIATE_TEST_CASE_P(AllAssignmentOperators, AssignmentTest, + Values("=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", + "^=", "<<=", ">>="), ); + +class IncDecTest : public ::testing::TestWithParam {}; + +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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre(ModExpr)); +} + +INSTANTIATE_TEST_CASE_P(AllIncDecOperators, IncDecTest, + Values("++x", "--x", "x++", "x--"), ); + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("x.mf()")); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("x = 10")); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, ByValueArgument) { + const auto AST = + tooling::buildASTFromCode("void g(int); void f() { int x; g(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, ByNonConstRefArgument) { + const auto AST = + tooling::buildASTFromCode("void g(int&); void f() { int x; g(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre("g(x)")); +} + +TEST(IsModifiedTest, ByConstRefArgument) { + const auto AST = tooling::buildASTFromCode( + "void g(const int&); void f() { int x; g(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, ByNonConstRRefArgument) { + const auto AST = tooling::buildASTFromCode( + "void g(int&&); void f() { int x; g(static_cast(x)); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre("g(static_cast(x))")); +} + +TEST(IsModifiedTest, ByConstRRefArgument) { + const auto AST = tooling::buildASTFromCode( + "void g(const int&&); void f() { int x; g(static_cast(x)); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, Move) { + // Technically almost the same as ByNonConstRRefArgument, just double checking + // here. + const auto AST = tooling::buildASTFromCode( + "namespace std {" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template typename std::remove_reference::type&& " + "move(T&& t) noexcept; }" + "void f() { struct A {}; A x; std::move(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre("std::move(x)")); +} + +TEST(IsModifiedTest, Forward) { + // Technically almost the same as ByNonConstRefArgument, just double checking + // here. + const auto AST = tooling::buildASTFromCode( + "namespace std {" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template T&& " + "forward(typename std::remove_reference::type&) noexcept;" + "template T&& " + "forward(typename std::remove_reference::type&&) noexcept;" + "void f() { struct A {}; A x; std::forward(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre("std::forward(x)")); +} + +TEST(IsModifiedTest, ReturnAsValue) { + const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, ReturnAsNonConstRef) { + const auto AST = tooling::buildASTFromCode("int& f() { int x; return x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre("return x;")); +} + +TEST(IsModifiedTest, ReturnAsConstRef) { + const auto AST = + tooling::buildASTFromCode("const int& f() { int x; return x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, ReturnAsNonConstRRef) { + const auto AST = tooling::buildASTFromCode( + "int&& f() { int x; return static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre("static_cast(x)", + "return static_cast(x);")); +} + +TEST(IsModifiedTest, ReturnAsConstRRef) { + const auto AST = tooling::buildASTFromCode( + "const int&& f() { int x; return static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, TakeAddress) { + const auto AST = + tooling::buildASTFromCode("void g(int*); void f() { int x; g(&x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("&x")); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("x")); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("int &r0 = x;", "r0", "int &r1 = r0;", "r1", + "int &r2 = r1;", "r2", "int &r3 = r2;", "r3", + "r3 = 10")); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("int &r = b ? x : y;", "r", "r = 10")); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, ArrayElementModified) { + const auto AST = + tooling::buildASTFromCode("void f() { int x[2]; x[0] = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("x[0]", "x[0] = 10")); +} + +TEST(IsModifiedTest, ArrayElementNotModified) { + const auto AST = tooling::buildASTFromCode("void f() { int x[2]; x[0]; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, + ElementsAre("x.vb", "x.vb.va", "x.vb.va.vi", "x.vb.va.vi = 10")); +} + +TEST(IsModifiedTest, 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()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, CastToValue) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, CastToRefModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x; static_cast(x) = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified); + EXPECT_THAT(Chain, ElementsAre("static_cast(x)", + "static_cast(x) = 10")); +} + +TEST(IsModifiedTest, CastToRefNotModified) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, CastToConstRef) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x; static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, LambdaDefaultCaptureByValue) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [=]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, LambdaExplicitCaptureByValue) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [x]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified); + EXPECT_THAT(Chain, IsEmpty()); +} + +TEST(IsModifiedTest, LambdaDefaultCaptureByRef) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [&]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre(ResultOf(removeSpace, "[&](){x=10;}"))); +} + +TEST(IsModifiedTest, LambdaExplicitCaptureByRef) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [&x]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + SmallVector Chain; + EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped); + EXPECT_THAT(Chain, ElementsAre(ResultOf(removeSpace, "[&x](){x=10;}"))); +} + +} // namespace test +} // namespace tidy +} // namespace clang