Index: lib/Analysis/ExprMutationAnalyzer.cpp =================================================================== --- lib/Analysis/ExprMutationAnalyzer.cpp +++ lib/Analysis/ExprMutationAnalyzer.cpp @@ -261,7 +261,16 @@ nonConstReferenceType())))) .bind("expr")), 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) { @@ -318,7 +327,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("expr")), Index: unittests/Analysis/ExprMutationAnalyzerTest.cpp =================================================================== --- unittests/Analysis/ExprMutationAnalyzerTest.cpp +++ unittests/Analysis/ExprMutationAnalyzerTest.cpp @@ -373,36 +373,46 @@ } TEST(ExprMutationAnalyzerTest, Move) { - // Technically almost the same as ByNonConstRRefArgument, just double checking - const auto AST = tooling::buildASTFromCode( + const std::string Move = "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 = + "move(T&& t) noexcept {} }"; + auto AST = tooling::buildASTFromCode( + Move + "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( + Move + "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")); } TEST(ExprMutationAnalyzerTest, Forward) { - // Technically almost the same as ByNonConstRefArgument, just double checking - const auto AST = tooling::buildASTFromCode( + const std::string Forward = "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;" + "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 = + "forward(typename std::remove_reference::type&&) noexcept {} }"; + auto AST = tooling::buildASTFromCode( + Forward + "void f() { struct A {}; A x; std::forward(x); }"); + auto Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + Forward + "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 +649,26 @@ "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( + "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& t) noexcept" + "{ return t; }" + "template T&& " + "forward(typename std::remove_reference::type&& t) noexcept" + "{ return t; } }" + "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 +713,26 @@ "void f() { int x; S s(x); }"); Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); EXPECT_FALSE(isMutated(Results, AST.get())); + + 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& t) noexcept" + "{ return t; }" + "template T&& " + "forward(typename std::remove_reference::type&& t) noexcept" + "{ return t; } }" + "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) {