mirror of https://github.com/microsoft/clang.git
[analyzer] Treat std::{move,forward} as casts in ExprMutationAnalyzer.
Summary: This is a follow up of 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. Reviewers: lebedev.ri, JonasToth, george.karpenkov Subscribers: xazax.hun, szepet, a.sidorin, mikhail.ramalho, Szelethus, cfe-commits Differential Revision: https://reviews.llvm.org/D52120 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@342409 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
926e91b0e6
commit
ccf123b17d
|
@ -304,7 +304,16 @@ const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) {
|
||||||
nonConstReferenceType()))))
|
nonConstReferenceType()))))
|
||||||
.bind(NodeID<Expr>::value)),
|
.bind(NodeID<Expr>::value)),
|
||||||
Stm, Context);
|
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) {
|
const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) {
|
||||||
|
@ -360,7 +369,9 @@ const Stmt *ExprMutationAnalyzer::findFunctionArgMutation(const Expr *Exp) {
|
||||||
const auto IsInstantiated = hasDeclaration(isInstantiated());
|
const auto IsInstantiated = hasDeclaration(isInstantiated());
|
||||||
const auto FuncDecl = hasDeclaration(functionDecl().bind("func"));
|
const auto FuncDecl = hasDeclaration(functionDecl().bind("func"));
|
||||||
const auto Matches = match(
|
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,
|
cxxConstructExpr(NonConstRefParam, IsInstantiated,
|
||||||
FuncDecl)))
|
FuncDecl)))
|
||||||
.bind(NodeID<Expr>::value)),
|
.bind(NodeID<Expr>::value)),
|
||||||
|
|
|
@ -66,6 +66,25 @@ std::string removeSpace(std::string s) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string StdRemoveReference =
|
||||||
|
"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; }; }";
|
||||||
|
|
||||||
|
const std::string StdMove =
|
||||||
|
"namespace std {"
|
||||||
|
"template<class T> typename remove_reference<T>::type&& "
|
||||||
|
"move(T&& t) noexcept {"
|
||||||
|
"return static_cast<typename remove_reference<T>::type&&>(t); } }";
|
||||||
|
|
||||||
|
const std::string StdForward =
|
||||||
|
"namespace std {"
|
||||||
|
"template<class T> T&& "
|
||||||
|
"forward(typename remove_reference<T>::type& t) noexcept { return t; }"
|
||||||
|
"template<class T> T&& "
|
||||||
|
"forward(typename remove_reference<T>::type&&) noexcept { return t; } }";
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(ExprMutationAnalyzerTest, Trivial) {
|
TEST(ExprMutationAnalyzerTest, Trivial) {
|
||||||
|
@ -373,36 +392,87 @@ TEST(ExprMutationAnalyzerTest, ByConstRRefArgument) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ExprMutationAnalyzerTest, Move) {
|
TEST(ExprMutationAnalyzerTest, Move) {
|
||||||
// Technically almost the same as ByNonConstRRefArgument, just double checking
|
auto AST =
|
||||||
const auto AST = tooling::buildASTFromCode(
|
tooling::buildASTFromCode(StdRemoveReference + StdMove +
|
||||||
"namespace std {"
|
"void f() { struct A {}; A x; std::move(x); }");
|
||||||
"template<class T> struct remove_reference { typedef T type; };"
|
auto Results =
|
||||||
"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());
|
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) {
|
TEST(ExprMutationAnalyzerTest, Forward) {
|
||||||
// Technically almost the same as ByNonConstRefArgument, just double checking
|
auto AST = tooling::buildASTFromCode(
|
||||||
const auto AST = tooling::buildASTFromCode(
|
StdRemoveReference + StdForward +
|
||||||
"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); }");
|
"void f() { struct A {}; A x; std::forward<A &>(x); }");
|
||||||
const auto Results =
|
auto Results =
|
||||||
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
|
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<A &>(x) = y; }");
|
||||||
|
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
|
||||||
EXPECT_THAT(mutatedBy(Results, AST.get()),
|
EXPECT_THAT(mutatedBy(Results, AST.get()),
|
||||||
ElementsAre("std::forward<A &>(x)"));
|
ElementsAre("std::forward<A &>(x) = y"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ExprMutationAnalyzerTest, CallUnresolved) {
|
TEST(ExprMutationAnalyzerTest, CallUnresolved) {
|
||||||
|
@ -639,6 +709,17 @@ TEST(ExprMutationAnalyzerTest, FollowFuncArgModified) {
|
||||||
"void f() { int x; S s(x); }");
|
"void f() { int x; S s(x); }");
|
||||||
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
|
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
|
||||||
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
|
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
|
||||||
|
|
||||||
|
AST = tooling::buildASTFromCode(
|
||||||
|
StdRemoveReference + StdForward +
|
||||||
|
"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) {
|
TEST(ExprMutationAnalyzerTest, FollowFuncArgNotModified) {
|
||||||
|
@ -683,6 +764,17 @@ TEST(ExprMutationAnalyzerTest, FollowFuncArgNotModified) {
|
||||||
"void f() { int x; S s(x); }");
|
"void f() { int x; S s(x); }");
|
||||||
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
|
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
|
||||||
EXPECT_FALSE(isMutated(Results, AST.get()));
|
EXPECT_FALSE(isMutated(Results, AST.get()));
|
||||||
|
|
||||||
|
AST = tooling::buildASTFromCode(
|
||||||
|
StdRemoveReference + StdForward +
|
||||||
|
"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) {
|
TEST(ExprMutationAnalyzerTest, ArrayElementModified) {
|
||||||
|
|
Loading…
Reference in New Issue