1// unittests/ASTMatchers/ASTMatchersInternalTest.cpp - AST matcher unit tests //
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ASTMatchersTest.h"
10#include "clang/AST/PrettyPrinter.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/ASTMatchers/ASTMatchers.h"
13#include "clang/Tooling/Tooling.h"
14#include "llvm/TargetParser/Host.h"
15#include "llvm/TargetParser/Triple.h"
16#include "llvm/Testing/Support/SupportHelpers.h"
17#include "gtest/gtest.h"
18
19namespace clang {
20namespace ast_matchers {
21using internal::DynTypedMatcher;
22
23#if GTEST_HAS_DEATH_TEST
24TEST(HasNameDeathTest, DiesOnEmptyName) {
25 ASSERT_DEBUG_DEATH({
26 DeclarationMatcher HasEmptyName = recordDecl(hasName(""));
27 EXPECT_TRUE(notMatches("class X {};", HasEmptyName));
28 }, "");
29}
30
31TEST(HasNameDeathTest, DiesOnEmptyPattern) {
32 ASSERT_DEBUG_DEATH({
33 DeclarationMatcher HasEmptyName = recordDecl(matchesName(""));
34 EXPECT_TRUE(notMatches("class X {};", HasEmptyName));
35 }, "");
36}
37
38// FIXME Re-enable these tests without breaking standalone builds.
39#if 0
40// FIXME: Figure out why back traces aren't being generated on clang builds on
41// windows.
42#if ENABLE_BACKTRACES && (!defined(_MSC_VER) || !defined(__clang__))
43
44AST_MATCHER(Decl, causeCrash) {
45 abort();
46 return true;
47}
48
49TEST(MatcherCrashDeathTest, CrashOnMatcherDump) {
50 llvm::EnablePrettyStackTrace();
51 auto Matcher = testing::HasSubstr(
52 "ASTMatcher: Matching '<unknown>' against:\n\tFunctionDecl foo : "
53 "<input.cc:1:1, col:10>");
54 ASSERT_DEATH(matches("void foo();", functionDecl(causeCrash())), Matcher);
55}
56
57template <typename MatcherT>
58static void crashTestNodeDump(MatcherT Matcher,
59 ArrayRef<StringRef> MatchedNodes,
60 StringRef Against, StringRef Code) {
61 llvm::EnablePrettyStackTrace();
62 MatchFinder Finder;
63
64 struct CrashCallback : public MatchFinder::MatchCallback {
65 void run(const MatchFinder::MatchResult &Result) override { abort(); }
66 std::optional<TraversalKind> getCheckTraversalKind() const override {
67 return TK_IgnoreUnlessSpelledInSource;
68 }
69 StringRef getID() const override { return "CrashTester"; }
70 } Callback;
71 Finder.addMatcher(std::move(Matcher), &Callback);
72 if (MatchedNodes.empty()) {
73 ASSERT_DEATH(tooling::runToolOnCode(
74 newFrontendActionFactory(&Finder)->create(), Code),
75 testing::HasSubstr(
76 ("ASTMatcher: Processing 'CrashTester' against:\n\t" +
77 Against + "\nNo bound nodes")
78 .str()));
79 } else {
80 std::vector<testing::PolymorphicMatcher<
81 testing::internal::HasSubstrMatcher<std::string>>>
82 Matchers;
83 Matchers.reserve(MatchedNodes.size());
84 for (auto Node : MatchedNodes) {
85 Matchers.push_back(testing::HasSubstr(Node.str()));
86 }
87 auto CrashMatcher = testing::AllOf(
88 testing::HasSubstr(
89 ("ASTMatcher: Processing 'CrashTester' against:\n\t" + Against +
90 "\n--- Bound Nodes Begin ---")
91 .str()),
92 testing::HasSubstr("--- Bound Nodes End ---"),
93 testing::AllOfArray(Matchers));
94
95 ASSERT_DEATH(tooling::runToolOnCode(
96 newFrontendActionFactory(&Finder)->create(), Code),
97 CrashMatcher);
98 }
99}
100TEST(MatcherCrashDeathTest, CrashOnCallbackDump) {
101 crashTestNodeDump(forStmt(), {}, "ForStmt : <input.cc:1:14, col:21>",
102 "void foo() { for(;;); }");
103 crashTestNodeDump(
104 forStmt(hasLoopInit(declStmt(hasSingleDecl(
105 varDecl(hasType(qualType().bind("QT")),
106 hasType(type().bind("T")),
107 hasInitializer(
108 integerLiteral().bind("IL")))
109 .bind("VD")))
110 .bind("DS")))
111 .bind("FS"),
112 {"FS - { ForStmt : <input.cc:3:5, line:4:5> }",
113 "DS - { DeclStmt : <input.cc:3:10, col:19> }",
114 "IL - { IntegerLiteral : <input.cc:3:18> }", "QT - { QualType : int }",
115 "T - { BuiltinType : int }",
116 "VD - { VarDecl I : <input.cc:3:10, col:18> }"},
117 "ForStmt : <input.cc:3:5, line:4:5>",
118 R"cpp(
119 void foo() {
120 for (int I = 0; I < 5; ++I) {
121 }
122 }
123 )cpp");
124 crashTestNodeDump(
125 cxxRecordDecl(hasMethod(cxxMethodDecl(hasName("operator+")).bind("Op+")))
126 .bind("Unnamed"),
127 {"Unnamed - { CXXRecordDecl (anonymous) : <input.cc:1:1, col:36> }",
128 "Op+ - { CXXMethodDecl (anonymous struct)::operator+ : <input.cc:1:10, "
129 "col:29> }"},
130 "CXXRecordDecl (anonymous) : <input.cc:1:1, col:36>",
131 "struct { int operator+(int) const; } Unnamed;");
132 crashTestNodeDump(
133 cxxRecordDecl(hasMethod(cxxConstructorDecl(isDefaulted()).bind("Ctor")),
134 hasMethod(cxxDestructorDecl(isDefaulted()).bind("Dtor"))),
135 {"Ctor - { CXXConstructorDecl Foo::Foo : <input.cc:1:14, col:28> }",
136 "Dtor - { CXXDestructorDecl Foo::~Foo : <input.cc:1:31, col:46> }"},
137 "CXXRecordDecl Foo : <input.cc:1:1, col:49>",
138 "struct Foo { Foo() = default; ~Foo() = default; };");
139}
140#endif // ENABLE_BACKTRACES
141#endif
142#endif
143
144TEST(ConstructVariadic, MismatchedTypes_Regression) {
145 EXPECT_TRUE(
146 matches("const int a = 0;", internal::DynTypedMatcher::constructVariadic(
147 internal::DynTypedMatcher::VO_AnyOf,
148 ASTNodeKind::getFromNodeKind<QualType>(),
149 {isConstQualified(), arrayType()})
150 .convertTo<QualType>()));
151}
152
153// For testing AST_MATCHER_P().
154AST_MATCHER_P(Decl, just, internal::Matcher<Decl>, AMatcher) {
155 // Make sure all special variables are used: node, match_finder,
156 // bound_nodes_builder, and the parameter named 'AMatcher'.
157 return AMatcher.matches(Node, Finder, Builder);
158}
159
160TEST(AstMatcherPMacro, Works) {
161 DeclarationMatcher HasClassB = just(AMatcher: has(recordDecl(hasName(Name: "B")).bind(ID: "b")));
162
163 EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };",
164 HasClassB, std::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
165
166 EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };",
167 HasClassB, std::make_unique<VerifyIdIsBoundTo<Decl>>("a")));
168
169 EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };",
170 HasClassB, std::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
171}
172
173AST_POLYMORPHIC_MATCHER_P(polymorphicHas,
174 AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt),
175 internal::Matcher<Decl>, AMatcher) {
176 return Finder->matchesChildOf(
177 Node, AMatcher, Builder,
178 ASTMatchFinder::BK_First);
179}
180
181TEST(AstPolymorphicMatcherPMacro, Works) {
182 DeclarationMatcher HasClassB =
183 polymorphicHas(AMatcher: recordDecl(hasName(Name: "B")).bind(ID: "b"));
184
185 EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };",
186 HasClassB, std::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
187
188 EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };",
189 HasClassB, std::make_unique<VerifyIdIsBoundTo<Decl>>("a")));
190
191 EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };",
192 HasClassB, std::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
193
194 StatementMatcher StatementHasClassB =
195 polymorphicHas(AMatcher: recordDecl(hasName(Name: "B")));
196
197 EXPECT_TRUE(matches("void x() { class B {}; }", StatementHasClassB));
198}
199
200TEST(MatchFinder, CheckProfiling) {
201 MatchFinder::MatchFinderOptions Options;
202 llvm::StringMap<llvm::TimeRecord> Records;
203 Options.CheckProfiling.emplace(args&: Records);
204 MatchFinder Finder(std::move(Options));
205
206 struct NamedCallback : public MatchFinder::MatchCallback {
207 void run(const MatchFinder::MatchResult &Result) override {}
208 StringRef getID() const override { return "MyID"; }
209 } Callback;
210 Finder.addMatcher(NodeMatch: decl(), Action: &Callback);
211 std::unique_ptr<FrontendActionFactory> Factory(
212 newFrontendActionFactory(ConsumerFactory: &Finder));
213 ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;"));
214
215 EXPECT_EQ(1u, Records.size());
216 EXPECT_EQ("MyID", Records.begin()->getKey());
217}
218
219class VerifyStartOfTranslationUnit : public MatchFinder::MatchCallback {
220public:
221 VerifyStartOfTranslationUnit() : Called(false) {}
222 void run(const MatchFinder::MatchResult &Result) override {
223 EXPECT_TRUE(Called);
224 }
225 void onStartOfTranslationUnit() override { Called = true; }
226 bool Called;
227};
228
229TEST(MatchFinder, InterceptsStartOfTranslationUnit) {
230 MatchFinder Finder;
231 VerifyStartOfTranslationUnit VerifyCallback;
232 Finder.addMatcher(NodeMatch: decl(), Action: &VerifyCallback);
233 std::unique_ptr<FrontendActionFactory> Factory(
234 newFrontendActionFactory(ConsumerFactory: &Finder));
235 ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;"));
236 EXPECT_TRUE(VerifyCallback.Called);
237
238 VerifyCallback.Called = false;
239 std::unique_ptr<ASTUnit> AST(tooling::buildASTFromCode(Code: "int x;"));
240 ASSERT_TRUE(AST.get());
241 Finder.matchAST(Context&: AST->getASTContext());
242 EXPECT_TRUE(VerifyCallback.Called);
243}
244
245class VerifyEndOfTranslationUnit : public MatchFinder::MatchCallback {
246public:
247 VerifyEndOfTranslationUnit() : Called(false) {}
248 void run(const MatchFinder::MatchResult &Result) override {
249 EXPECT_FALSE(Called);
250 }
251 void onEndOfTranslationUnit() override { Called = true; }
252 bool Called;
253};
254
255TEST(MatchFinder, InterceptsEndOfTranslationUnit) {
256 MatchFinder Finder;
257 VerifyEndOfTranslationUnit VerifyCallback;
258 Finder.addMatcher(NodeMatch: decl(), Action: &VerifyCallback);
259 std::unique_ptr<FrontendActionFactory> Factory(
260 newFrontendActionFactory(ConsumerFactory: &Finder));
261 ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;"));
262 EXPECT_TRUE(VerifyCallback.Called);
263
264 VerifyCallback.Called = false;
265 std::unique_ptr<ASTUnit> AST(tooling::buildASTFromCode(Code: "int x;"));
266 ASSERT_TRUE(AST.get());
267 Finder.matchAST(Context&: AST->getASTContext());
268 EXPECT_TRUE(VerifyCallback.Called);
269}
270
271TEST(Matcher, matchOverEntireASTContext) {
272 std::unique_ptr<ASTUnit> AST =
273 clang::tooling::buildASTFromCode(Code: "struct { int *foo; };");
274 ASSERT_TRUE(AST.get());
275 auto PT = selectFirst<PointerType>(
276 BoundTo: "x", Results: match(Matcher: pointerType().bind(ID: "x"), Context&: AST->getASTContext()));
277 EXPECT_NE(nullptr, PT);
278}
279
280TEST(DynTypedMatcherTest, TraversalKindForwardsToImpl) {
281 auto M = DynTypedMatcher(decl());
282 EXPECT_FALSE(M.getTraversalKind());
283
284 M = DynTypedMatcher(traverse(TK: TK_AsIs, InnerMatcher: decl()));
285 EXPECT_THAT(M.getTraversalKind(), llvm::ValueIs(TK_AsIs));
286}
287
288TEST(DynTypedMatcherTest, ConstructWithTraversalKindSetsTK) {
289 auto M = DynTypedMatcher(decl()).withTraversalKind(TK: TK_AsIs);
290 EXPECT_THAT(M.getTraversalKind(), llvm::ValueIs(TK_AsIs));
291}
292
293TEST(DynTypedMatcherTest, ConstructWithTraversalKindOverridesNestedTK) {
294 auto M = DynTypedMatcher(decl()).withTraversalKind(TK: TK_AsIs).withTraversalKind(
295 TK: TK_IgnoreUnlessSpelledInSource);
296 EXPECT_THAT(M.getTraversalKind(),
297 llvm::ValueIs(TK_IgnoreUnlessSpelledInSource));
298}
299
300TEST(IsInlineMatcher, IsInline) {
301 EXPECT_TRUE(matches("void g(); inline void f();",
302 functionDecl(isInline(), hasName("f"))));
303 EXPECT_TRUE(matches("namespace n { inline namespace m {} }",
304 namespaceDecl(isInline(), hasName("m"))));
305 EXPECT_TRUE(matches("inline int Foo = 5;",
306 varDecl(isInline(), hasName("Foo")), {Lang_CXX17}));
307}
308
309// FIXME: Figure out how to specify paths so the following tests pass on
310// Windows.
311#ifndef _WIN32
312
313TEST(Matcher, IsExpansionInMainFileMatcher) {
314 EXPECT_TRUE(matches("class X {};",
315 recordDecl(hasName("X"), isExpansionInMainFile())));
316 EXPECT_TRUE(notMatches("", recordDecl(isExpansionInMainFile())));
317 FileContentMappings M;
318 M.push_back(x: std::make_pair(x: "/other", y: "class X {};"));
319 EXPECT_TRUE(matchesConditionally("#include <other>\n",
320 recordDecl(isExpansionInMainFile()), false,
321 {"-isystem/"}, M));
322}
323
324TEST(Matcher, IsExpansionInSystemHeader) {
325 FileContentMappings M;
326 M.push_back(x: std::make_pair(x: "/other", y: "class X {};"));
327 EXPECT_TRUE(matchesConditionally("#include \"other\"\n",
328 recordDecl(isExpansionInSystemHeader()),
329 true, {"-isystem/"}, M));
330 EXPECT_TRUE(matchesConditionally("#include \"other\"\n",
331 recordDecl(isExpansionInSystemHeader()),
332 false, {"-I/"}, M));
333 EXPECT_TRUE(notMatches("class X {};",
334 recordDecl(isExpansionInSystemHeader())));
335 EXPECT_TRUE(notMatches("", recordDecl(isExpansionInSystemHeader())));
336}
337
338TEST(Matcher, IsExpansionInFileMatching) {
339 FileContentMappings M;
340 M.push_back(x: std::make_pair(x: "/foo", y: "class A {};"));
341 M.push_back(x: std::make_pair(x: "/bar", y: "class B {};"));
342 EXPECT_TRUE(matchesConditionally(
343 "#include <foo>\n"
344 "#include <bar>\n"
345 "class X {};",
346 recordDecl(isExpansionInFileMatching("b.*"), hasName("B")), true,
347 {"-isystem/"}, M));
348 EXPECT_TRUE(matchesConditionally(
349 "#include <foo>\n"
350 "#include <bar>\n"
351 "class X {};",
352 recordDecl(isExpansionInFileMatching("f.*"), hasName("X")), false,
353 {"-isystem/"}, M));
354}
355
356#endif // _WIN32
357
358} // end namespace ast_matchers
359} // end namespace clang
360

source code of clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp