xref: /llvm-project/clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp (revision d768bf994f508d7eaf9541a568be3d71096febf5)
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 
19 namespace clang {
20 namespace ast_matchers {
21 using internal::DynTypedMatcher;
22 
23 #if GTEST_HAS_DEATH_TEST
TEST(HasNameDeathTest,DiesOnEmptyName)24 TEST(HasNameDeathTest, DiesOnEmptyName) {
25   ASSERT_DEBUG_DEATH({
26     DeclarationMatcher HasEmptyName = recordDecl(hasName(""));
27     EXPECT_TRUE(notMatches("class X {};", HasEmptyName));
28   }, "");
29 }
30 
TEST(HasNameDeathTest,DiesOnEmptyPattern)31 TEST(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 
44 AST_MATCHER(Decl, causeCrash) {
45   abort();
46   return true;
47 }
48 
49 TEST(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 
57 template <typename MatcherT>
58 static 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 }
100 TEST(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 
TEST(ConstructVariadic,MismatchedTypes_Regression)144 TEST(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().
AST_MATCHER_P(Decl,just,internal::Matcher<Decl>,AMatcher)154 AST_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 
TEST(AstMatcherPMacro,Works)160 TEST(AstMatcherPMacro, Works) {
161   DeclarationMatcher HasClassB = just(has(recordDecl(hasName("B")).bind("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 
AST_POLYMORPHIC_MATCHER_P(polymorphicHas,AST_POLYMORPHIC_SUPPORTED_TYPES (Decl,Stmt),internal::Matcher<Decl>,AMatcher)173 AST_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 
TEST(AstPolymorphicMatcherPMacro,Works)181 TEST(AstPolymorphicMatcherPMacro, Works) {
182   DeclarationMatcher HasClassB =
183       polymorphicHas(recordDecl(hasName("B")).bind("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(recordDecl(hasName("B")));
196 
197   EXPECT_TRUE(matches("void x() { class B {}; }", StatementHasClassB));
198 }
199 
TEST(MatchFinder,CheckProfiling)200 TEST(MatchFinder, CheckProfiling) {
201   MatchFinder::MatchFinderOptions Options;
202   llvm::StringMap<llvm::TimeRecord> Records;
203   Options.CheckProfiling.emplace(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(decl(), &Callback);
211   std::unique_ptr<FrontendActionFactory> Factory(
212       newFrontendActionFactory(&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 
219 class VerifyStartOfTranslationUnit : public MatchFinder::MatchCallback {
220 public:
VerifyStartOfTranslationUnit()221   VerifyStartOfTranslationUnit() : Called(false) {}
run(const MatchFinder::MatchResult & Result)222   void run(const MatchFinder::MatchResult &Result) override {
223     EXPECT_TRUE(Called);
224   }
onStartOfTranslationUnit()225   void onStartOfTranslationUnit() override { Called = true; }
226   bool Called;
227 };
228 
TEST(MatchFinder,InterceptsStartOfTranslationUnit)229 TEST(MatchFinder, InterceptsStartOfTranslationUnit) {
230   MatchFinder Finder;
231   VerifyStartOfTranslationUnit VerifyCallback;
232   Finder.addMatcher(decl(), &VerifyCallback);
233   std::unique_ptr<FrontendActionFactory> Factory(
234       newFrontendActionFactory(&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("int x;"));
240   ASSERT_TRUE(AST.get());
241   Finder.matchAST(AST->getASTContext());
242   EXPECT_TRUE(VerifyCallback.Called);
243 }
244 
245 class VerifyEndOfTranslationUnit : public MatchFinder::MatchCallback {
246 public:
VerifyEndOfTranslationUnit()247   VerifyEndOfTranslationUnit() : Called(false) {}
run(const MatchFinder::MatchResult & Result)248   void run(const MatchFinder::MatchResult &Result) override {
249     EXPECT_FALSE(Called);
250   }
onEndOfTranslationUnit()251   void onEndOfTranslationUnit() override { Called = true; }
252   bool Called;
253 };
254 
TEST(MatchFinder,InterceptsEndOfTranslationUnit)255 TEST(MatchFinder, InterceptsEndOfTranslationUnit) {
256   MatchFinder Finder;
257   VerifyEndOfTranslationUnit VerifyCallback;
258   Finder.addMatcher(decl(), &VerifyCallback);
259   std::unique_ptr<FrontendActionFactory> Factory(
260       newFrontendActionFactory(&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("int x;"));
266   ASSERT_TRUE(AST.get());
267   Finder.matchAST(AST->getASTContext());
268   EXPECT_TRUE(VerifyCallback.Called);
269 }
270 
TEST(Matcher,matchOverEntireASTContext)271 TEST(Matcher, matchOverEntireASTContext) {
272   std::unique_ptr<ASTUnit> AST =
273       clang::tooling::buildASTFromCode("struct { int *foo; };");
274   ASSERT_TRUE(AST.get());
275   auto PT = selectFirst<PointerType>(
276       "x", match(pointerType().bind("x"), AST->getASTContext()));
277   EXPECT_NE(nullptr, PT);
278 }
279 
TEST(DynTypedMatcherTest,TraversalKindForwardsToImpl)280 TEST(DynTypedMatcherTest, TraversalKindForwardsToImpl) {
281   auto M = DynTypedMatcher(decl());
282   EXPECT_FALSE(M.getTraversalKind());
283 
284   M = DynTypedMatcher(traverse(TK_AsIs, decl()));
285   EXPECT_THAT(M.getTraversalKind(), llvm::ValueIs(TK_AsIs));
286 }
287 
TEST(DynTypedMatcherTest,ConstructWithTraversalKindSetsTK)288 TEST(DynTypedMatcherTest, ConstructWithTraversalKindSetsTK) {
289   auto M = DynTypedMatcher(decl()).withTraversalKind(TK_AsIs);
290   EXPECT_THAT(M.getTraversalKind(), llvm::ValueIs(TK_AsIs));
291 }
292 
TEST(DynTypedMatcherTest,ConstructWithTraversalKindOverridesNestedTK)293 TEST(DynTypedMatcherTest, ConstructWithTraversalKindOverridesNestedTK) {
294   auto M = DynTypedMatcher(decl()).withTraversalKind(TK_AsIs).withTraversalKind(
295       TK_IgnoreUnlessSpelledInSource);
296   EXPECT_THAT(M.getTraversalKind(),
297               llvm::ValueIs(TK_IgnoreUnlessSpelledInSource));
298 }
299 
TEST(IsInlineMatcher,IsInline)300 TEST(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 
TEST(Matcher,IsExpansionInMainFileMatcher)313 TEST(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(std::make_pair("/other", "class X {};"));
319   EXPECT_TRUE(matchesConditionally("#include <other>\n",
320                                    recordDecl(isExpansionInMainFile()), false,
321                                    {"-isystem/"}, M));
322 }
323 
TEST(Matcher,IsExpansionInSystemHeader)324 TEST(Matcher, IsExpansionInSystemHeader) {
325   FileContentMappings M;
326   M.push_back(std::make_pair("/other", "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 
TEST(Matcher,IsExpansionInFileMatching)338 TEST(Matcher, IsExpansionInFileMatching) {
339   FileContentMappings M;
340   M.push_back(std::make_pair("/foo", "class A {};"));
341   M.push_back(std::make_pair("/bar", "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