[clang-tidy] Handle unresolved expressions in ExprMutationAnalyzer

Summary:
- If a function is unresolved, assume it mutates its arguments
- Follow unresolved member expressions for nested mutations

Reviewers: aaron.ballman, JonasToth, george.karpenkov

Subscribers: xazax.hun, a.sidorin, cfe-commits

Differential Revision: https://reviews.llvm.org/D50619

llvm-svn: 341848
This commit is contained in:
Shuai Wang 2018-09-10 18:05:13 +00:00
parent f8acd723e8
commit bef0941b6b
2 changed files with 126 additions and 10 deletions

View File

@ -145,11 +145,16 @@ const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
hasUnaryOperand(equalsNode(Exp)));
// Invoking non-const member function.
// A member function is assumed to be non-const when it is unresolved.
const auto NonConstMethod = cxxMethodDecl(unless(isConst()));
const auto AsNonConstThis =
expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))),
cxxOperatorCallExpr(callee(NonConstMethod),
hasArgument(0, equalsNode(Exp)))));
hasArgument(0, equalsNode(Exp))),
callExpr(callee(expr(anyOf(
unresolvedMemberExpr(hasObjectExpression(equalsNode(Exp))),
cxxDependentScopeMemberExpr(
hasObjectExpression(equalsNode(Exp)))))))));
// Taking address of 'Exp'.
// We're assuming 'Exp' is mutated as soon as its address is taken, though in
@ -165,10 +170,16 @@ const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp)));
// Used as non-const-ref argument when calling a function.
// An argument is assumed to be non-const-ref when the function is unresolved.
const auto NonConstRefParam = forEachArgumentWithParam(
equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType())));
const auto AsNonConstRefArg =
anyOf(callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam));
const auto AsNonConstRefArg = anyOf(
callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam),
callExpr(callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(),
cxxDependentScopeMemberExpr(),
hasType(templateTypeParmType())))),
hasAnyArgument(equalsNode(Exp))),
cxxUnresolvedConstructExpr(hasAnyArgument(equalsNode(Exp))));
// Captured by a lambda by reference.
// If we're initializing a capture with 'Exp' directly then we're initializing
@ -195,9 +206,12 @@ const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) {
// Check whether any member of 'Exp' is mutated.
const auto MemberExprs = match(
findAll(memberExpr(hasObjectExpression(equalsNode(Exp))).bind("expr")),
*Stm, *Context);
const auto MemberExprs =
match(findAll(expr(anyOf(memberExpr(hasObjectExpression(equalsNode(Exp))),
cxxDependentScopeMemberExpr(
hasObjectExpression(equalsNode(Exp)))))
.bind("expr")),
*Stm, *Context);
return findExprMutation(MemberExprs);
}

View File

@ -114,6 +114,26 @@ TEST(ExprMutationAnalyzerTest, NonConstMemberFunc) {
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf()"));
}
TEST(ExprMutationAnalyzerTest, AssumedNonConstMemberFunc) {
auto AST = tooling::buildASTFromCode(
"struct X { template <class T> void mf(); };"
"template <class T> void f() { X x; x.mf<T>(); }");
auto Results =
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf<T>()"));
AST =
tooling::buildASTFromCode("template <class T> void f() { T x; x.mf(); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf()"));
AST = tooling::buildASTFromCode(
"template <class T> struct X;"
"template <class T> void f() { X<T> x; x.mf(); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf()"));
}
TEST(ExprMutationAnalyzerTest, ConstMemberFunc) {
const auto AST = tooling::buildASTFromCode(
"void f() { struct Foo { void mf() const; }; Foo x; x.mf(); }");
@ -292,6 +312,51 @@ TEST(ExprMutationAnalyzerTest, Forward) {
ElementsAre("std::forward<A &>(x)"));
}
TEST(ExprMutationAnalyzerTest, CallUnresolved) {
auto AST =
tooling::buildASTFromCode("template <class T> void f() { T x; g(x); }");
auto Results =
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)"));
AST = tooling::buildASTFromCode(
"template <int N> void f() { char x[N]; g(x); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)"));
AST = tooling::buildASTFromCode(
"template <class T> void f(T t) { int x; g(t, x); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(t, x)"));
AST = tooling::buildASTFromCode(
"template <class T> void f(T t) { int x; t.mf(x); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("t.mf(x)"));
AST = tooling::buildASTFromCode(
"template <class T> struct S;"
"template <class T> void f() { S<T> s; int x; s.mf(x); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("s.mf(x)"));
AST = tooling::buildASTFromCode(
"struct S { template <class T> void mf(); };"
"template <class T> void f(S s) { int x; s.mf<T>(x); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("s.mf<T>(x)"));
AST = tooling::buildASTFromCode("template <class F>"
"void g(F f) { int x; f(x); } ");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("f(x)"));
AST = tooling::buildASTFromCode(
"template <class T> void f() { int x; (void)T(x); }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("T(x)"));
}
TEST(ExprMutationAnalyzerTest, ReturnAsValue) {
const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }");
const auto Results =
@ -347,6 +412,21 @@ TEST(ExprMutationAnalyzerTest, ArrayToPointerDecay) {
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
}
TEST(ExprMutationAnalyzerTest, TemplateWithArrayToPointerDecay) {
const auto AST = tooling::buildASTFromCode(
"template <typename T> struct S { static constexpr int v = 8; };"
"template <> struct S<int> { static constexpr int v = 4; };"
"void g(char*);"
"template <typename T> void f() { char x[S<T>::v]; g(x); }"
"template <> void f<int>() { char y[S<int>::v]; g(y); }");
const auto ResultsX =
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(ResultsX, AST.get()), ElementsAre("g(x)"));
const auto ResultsY =
match(withEnclosingCompound(declRefTo("y")), AST->getASTContext());
EXPECT_THAT(mutatedBy(ResultsY, AST.get()), ElementsAre("y"));
}
TEST(ExprMutationAnalyzerTest, FollowRefModified) {
const auto AST = tooling::buildASTFromCode(
"void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; "
@ -398,21 +478,43 @@ TEST(ExprMutationAnalyzerTest, ArrayElementNotModified) {
}
TEST(ExprMutationAnalyzerTest, NestedMemberModified) {
const auto AST = tooling::buildASTFromCode(
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 =
auto Results =
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.vb.va.vi = 10"));
AST = tooling::buildASTFromCode(
"template <class T> void f() { T x; x.y.z = 10; }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.y.z = 10"));
AST = tooling::buildASTFromCode(
"template <class T> struct S;"
"template <class T> void f() { S<T> x; x.y.z = 10; }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.y.z = 10"));
}
TEST(ExprMutationAnalyzerTest, NestedMemberNotModified) {
const auto AST = tooling::buildASTFromCode(
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 =
auto Results =
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_FALSE(isMutated(Results, AST.get()));
AST =
tooling::buildASTFromCode("template <class T> void f() { T x; x.y.z; }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_FALSE(isMutated(Results, AST.get()));
AST = tooling::buildASTFromCode(
"template <class T> struct S;"
"template <class T> void f() { S<T> x; x.y.z; }");
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
EXPECT_FALSE(isMutated(Results, AST.get()));
}
TEST(ExprMutationAnalyzerTest, CastToValue) {