shuaiwang created this revision. shuaiwang added reviewers: lebedev.ri, JonasToth. Herald added subscribers: cfe-commits, Szelethus, mikhail.ramalho, a.sidorin, szepet, xazax.hun. Herald added a reviewer: george.karpenkov.
This is a follow up of https://reviews.llvm.org/D52008 and should make the analyzer being able to handle perfect forwardings in real world cases where forwardings are done through multiple layers of function calls with `std::forward`. Fixes PR38891. Repository: rC Clang https://reviews.llvm.org/D52120 Files: lib/Analysis/ExprMutationAnalyzer.cpp unittests/Analysis/ExprMutationAnalyzerTest.cpp
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<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 = + "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<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;" + "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 = + "forward(typename std::remove_reference<T>::type&&) noexcept {} }"; + auto AST = tooling::buildASTFromCode( + Forward + "void f() { struct A {}; A x; std::forward<A &>(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<A &>(x) = y; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); EXPECT_THAT(mutatedBy(Results, AST.get()), - ElementsAre("std::forward<A &>(x)")); + ElementsAre("std::forward<A &>(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<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& t) noexcept" + "{ return t; }" + "template<class T> T&& " + "forward(typename std::remove_reference<T>::type&& t) noexcept" + "{ return t; } }" + "template <class... Args> void u(Args&...);" + "template <class... Args> void h(Args&&... args)" + "{ u(std::forward<Args>(args)...); }" + "template <class... Args> void g(Args&&... args)" + "{ h(std::forward<Args>(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<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& t) noexcept" + "{ return t; }" + "template<class T> T&& " + "forward(typename std::remove_reference<T>::type&& t) noexcept" + "{ return t; } }" + "template <class... Args> void u(Args...);" + "template <class... Args> void h(Args&&... args)" + "{ u(std::forward<Args>(args)...); }" + "template <class... Args> void g(Args&&... args)" + "{ h(std::forward<Args>(args)...); }" + "void f() { int x; g(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); } TEST(ExprMutationAnalyzerTest, ArrayElementModified) { 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")),
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits