Skip to content

Commit 04a02c1

Browse files
[ASTMatchers] forCallable should not erase binding on success (#89657)
Do not erase Builder when the first check fails because it could succeed on the second stack frame. The problem was that `InnerMatcher.matches` erases the bindings when it returns false. The appropriate solution is to pass a copy of the bindings, similar to what `matchesFirstInRange` does.
1 parent 419e7b8 commit 04a02c1

File tree

3 files changed

+143
-4
lines changed

3 files changed

+143
-4
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,7 @@ AST Matchers
996996
- Fixed ``forEachArgumentWithParam`` and ``forEachArgumentWithParamType`` to
997997
not skip the explicit object parameter for operator calls.
998998
- Fixed captureVars assertion failure if not capturesVariables. (#GH76425)
999+
- ``forCallable`` now properly preserves binding on successful match. (#GH89657)
9991000

10001001
clang-format
10011002
------------

clang/include/clang/ASTMatchers/ASTMatchers.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8371,20 +8371,28 @@ AST_MATCHER_P(Stmt, forCallable, internal::Matcher<Decl>, InnerMatcher) {
83718371
const auto &CurNode = Stack.back();
83728372
Stack.pop_back();
83738373
if (const auto *FuncDeclNode = CurNode.get<FunctionDecl>()) {
8374-
if (InnerMatcher.matches(*FuncDeclNode, Finder, Builder)) {
8374+
BoundNodesTreeBuilder B = *Builder;
8375+
if (InnerMatcher.matches(*FuncDeclNode, Finder, &B)) {
8376+
*Builder = std::move(B);
83758377
return true;
83768378
}
83778379
} else if (const auto *LambdaExprNode = CurNode.get<LambdaExpr>()) {
8380+
BoundNodesTreeBuilder B = *Builder;
83788381
if (InnerMatcher.matches(*LambdaExprNode->getCallOperator(), Finder,
8379-
Builder)) {
8382+
&B)) {
8383+
*Builder = std::move(B);
83808384
return true;
83818385
}
83828386
} else if (const auto *ObjCMethodDeclNode = CurNode.get<ObjCMethodDecl>()) {
8383-
if (InnerMatcher.matches(*ObjCMethodDeclNode, Finder, Builder)) {
8387+
BoundNodesTreeBuilder B = *Builder;
8388+
if (InnerMatcher.matches(*ObjCMethodDeclNode, Finder, &B)) {
8389+
*Builder = std::move(B);
83848390
return true;
83858391
}
83868392
} else if (const auto *BlockDeclNode = CurNode.get<BlockDecl>()) {
8387-
if (InnerMatcher.matches(*BlockDeclNode, Finder, Builder)) {
8393+
BoundNodesTreeBuilder B = *Builder;
8394+
if (InnerMatcher.matches(*BlockDeclNode, Finder, &B)) {
8395+
*Builder = std::move(B);
83888396
return true;
83898397
}
83908398
} else {

clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5973,6 +5973,37 @@ TEST(StatementMatcher, ForCallable) {
59735973
EXPECT_TRUE(notMatches(CppString2,
59745974
returnStmt(forCallable(functionDecl(hasName("F"))))));
59755975

5976+
StringRef CodeWithDeepCallExpr = R"cpp(
5977+
void Other();
5978+
void Function() {
5979+
{
5980+
(
5981+
Other()
5982+
);
5983+
}
5984+
}
5985+
)cpp";
5986+
auto ForCallableFirst =
5987+
callExpr(forCallable(functionDecl(hasName("Function"))),
5988+
callee(functionDecl(hasName("Other")).bind("callee")))
5989+
.bind("call");
5990+
auto ForCallableSecond =
5991+
callExpr(callee(functionDecl(hasName("Other")).bind("callee")),
5992+
forCallable(functionDecl(hasName("Function"))))
5993+
.bind("call");
5994+
EXPECT_TRUE(matchAndVerifyResultTrue(
5995+
CodeWithDeepCallExpr, ForCallableFirst,
5996+
std::make_unique<VerifyIdIsBoundTo<CallExpr>>("call")));
5997+
EXPECT_TRUE(matchAndVerifyResultTrue(
5998+
CodeWithDeepCallExpr, ForCallableFirst,
5999+
std::make_unique<VerifyIdIsBoundTo<FunctionDecl>>("callee")));
6000+
EXPECT_TRUE(matchAndVerifyResultTrue(
6001+
CodeWithDeepCallExpr, ForCallableSecond,
6002+
std::make_unique<VerifyIdIsBoundTo<CallExpr>>("call")));
6003+
EXPECT_TRUE(matchAndVerifyResultTrue(
6004+
CodeWithDeepCallExpr, ForCallableSecond,
6005+
std::make_unique<VerifyIdIsBoundTo<FunctionDecl>>("callee")));
6006+
59766007
// These tests are specific to forCallable().
59776008
StringRef ObjCString1 = "@interface I"
59786009
"-(void) foo;"
@@ -6014,6 +6045,105 @@ TEST(StatementMatcher, ForCallable) {
60146045
binaryOperator(forCallable(blockDecl()))));
60156046
}
60166047

6048+
namespace {
6049+
class ForCallablePreservesBindingWithMultipleParentsTestCallback
6050+
: public BoundNodesCallback {
6051+
public:
6052+
bool run(const BoundNodes *BoundNodes) override {
6053+
FunctionDecl const *FunDecl =
6054+
BoundNodes->getNodeAs<FunctionDecl>("funDecl");
6055+
// Validate test assumptions. This would be expressed as ASSERT_* in
6056+
// a TEST().
6057+
if (!FunDecl) {
6058+
EXPECT_TRUE(false && "Incorrect test setup");
6059+
return false;
6060+
}
6061+
auto const *FunDef = FunDecl->getDefinition();
6062+
if (!FunDef || !FunDef->getBody() ||
6063+
FunDef->getNameAsString() != "Function") {
6064+
EXPECT_TRUE(false && "Incorrect test setup");
6065+
return false;
6066+
}
6067+
6068+
ExpectCorrectResult(
6069+
"Baseline",
6070+
callExpr(callee(cxxMethodDecl().bind("callee"))).bind("call"), //
6071+
FunDecl);
6072+
6073+
ExpectCorrectResult("ForCallable first",
6074+
callExpr(forCallable(equalsNode(FunDecl)),
6075+
callee(cxxMethodDecl().bind("callee")))
6076+
.bind("call"),
6077+
FunDecl);
6078+
6079+
ExpectCorrectResult("ForCallable second",
6080+
callExpr(callee(cxxMethodDecl().bind("callee")),
6081+
forCallable(equalsNode(FunDecl)))
6082+
.bind("call"),
6083+
FunDecl);
6084+
6085+
// This value does not really matter: the EXPECT_* will set the exit code.
6086+
return true;
6087+
}
6088+
6089+
bool run(const BoundNodes *BoundNodes, ASTContext *Context) override {
6090+
return run(BoundNodes);
6091+
}
6092+
6093+
private:
6094+
void ExpectCorrectResult(StringRef LogInfo,
6095+
ArrayRef<BoundNodes> Results) const {
6096+
EXPECT_EQ(Results.size(), 1u) << LogInfo;
6097+
if (Results.empty())
6098+
return;
6099+
auto const &R = Results.front();
6100+
EXPECT_TRUE(R.getNodeAs<CallExpr>("call")) << LogInfo;
6101+
EXPECT_TRUE(R.getNodeAs<CXXMethodDecl>("callee")) << LogInfo;
6102+
}
6103+
6104+
template <typename MatcherT>
6105+
void ExpectCorrectResult(StringRef LogInfo, MatcherT Matcher,
6106+
FunctionDecl const *FunDef) const {
6107+
auto &Context = FunDef->getASTContext();
6108+
auto const &Results = match(findAll(Matcher), *FunDef->getBody(), Context);
6109+
ExpectCorrectResult(LogInfo, Results);
6110+
}
6111+
};
6112+
} // namespace
6113+
6114+
TEST(StatementMatcher, ForCallablePreservesBindingWithMultipleParents) {
6115+
// Tests in this file are fairly simple and therefore can rely on matches,
6116+
// matchAndVerifyResultTrue, etc. This test, however, needs a FunctionDecl* in
6117+
// order to call equalsNode in order to reproduce the observed issue (bindings
6118+
// being removed despite forCallable matching the node).
6119+
//
6120+
// Because of this and because the machinery to compile the code into an
6121+
// ASTUnit is not exposed outside matchAndVerifyResultConditionally, it is
6122+
// cheaper to have a custom BoundNodesCallback for the purpose of this test.
6123+
StringRef codeWithTemplateFunction = R"cpp(
6124+
struct Klass {
6125+
void Method();
6126+
template <typename T>
6127+
void Function(T t); // Declaration
6128+
};
6129+
6130+
void Instantiate(Klass k) {
6131+
k.Function(0);
6132+
}
6133+
6134+
template <typename T>
6135+
void Klass::Function(T t) { // Definition
6136+
// Compound statement has two parents: the declaration and the definition.
6137+
Method();
6138+
}
6139+
)cpp";
6140+
EXPECT_TRUE(matchAndVerifyResultTrue(
6141+
codeWithTemplateFunction,
6142+
callExpr(callee(functionDecl(hasName("Function")).bind("funDecl"))),
6143+
std::make_unique<
6144+
ForCallablePreservesBindingWithMultipleParentsTestCallback>()));
6145+
}
6146+
60176147
TEST(Matcher, ForEachOverriden) {
60186148
const auto ForEachOverriddenInClass = [](const char *ClassName) {
60196149
return cxxMethodDecl(ofClass(hasName(ClassName)), isVirtual(),

0 commit comments

Comments
 (0)