Skip to content

Commit fe8cf2f

Browse files
authored
[clang][ASTMatchers] Fix forEachArgumentWithParam* for deducing "this" operator calls (#84887)
This is a follow-up commit of #84446. In this patch, I demonstrate that `forEachArgumentWithParam` and `forEachArgumentWithParamType` did not correctly handle the presence of the explicit object parameter for operator calls. Prior to this patch, the matcher would skip the first (and only) argument of the operator call if the explicit object param was used. Note that I had to move the definition of `isExplicitObjectMemberFunction`, to be declared before the matcher I fix to be visible. I also had to do some gymnastics for passing the language standard version command-line flags to the invocation as `matchAndVerifyResultTrue` wasn't really considered for non-c++11 code. See the that it always prepends `-std=gnu++11` to the command-line arguments. I workarounded it by accepting extra args, which get appended, thus possibly overriding the hardcoded arguments. I'm not sure if this qualifies for backporting to clang-18 (probably not because its not a crash, but a semantic problem), but I figure it might be useful for some vendors (like us). But we are also happy to cherry-pick this fix to downstream. Let me know if you want this to be backported or not. CPP-5074
1 parent af21659 commit fe8cf2f

File tree

4 files changed

+119
-41
lines changed

4 files changed

+119
-41
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ AST Matchers
450450

451451
- ``isInStdNamespace`` now supports Decl declared with ``extern "C++"``.
452452
- Add ``isExplicitObjectMemberFunction``.
453+
- Fixed ``forEachArgumentWithParam`` and ``forEachArgumentWithParamType`` to
454+
not skip the explicit object parameter for operator calls.
453455

454456
clang-format
455457
------------

clang/include/clang/ASTMatchers/ASTMatchers.h

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5032,6 +5032,25 @@ AST_POLYMORPHIC_MATCHER_P2(hasParameter,
50325032
&& InnerMatcher.matches(*Node.parameters()[N], Finder, Builder));
50335033
}
50345034

