Index: lib/Analysis/ExprMutationAnalyzer.cpp =================================================================== --- lib/Analysis/ExprMutationAnalyzer.cpp +++ lib/Analysis/ExprMutationAnalyzer.cpp @@ -304,7 +304,16 @@ nonConstReferenceType())))) .bind(NodeID::value)), Stm, Context); - return findExprMutation(Casts); + if (const Stmt *S = findExprMutation(Casts)) + return S; + // Treat std::{move,forward} as cast. + const auto Calls = + match(findAll(callExpr(callee(namedDecl( + hasAnyName("::std::move", "::std::forward"))), + hasArgument(0, equalsNode(Exp))) + .bind("expr")), + Stm, Context); + return findExprMutation(Calls); } const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) { @@ -360,7 +369,9 @@ const auto IsInstantiated = hasDeclaration(isInstantiated()); const auto FuncDecl = hasDeclaration(functionDecl().bind("func")); const auto Matches = match( - findAll(expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl), + findAll(expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl, + unless(callee(namedDecl(hasAnyName( + "::std::move", "::std::forward"))))), cxxConstructExpr(NonConstRefParam, IsInstantiated, FuncDecl))) .bind(NodeID::value)), Index: unittests/Analysis/ExprMutationAnalyzerTest.cpp =================================================================== --- unittests/Analysis/ExprMutationAnalyzerTest.cpp +++ unittests/Analysis/ExprMutationAnalyzerTest.cpp @@ -66,6 +66,25 @@ return s; } +const std::string StdRemoveReference = + "namespace std {" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; }; }"; + +const std::string StdMove = + "namespace std {" + "template typename remove_reference::type&& " + "move(T&& t) noexcept {" + "return static_cast::type&&>(t); } }"; + +const std::string StdForward = + "namespace std {" + "template T&& " + "forward(typename remove_reference::type& t) noexcept { return t; }" + "template T&& " + "forward(typename remove_reference::type&&) noexcept { return t; } }"; + } // namespace TEST(ExprMutationAnalyzerTest, Trivial) { @@ -373,36 +392,87 @@ } TEST(ExprMutationAnalyzerTest, Move) { - // Technically almost the same as ByNonConstRRefArgument, just double checking - 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 = + auto AST = + tooling::buildASTFromCode(StdRemoveReference + StdMove + + "void f() { struct A {}; A x; std::move(x); }"); + auto Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); - EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("std::move(x)")); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdMove + + "void f() { struct A {}; A x, y; std::move(x) = y; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("std::move(x) = y")); + + AST = tooling::buildASTFromCode(StdRemoveReference + StdMove + + "void f() { int x, y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdMove + + "struct S { S(); S(const S&); S& operator=(const S&); };" + "void f() { S x, y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = + tooling::buildASTFromCode(StdRemoveReference + StdMove + + "struct S { S(); S(S&&); S& operator=(S&&); };" + "void f() { S x, y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("y = std::move(x)")); + + AST = + tooling::buildASTFromCode(StdRemoveReference + StdMove + + "struct S { S(); S(const S&); S(S&&);" + "S& operator=(const S&); S& operator=(S&&); };" + "void f() { S x, y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("y = std::move(x)")); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdMove + + "struct S { S(); S(const S&); S(S&&);" + "S& operator=(const S&); S& operator=(S&&); };" + "void f() { const S x; S y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode(StdRemoveReference + StdMove + + "struct S { S(); S(S); S& operator=(S); };" + "void f() { S x, y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdMove + + "struct S{}; void f() { S x, y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("y = std::move(x)")); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdMove + + "struct S{}; void f() { const S x; S y; y = std::move(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); } TEST(ExprMutationAnalyzerTest, Forward) { - // Technically almost the same as ByNonConstRefArgument, just double checking - 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;" + auto AST = tooling::buildASTFromCode( + StdRemoveReference + StdForward + "void f() { struct A {}; A x; std::forward(x); }"); - const auto Results = + auto Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdForward + + "void f() { struct A {}; A x, y; std::forward(x) = y; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); EXPECT_THAT(mutatedBy(Results, AST.get()), - ElementsAre("std::forward(x)")); + ElementsAre("std::forward(x) = y")); } TEST(ExprMutationAnalyzerTest, CallUnresolved) { @@ -639,6 +709,17 @@ "void f() { int x; S s(x); }"); Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x")); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdForward + + "template void u(Args&...);" + "template void h(Args&&... args)" + "{ u(std::forward(args)...); }" + "template void g(Args&&... args)" + "{ h(std::forward(args)...); }" + "void f() { int x; g(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)")); } TEST(ExprMutationAnalyzerTest, FollowFuncArgNotModified) { @@ -683,6 +764,17 @@ "void f() { int x; S s(x); }"); Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + StdRemoveReference + StdForward + + "template void u(Args...);" + "template void h(Args&&... args)" + "{ u(std::forward(args)...); }" + "template void g(Args&&... args)" + "{ h(std::forward(args)...); }" + "void f() { int x; g(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); } TEST(ExprMutationAnalyzerTest, ArrayElementModified) {