// unittests/ASTMatchers/ASTMatchersInternalTest.cpp - AST matcher unit tests //
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ASTMatchersTest.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Support/Host.h"
#include "gtest/gtest.h"
namespace clang {
namespace ast_matchers {
#if GTEST_HAS_DEATH_TEST
TEST(HasNameDeathTest, DiesOnEmptyName) {
ASSERT_DEBUG_DEATH({
DeclarationMatcher HasEmptyName = recordDecl(hasName(""));
EXPECT_TRUE(notMatches("class X {};", HasEmptyName));
}, "");
}
TEST(HasNameDeathTest, DiesOnEmptyPattern) {
ASSERT_DEBUG_DEATH({
DeclarationMatcher HasEmptyName = recordDecl(matchesName(""));
EXPECT_TRUE(notMatches("class X {};", HasEmptyName));
}, "");
}
TEST(IsDerivedFromDeathTest, DiesOnEmptyBaseName) {
ASSERT_DEBUG_DEATH({
DeclarationMatcher IsDerivedFromEmpty = cxxRecordDecl(isDerivedFrom(""));
EXPECT_TRUE(notMatches("class X {};", IsDerivedFromEmpty));
}, "");
}
#endif
TEST(ConstructVariadic, MismatchedTypes_Regression) {
EXPECT_TRUE(
matches("const int a = 0;",
internal::DynTypedMatcher::constructVariadic(
internal::DynTypedMatcher::VO_AnyOf,
ast_type_traits::ASTNodeKind::getFromNodeKind<QualType>(),
{isConstQualified(), arrayType()})
.convertTo<QualType>()));
}
// For testing AST_MATCHER_P().
AST_MATCHER_P(Decl, just, internal::Matcher<Decl>, AMatcher) {
// Make sure all special variables are used: node, match_finder,
// bound_nodes_builder, and the parameter named 'AMatcher'.
return AMatcher.matches(Node, Finder, Builder);
}
TEST(AstMatcherPMacro, Works) {
DeclarationMatcher HasClassB = just(has(recordDecl(hasName("B")).bind("b")));
EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };",
HasClassB, llvm::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };",
HasClassB, llvm::make_unique<VerifyIdIsBoundTo<Decl>>("a")));
EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };",
HasClassB, llvm::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
}
AST_POLYMORPHIC_MATCHER_P(polymorphicHas,
AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt),
internal::Matcher<Decl>, AMatcher) {
return Finder->matchesChildOf(
Node, AMatcher, Builder,
ASTMatchFinder::TK_IgnoreImplicitCastsAndParentheses,
ASTMatchFinder::BK_First);
}
TEST(AstPolymorphicMatcherPMacro, Works) {
DeclarationMatcher HasClassB =
polymorphicHas(recordDecl(hasName("B")).bind("b"));
EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };",
HasClassB, llvm::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };",
HasClassB, llvm::make_unique<VerifyIdIsBoundTo<Decl>>("a")));
EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };",
HasClassB, llvm::make_unique<VerifyIdIsBoundTo<Decl>>("b")));
StatementMatcher StatementHasClassB =
polymorphicHas(recordDecl(hasName("B")));
EXPECT_TRUE(matches("void x() { class B {}; }", StatementHasClassB));
}
TEST(MatchFinder, CheckProfiling) {
MatchFinder::MatchFinderOptions Options;
llvm::StringMap<llvm::TimeRecord> Records;
Options.CheckProfiling.emplace(Records);
MatchFinder Finder(std::move(Options));
struct NamedCallback : public MatchFinder::MatchCallback {
void run(const MatchFinder::MatchResult &Result) override {}
StringRef getID() const override { return "MyID"; }
} Callback;
Finder.addMatcher(decl(), &Callback);
std::unique_ptr<FrontendActionFactory> Factory(
newFrontendActionFactory(&Finder));
ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;"));
EXPECT_EQ(1u, Records.size());
EXPECT_EQ("MyID", Records.begin()->getKey());
}
class VerifyStartOfTranslationUnit : public MatchFinder::MatchCallback {
public:
VerifyStartOfTranslationUnit() : Called(false) {}
void run(const MatchFinder::MatchResult &Result) override {
EXPECT_TRUE(Called);
}
void onStartOfTranslationUnit() override { Called = true; }
bool Called;
};
TEST(MatchFinder, InterceptsStartOfTranslationUnit) {
MatchFinder Finder;
VerifyStartOfTranslationUnit VerifyCallback;
Finder.addMatcher(decl(), &VerifyCallback);
std::unique_ptr<FrontendActionFactory> Factory(
newFrontendActionFactory(&Finder));
ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;"));
EXPECT_TRUE(VerifyCallback.Called);
VerifyCallback.Called = false;
std::unique_ptr<ASTUnit> AST(tooling::buildASTFromCode("int x;"));
ASSERT_TRUE(AST.get());
Finder.matchAST(AST->getASTContext());
EXPECT_TRUE(VerifyCallback.Called);
}
class VerifyEndOfTranslationUnit : public MatchFinder::MatchCallback {
public:
VerifyEndOfTranslationUnit() : Called(false) {}
void run(const MatchFinder::MatchResult &Result) override {
EXPECT_FALSE(Called);
}
void onEndOfTranslationUnit() override { Called = true; }
bool Called;
};
TEST(MatchFinder, InterceptsEndOfTranslationUnit) {
MatchFinder Finder;
VerifyEndOfTranslationUnit VerifyCallback;
Finder.addMatcher(decl(), &VerifyCallback);
std::unique_ptr<FrontendActionFactory> Factory(
newFrontendActionFactory(&Finder));
ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;"));
EXPECT_TRUE(VerifyCallback.Called);
VerifyCallback.Called = false;
std::unique_ptr<ASTUnit> AST(tooling::buildASTFromCode("int x;"));
ASSERT_TRUE(AST.get());
Finder.matchAST(AST->getASTContext());
EXPECT_TRUE(VerifyCallback.Called);
}
TEST(Matcher, matchOverEntireASTContext) {
std::unique_ptr<ASTUnit> AST =
clang::tooling::buildASTFromCode("struct { int *foo; };");
ASSERT_TRUE(AST.get());
auto PT = selectFirst<PointerType>(
"x", match(pointerType().bind("x"), AST->getASTContext()));
EXPECT_NE(nullptr, PT);
}
TEST(IsInlineMatcher, IsInline) {
EXPECT_TRUE(matches("void g(); inline void f();",
functionDecl(isInline(), hasName("f"))));
EXPECT_TRUE(matches("namespace n { inline namespace m {} }",
namespaceDecl(isInline(), hasName("m"))));
}
// FIXME: Figure out how to specify paths so the following tests pass on
// Windows.
#ifndef LLVM_ON_WIN32
TEST(Matcher, IsExpansionInMainFileMatcher) {
EXPECT_TRUE(matches("class X {};",
recordDecl(hasName("X"), isExpansionInMainFile())));
EXPECT_TRUE(notMatches("", recordDecl(isExpansionInMainFile())));
FileContentMappings M;
M.push_back(std::make_pair("/other", "class X {};"));
EXPECT_TRUE(matchesConditionally("#include <other>\n",
recordDecl(isExpansionInMainFile()), false,
"-isystem/", M));
}
TEST(Matcher, IsExpansionInSystemHeader) {
FileContentMappings M;
M.push_back(std::make_pair("/other", "class X {};"));
EXPECT_TRUE(matchesConditionally(
"#include \"other\"\n", recordDecl(isExpansionInSystemHeader()), true,
"-isystem/", M));
EXPECT_TRUE(matchesConditionally("#include \"other\"\n",
recordDecl(isExpansionInSystemHeader()),
false, "-I/", M));
EXPECT_TRUE(notMatches("class X {};",
recordDecl(isExpansionInSystemHeader())));
EXPECT_TRUE(notMatches("", recordDecl(isExpansionInSystemHeader())));
}
TEST(Matcher, IsExpansionInFileMatching) {
FileContentMappings M;
M.push_back(std::make_pair("/foo", "class A {};"));
M.push_back(std::make_pair("/bar", "class B {};"));
EXPECT_TRUE(matchesConditionally(
"#include <foo>\n"
"#include <bar>\n"
"class X {};",
recordDecl(isExpansionInFileMatching("b.*"), hasName("B")), true,
"-isystem/", M));
EXPECT_TRUE(matchesConditionally(
"#include <foo>\n"
"#include <bar>\n"
"class X {};",
recordDecl(isExpansionInFileMatching("f.*"), hasName("X")), false,
"-isystem/", M));
}
#endif // LLVM_ON_WIN32
} // end namespace ast_matchers
} // end namespace clang