5035+
/// Matches if the given method declaration declares a member function with an
5036+
/// explicit object parameter.
5037+
///
5038+
/// Given
5039+
/// \code
5040+
/// struct A {
5041+
/// int operator-(this A, int);
5042+
/// void fun(this A &&self);
5043+
/// static int operator()(int);
5044+
/// int operator+(int);
5045+
/// };
5046+
/// \endcode
5047+
///
5048+
/// cxxMethodDecl(isExplicitObjectMemberFunction()) matches the first two
5049+
/// methods but not the last two.
5050+
AST_MATCHER(CXXMethodDecl, isExplicitObjectMemberFunction) {
5051+
return Node.isExplicitObjectMemberFunction();
5052+
}
5053+
50355054
/// Matches all arguments and their respective ParmVarDecl.
50365055
///
50375056
/// Given
@@ -5060,10 +5079,12 @@ AST_POLYMORPHIC_MATCHER_P2(forEachArgumentWithParam,
50605079
// argument of the method which should not be matched against a parameter, so
50615080
// we skip over it here.
50625081
BoundNodesTreeBuilder Matches;
5063-
unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
5064-
.matches(Node, Finder, &Matches)
5065-
? 1
5066-
: 0;
5082+
unsigned ArgIndex =
5083+
cxxOperatorCallExpr(
5084+
callee(cxxMethodDecl(unless(isExplicitObjectMemberFunction()))))
5085+
.matches(Node, Finder, &Matches)
5086+
? 1
5087+
: 0;
50675088
int ParamIndex = 0;
50685089
bool Matched = false;
50695090
for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {
@@ -5121,11 +5142,12 @@ AST_POLYMORPHIC_MATCHER_P2(forEachArgumentWithParamType,
51215142
// argument of the method which should not be matched against a parameter, so
51225143
// we skip over it here.
51235144
BoundNodesTreeBuilder Matches;
5124-
unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
5125-
.matches(Node, Finder, &Matches)
5126-
? 1
5127-
: 0;
5128-
5145+
unsigned ArgIndex =
5146+
cxxOperatorCallExpr(
5147+
callee(cxxMethodDecl(unless(isExplicitObjectMemberFunction()))))
5148+
.matches(Node, Finder, &Matches)
5149+
? 1
5150+
: 0;
51295151
const FunctionProtoType *FProto = nullptr;
51305152

51315153
if (const auto *Call = dyn_cast<CallExpr>(&Node)) {
@@ -6366,25 +6388,6 @@ AST_MATCHER(CXXMethodDecl, isConst) {
63666388
return Node.isConst();
63676389
}
63686390

6369-
/// Matches if the given method declaration declares a member function with an
6370-
/// explicit object parameter.
6371-
///
6372-
/// Given
6373-
/// \code
6374-
/// struct A {
6375-
/// int operator-(this A, int);
6376-
/// void fun(this A &&self);
6377-
/// static int operator()(int);
6378-
/// int operator+(int);
6379-
/// };
6380-
/// \endcode
6381-
///
6382-
/// cxxMethodDecl(isExplicitObjectMemberFunction()) matches the first two
6383-
/// methods but not the last two.
6384-
AST_MATCHER(CXXMethodDecl, isExplicitObjectMemberFunction) {
6385-
return Node.isExplicitObjectMemberFunction();
6386-
}
6387-
63886391
/// Matches if the given method declaration declares a copy assignment
63896392
/// operator.
63906393
///

clang/unittests/ASTMatchers/ASTMatchersTest.h

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,8 @@ testing::AssertionResult notMatchesWithOpenMP51(const Twine &Code,
293293
template <typename T>
294294
testing::AssertionResult matchAndVerifyResultConditionally(
295295
const Twine &Code, const T &AMatcher,
296-
std::unique_ptr<BoundNodesCallback> FindResultVerifier, bool ExpectResult) {
296+
std::unique_ptr<BoundNodesCallback> FindResultVerifier, bool ExpectResult,
297+
ArrayRef<std::string> Args = {}, StringRef Filename = "input.cc") {
297298
bool VerifiedResult = false;
298299
MatchFinder Finder;
299300
VerifyMatch VerifyVerifiedResult(std::move(FindResultVerifier),
@@ -304,9 +305,13 @@ testing::AssertionResult matchAndVerifyResultConditionally(
304305
// Some tests use typeof, which is a gnu extension. Using an explicit
305306
// unknown-unknown triple is good for a large speedup, because it lets us
306307
// avoid constructing a full system triple.
307-
std::vector<std::string> Args = {"-std=gnu++11", "-target",
308-
"i386-unknown-unknown"};
309-
if (!runToolOnCodeWithArgs(Factory->create(), Code, Args)) {
308+
std::vector<std::string> CompileArgs = {"-std=gnu++11", "-target",
309+
"i386-unknown-unknown"};
310+
// Append additional arguments at the end to allow overriding the default
311+
// choices that we made above.
312+
llvm::copy(Args, std::back_inserter(CompileArgs));
313+
314+
if (!runToolOnCodeWithArgs(Factory->create(), Code, CompileArgs, Filename)) {
310315
return testing::AssertionFailure() << "Parsing error in \"" << Code << "\"";
311316
}
312317
if (!VerifiedResult && ExpectResult) {
@@ -319,8 +324,8 @@ testing::AssertionResult matchAndVerifyResultConditionally(
319324

320325
VerifiedResult = false;
321326
SmallString<256> Buffer;
322-
std::unique_ptr<ASTUnit> AST(
323-
buildASTFromCodeWithArgs(Code.toStringRef(Buffer), Args));
327+
std::unique_ptr<ASTUnit> AST(buildASTFromCodeWithArgs(
328+
Code.toStringRef(Buffer), CompileArgs, Filename));
324329
if (!AST.get())
325330
return testing::AssertionFailure()
326331
<< "Parsing error in \"" << Code << "\" while building AST";
@@ -339,19 +344,24 @@ testing::AssertionResult matchAndVerifyResultConditionally(
339344
// FIXME: Find better names for these functions (or document what they
340345
// do more precisely).
341346
template <typename T>
342-
testing::AssertionResult matchAndVerifyResultTrue(
343-
const Twine &Code, const T &AMatcher,
344-
std::unique_ptr<BoundNodesCallback> FindResultVerifier) {
345-
return matchAndVerifyResultConditionally(Code, AMatcher,
346-
std::move(FindResultVerifier), true);
347+
testing::AssertionResult
348+
matchAndVerifyResultTrue(const Twine &Code, const T &AMatcher,
349+
std::unique_ptr<BoundNodesCallback> FindResultVerifier,
350+
ArrayRef<std::string> Args = {},
351+
StringRef Filename = "input.cc") {
352+
return matchAndVerifyResultConditionally(
353+
Code, AMatcher, std::move(FindResultVerifier),
354+
/*ExpectResult=*/true, Args, Filename);
347355
}
348356

349357
template <typename T>
350358
testing::AssertionResult matchAndVerifyResultFalse(
351359
const Twine &Code, const T &AMatcher,
352-
std::unique_ptr<BoundNodesCallback> FindResultVerifier) {
360+
std::unique_ptr<BoundNodesCallback> FindResultVerifier,
361+
ArrayRef<std::string> Args = {}, StringRef Filename = "input.cc") {
353362
return matchAndVerifyResultConditionally(
354-
Code, AMatcher, std::move(FindResultVerifier), false);
363+
Code, AMatcher, std::move(FindResultVerifier),
364+
/*ExpectResult=*/false, Args, Filename);
355365
}
356366

357367
// Implements a run method that returns whether BoundNodes contains a

clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,38 @@ TEST(ForEachArgumentWithParam, HandlesBoundNodesForNonMatches) {
985985
std::make_unique<VerifyIdIsBoundTo<VarDecl>>("v", 4)));
986986
}
987987

988+
TEST_P(ASTMatchersTest,
989+
ForEachArgumentWithParamMatchesExplicitObjectParamOnOperatorCalls) {
990+
if (!GetParam().isCXX23OrLater()) {
991+
return;
992+
}
993+
994+
auto DeclRef = declRefExpr(to(varDecl().bind("declOfArg"))).bind("arg");
995+
auto SelfParam = parmVarDecl().bind("param");
996+
StatementMatcher CallExpr =
997+
callExpr(forEachArgumentWithParam(DeclRef, SelfParam));
998+
999+
StringRef S = R"cpp(
1000+
struct A {
1001+
int operator()(this const A &self);
1002+
};
1003+
A obj;
1004+
int global = obj();
1005+
)cpp";
1006+
1007+
auto Args = GetParam().getCommandLineArgs();
1008+
auto Filename = getFilenameForTesting(GetParam().Language);
1009+
1010+
EXPECT_TRUE(matchAndVerifyResultTrue(
1011+
S, CallExpr,
1012+
std::make_unique<VerifyIdIsBoundTo<ParmVarDecl>>("param", "self"), Args,
1013+
Filename));
1014+
EXPECT_TRUE(matchAndVerifyResultTrue(
1015+
S, CallExpr,
1016+
std::make_unique<VerifyIdIsBoundTo<VarDecl>>("declOfArg", "obj"), Args,
1017+
Filename));
1018+
}
1019+
9881020
TEST(ForEachArgumentWithParamType, ReportsNoFalsePositives) {
9891021
StatementMatcher ArgumentY =
9901022
declRefExpr(to(varDecl(hasName("y")))).bind("arg");
@@ -1168,6 +1200,37 @@ TEST(ForEachArgumentWithParamType, MatchesVariadicFunctionPtrCalls) {
11681200
S, CallExpr, std::make_unique<VerifyIdIsBoundTo<DeclRefExpr>>("arg")));
11691201
}
11701202

1203+
TEST_P(ASTMatchersTest,
1204+
ForEachArgumentWithParamTypeMatchesExplicitObjectParamOnOperatorCalls) {
1205+
if (!GetParam().isCXX23OrLater()) {
1206+
return;
1207+
}
1208+
1209+
auto DeclRef = declRefExpr(to(varDecl().bind("declOfArg"))).bind("arg");
1210+
auto SelfTy = qualType(asString("const A &")).bind("selfType");
1211+
StatementMatcher CallExpr =
1212+
callExpr(forEachArgumentWithParamType(DeclRef, SelfTy));
1213+
1214+
StringRef S = R"cpp(
1215+
struct A {
1216+
int operator()(this const A &self);
1217+
};
1218+
A obj;
1219+
int global = obj();
1220+
)cpp";
1221+
1222+
auto Args = GetParam().getCommandLineArgs();
1223+
auto Filename = getFilenameForTesting(GetParam().Language);
1224+
1225+
EXPECT_TRUE(matchAndVerifyResultTrue(
1226+
S, CallExpr, std::make_unique<VerifyIdIsBoundTo<QualType>>("selfType"),
1227+
Args, Filename));
1228+
EXPECT_TRUE(matchAndVerifyResultTrue(
1229+
S, CallExpr,
1230+
std::make_unique<VerifyIdIsBoundTo<VarDecl>>("declOfArg", "obj"), Args,
1231+
Filename));
1232+
}
1233+
11711234
TEST(QualType, hasCanonicalType) {
11721235
EXPECT_TRUE(notMatches("typedef int &int_ref;"
11731236
"int a;"

0 commit comments

Comments
 (0)