Introduce clang-query tool.

This tool is for interactive exploration of the Clang AST using AST matchers.
It currently allows the user to enter a matcher at an interactive prompt
and view the resulting bindings as diagnostics, AST pretty prints or AST
dumps. Example session:

$ cat foo.c
void foo(void) {}
$ clang-query foo.c --
clang-query> match functionDecl()

Match #1:

foo.c:1:1: note: "root" binds here
void foo(void) {}
^~~~~~~~~~~~~~~~~
1 match.

Differential Revision: http://llvm-reviews.chandlerc.com/D2098

llvm-svn: 194227
This commit is contained in:
Peter Collingbourne 2013-11-08 00:08:23 +00:00
parent 6408301421
commit 8b1265b353
23 changed files with 946 additions and 2 deletions

View File

@ -1,5 +1,8 @@
check_library_exists(edit el_init "" HAVE_LIBEDIT)
add_subdirectory(clang-apply-replacements)
add_subdirectory(clang-modernize)
add_subdirectory(clang-query)
add_subdirectory(clang-tidy)
add_subdirectory(modularize)
add_subdirectory(pp-trace)

View File

@ -12,7 +12,8 @@ CLANG_LEVEL := ../..
include $(CLANG_LEVEL)/../../Makefile.config
PARALLEL_DIRS := remove-cstr-calls tool-template modularize pp-trace
DIRS := clang-apply-replacements clang-modernize clang-tidy unittests
DIRS := clang-apply-replacements clang-modernize clang-tidy clang-query \
unittests
include $(CLANG_LEVEL)/Makefile

View File

@ -0,0 +1,13 @@
add_clang_library(clangQuery
Query.cpp
QueryParser.cpp
)
target_link_libraries(clangQuery
clangAST
clangASTMatchers
clangBasic
clangDynamicASTMatchers
clangFrontend
)
add_subdirectory(tool)

View File

@ -0,0 +1,14 @@
##===- tools/extra/clang-query/Makefile --------------------*- Makefile -*-===##
#
# The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
##===----------------------------------------------------------------------===##
CLANG_LEVEL := ../../..
LIBRARYNAME := clangQuery
include $(CLANG_LEVEL)/../../Makefile.config
include $(CLANG_LEVEL)/Makefile

View File

@ -0,0 +1,131 @@
//===---- Query.cpp - clang-query query -----------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Query.h"
#include "QuerySession.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/TextDiagnostic.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang::ast_matchers;
using namespace clang::ast_matchers::dynamic;
namespace clang {
namespace query {
Query::~Query() {}
bool InvalidQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
OS << ErrStr << "\n";
return false;
}
bool NoOpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
return true;
}
bool HelpQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
OS << "Available commands:\n\n"
" match MATCHER, m MATCHER "
"Match the loaded ASTs against the given matcher.\n"
" set bind-root (true|false) "
"Set whether to bind the root matcher to \"root\".\n"
" set output (diag|print|dump) "
"Set whether to print bindings as diagnostics,\n"
" "
"AST pretty prints or AST dumps.\n\n";
return true;
}
namespace {
struct CollectBoundNodes : MatchFinder::MatchCallback {
std::vector<BoundNodes> &Bindings;
CollectBoundNodes(std::vector<BoundNodes> &Bindings) : Bindings(Bindings) {}
void run(const MatchFinder::MatchResult &Result) {
Bindings.push_back(Result.Nodes);
}
};
}
bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
unsigned MatchCount = 0;
for (llvm::ArrayRef<ASTUnit *>::iterator I = QS.ASTs.begin(),
E = QS.ASTs.end();
I != E; ++I) {
ASTUnit *AST = *I;
MatchFinder Finder;
std::vector<BoundNodes> Matches;
DynTypedMatcher MaybeBoundMatcher = Matcher;
if (QS.BindRoot) {
llvm::Optional<DynTypedMatcher> M = Matcher.tryBind("root");
if (M)
MaybeBoundMatcher = *M;
}
CollectBoundNodes Collect(Matches);
if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) {
OS << "Not a valid top-level matcher.\n";
return false;
}
Finder.matchAST(AST->getASTContext());
for (std::vector<BoundNodes>::iterator MI = Matches.begin(),
ME = Matches.end();
MI != ME; ++MI) {
OS << "\nMatch #" << ++MatchCount << ":\n\n";
for (BoundNodes::IDToNodeMap::const_iterator BI = MI->getMap().begin(),
BE = MI->getMap().end();
BI != BE; ++BI) {
switch (QS.OutKind) {
case OK_Diag: {
clang::SourceRange R = BI->second.getSourceRange();
if (R.isValid()) {
TextDiagnostic TD(OS, AST->getASTContext().getLangOpts(),
&AST->getDiagnostics().getDiagnosticOptions());
TD.emitDiagnostic(
R.getBegin(), DiagnosticsEngine::Note,
"\"" + BI->first + "\" binds here",
ArrayRef<CharSourceRange>(CharSourceRange::getTokenRange(R)),
ArrayRef<FixItHint>(), &AST->getSourceManager());
}
break;
}
case OK_Print: {
OS << "Binding for \"" << BI->first << "\":\n";
BI->second.print(OS, AST->getASTContext().getPrintingPolicy());
OS << "\n";
break;
}
case OK_Dump: {
OS << "Binding for \"" << BI->first << "\":\n";
BI->second.dump(OS, AST->getSourceManager());
OS << "\n";
break;
}
}
}
if (MI->getMap().empty())
OS << "No bindings.\n";
}
}
OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n");
return true;
}
const QueryKind SetQueryKind<bool>::value;
const QueryKind SetQueryKind<OutputKind>::value;
} // namespace query
} // namespace clang

View File

@ -0,0 +1,119 @@
//===--- Query.h - clang-query ----------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H
#include <string>
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/Optional.h"
namespace clang {
namespace query {
enum OutputKind {
OK_Diag,
OK_Print,
OK_Dump
};
enum QueryKind {
QK_Invalid,
QK_NoOp,
QK_Help,
QK_Match,
QK_SetBool,
QK_SetOutputKind
};
class QuerySession;
struct Query : llvm::RefCountedBase<Query> {
Query(QueryKind Kind) : Kind(Kind) {}
virtual ~Query();
/// Perform the query on \p QS and print output to \p OS.
///
/// \return false if an error occurs, otherwise return true.
virtual bool run(llvm::raw_ostream &OS, QuerySession &QS) const = 0;
const QueryKind Kind;
};
typedef llvm::IntrusiveRefCntPtr<Query> QueryRef;
/// Any query which resulted in a parse error. The error message is in ErrStr.
struct InvalidQuery : Query {
InvalidQuery(const Twine &ErrStr) : Query(QK_Invalid), ErrStr(ErrStr.str()) {}
bool run(llvm::raw_ostream &OS, QuerySession &QS) const LLVM_OVERRIDE;
std::string ErrStr;
static bool classof(const Query *Q) { return Q->Kind == QK_Invalid; }
};
/// No-op query (i.e. a blank line).
struct NoOpQuery : Query {
NoOpQuery() : Query(QK_NoOp) {}
bool run(llvm::raw_ostream &OS, QuerySession &QS) const LLVM_OVERRIDE;
static bool classof(const Query *Q) { return Q->Kind == QK_NoOp; }
};
/// Query for "help".
struct HelpQuery : Query {
HelpQuery() : Query(QK_Help) {}
bool run(llvm::raw_ostream &OS, QuerySession &QS) const LLVM_OVERRIDE;
static bool classof(const Query *Q) { return Q->Kind == QK_Help; }
};
/// Query for "match MATCHER".
struct MatchQuery : Query {
MatchQuery(const ast_matchers::dynamic::DynTypedMatcher &Matcher)
: Query(QK_Match), Matcher(Matcher) {}
bool run(llvm::raw_ostream &OS, QuerySession &QS) const LLVM_OVERRIDE;
ast_matchers::dynamic::DynTypedMatcher Matcher;
static bool classof(const Query *Q) { return Q->Kind == QK_Match; }
};
template <typename T> struct SetQueryKind {};
template <> struct SetQueryKind<bool> {
static const QueryKind value = QK_SetBool;
};
template <> struct SetQueryKind<OutputKind> {
static const QueryKind value = QK_SetOutputKind;
};
/// Query for "set VAR VALUE".
template <typename T> struct SetQuery : Query {
SetQuery(T QuerySession::*Var, T Value)
: Query(SetQueryKind<T>::value), Var(Var), Value(Value) {}
bool run(llvm::raw_ostream &OS, QuerySession &QS) const LLVM_OVERRIDE {
QS.*Var = Value;
return true;
}
static bool classof(const Query *Q) {
return Q->Kind == SetQueryKind<T>::value;
}
T QuerySession::*Var;
T Value;
};
} // namespace query
} // namespace clang
#endif

View File

@ -0,0 +1,165 @@
//===---- QueryParser.cpp - clang-query command parser --------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "QueryParser.h"
#include "Query.h"
#include "QuerySession.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "clang/ASTMatchers/Dynamic/Parser.h"
#include "clang/Basic/CharInfo.h"
using namespace llvm;
using namespace clang::ast_matchers::dynamic;
namespace clang {
namespace query {
// Lex any amount of whitespace followed by a "word" (any sequence of
// non-whitespace characters) from the start of region [Begin,End). If no word
// is found before End, return StringRef(). Begin is adjusted to exclude the
// lexed region.
static StringRef LexWord(const char *&Begin, const char *End) {
while (true) {
if (Begin == End)
return StringRef();
if (!isWhitespace(*Begin))
break;
++Begin;
}
const char *WordBegin = Begin;
while (true) {
++Begin;
if (Begin == End || isWhitespace(*Begin))
return StringRef(WordBegin, Begin - WordBegin);
}
}
static QueryRef ParseSetBool(bool QuerySession::*Var, StringRef ValStr) {
unsigned Value = StringSwitch<unsigned>(ValStr)
.Case("false", 0)
.Case("true", 1)
.Default(~0u);
if (Value == ~0u) {
return new InvalidQuery("expected 'true' or 'false', got '" + ValStr + "'");
}
return new SetQuery<bool>(Var, Value);
}
static QueryRef ParseSetOutputKind(StringRef ValStr) {
unsigned OutKind = StringSwitch<unsigned>(ValStr)
.Case("diag", OK_Diag)
.Case("print", OK_Print)
.Case("dump", OK_Dump)
.Default(~0u);
if (OutKind == ~0u) {
return new InvalidQuery("expected 'diag', 'print' or 'dump', got '" +
ValStr + "'");
}
return new SetQuery<OutputKind>(&QuerySession::OutKind, OutputKind(OutKind));
}
static QueryRef EndQuery(const char *Begin, const char *End, QueryRef Q) {
const char *Extra = Begin;
if (!LexWord(Begin, End).empty())
return new InvalidQuery("unexpected extra input: '" +
StringRef(Extra, End - Extra) + "'");
return Q;
}
enum ParsedQueryKind {
PQK_Invalid,
PQK_NoOp,
PQK_Help,
PQK_Match,
PQK_Set
};
enum ParsedQueryVariable {
PQV_Invalid,
PQV_Output,
PQV_BindRoot
};
QueryRef ParseQuery(StringRef Line) {
const char *Begin = Line.data();
const char *End = Line.data() + Line.size();
StringRef CommandStr = LexWord(Begin, End);
ParsedQueryKind QKind = StringSwitch<ParsedQueryKind>(CommandStr)
.Case("", PQK_NoOp)
.Case("help", PQK_Help)
.Case("m", PQK_Match)
.Case("match", PQK_Match)
.Case("set", PQK_Set)
.Default(PQK_Invalid);
switch (QKind) {
case PQK_NoOp:
return new NoOpQuery;
case PQK_Help:
return EndQuery(Begin, End, new HelpQuery);
case PQK_Match: {
Diagnostics Diag;
Optional<DynTypedMatcher> Matcher =
Parser::parseMatcherExpression(StringRef(Begin, End - Begin), &Diag);
if (!Matcher) {
std::string ErrStr;
llvm::raw_string_ostream OS(ErrStr);
Diag.printToStreamFull(OS);
return new InvalidQuery(OS.str());
}
return new MatchQuery(*Matcher);
}
case PQK_Set: {
StringRef VarStr = LexWord(Begin, End);
if (VarStr.empty())
return new InvalidQuery("expected variable name");
ParsedQueryVariable Var = StringSwitch<ParsedQueryVariable>(VarStr)
.Case("output", PQV_Output)
.Case("bind-root", PQV_BindRoot)
.Default(PQV_Invalid);
if (Var == PQV_Invalid)
return new InvalidQuery("unknown variable: '" + VarStr + "'");
StringRef ValStr = LexWord(Begin, End);
if (ValStr.empty())
return new InvalidQuery("expected variable value");
QueryRef Q;
switch (Var) {
case PQV_Output:
Q = ParseSetOutputKind(ValStr);
break;
case PQV_BindRoot:
Q = ParseSetBool(&QuerySession::BindRoot, ValStr);
break;
case PQV_Invalid:
llvm_unreachable("Invalid query kind");
}
return EndQuery(Begin, End, Q);
}
case PQK_Invalid:
return new InvalidQuery("unknown command: " + CommandStr);
}
}
} // namespace query
} // namespace clang

View File

@ -0,0 +1,27 @@
//===--- QueryParser.h - clang-query ----------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H
#include "Query.h"
namespace clang {
namespace query {
/// \brief Parse \p Line.
///
/// \return A reference to the parsed query object, which may be an
/// \c InvalidQuery if a parse error occurs.
QueryRef ParseQuery(StringRef Line);
} // namespace query
} // namespace clang
#endif

View File

@ -0,0 +1,36 @@
//===--- QuerySession.h - clang-query ---------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H
#include "llvm/ADT/ArrayRef.h"
#include "Query.h"
namespace clang {
class ASTUnit;
namespace query {
/// Represents the state for a particular clang-query session.
class QuerySession {
public:
QuerySession(llvm::ArrayRef<ASTUnit *> ASTs)
: ASTs(ASTs), OutKind(OK_Diag), BindRoot(true) {}
llvm::ArrayRef<ASTUnit *> ASTs;
OutputKind OutKind;
bool BindRoot;
};
} // namespace query
} // namespace clang
#endif

View File

@ -0,0 +1,11 @@
if(HAVE_LIBEDIT)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_clang_executable(clang-query ClangQuery.cpp)
target_link_libraries(clang-query
edit
clangFrontend
clangQuery
clangTooling
)
endif()

View File

@ -0,0 +1,158 @@
//===---- ClangQuery.cpp - clang-query tool -------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This tool is for interactive exploration of the Clang AST using AST matchers.
// It currently allows the user to enter a matcher at an interactive prompt and
// view the resulting bindings as diagnostics, AST pretty prints or AST dumps.
// Example session:
//
// $ cat foo.c
// void foo(void) {}
// $ clang-query foo.c --
// clang-query> match functionDecl()
//
// Match #1:
//
// foo.c:1:1: note: "root" binds here
// void foo(void) {}
// ^~~~~~~~~~~~~~~~~
// 1 match.
//
//===----------------------------------------------------------------------===//
#include "Query.h"
#include "QuerySession.h"
#include "QueryParser.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/OwningPtr.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Signals.h"
#include <fstream>
#include <string>
#include <histedit.h>
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::ast_matchers::dynamic;
using namespace clang::query;
using namespace clang::tooling;
using namespace llvm;
static cl::opt<std::string> BuildPath("b", cl::desc("Specify build path"),
cl::value_desc("<path>"));
static cl::list<std::string> Commands("c", cl::desc("Specify command to run"),
cl::value_desc("<command>"));
static cl::list<std::string> CommandFiles("f",
cl::desc("Read commands from file"),
cl::value_desc("<file>"));
static cl::list<std::string> SourcePaths(cl::Positional,
cl::desc("<source0> [... <sourceN>]"),
cl::OneOrMore);
static char *ReturnPrompt(EditLine *EL) {
static char Prompt[] = "clang-query> ";
return Prompt;
}
int main(int argc, const char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal();
cl::ParseCommandLineOptions(argc, argv);
if (!Commands.empty() && !CommandFiles.empty()) {
llvm::errs() << argv[0] << ": cannot specify both -c and -f\n";
return 1;
}
llvm::OwningPtr<CompilationDatabase> Compilations(
FixedCompilationDatabase::loadFromCommandLine(argc, argv));
if (!Compilations) { // Couldn't find a compilation DB from the command line
std::string ErrorMessage;
Compilations.reset(
!BuildPath.empty() ?
CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage) :
CompilationDatabase::autoDetectFromSource(SourcePaths[0], ErrorMessage)
);
// Still no compilation DB? - bail.
if (!Compilations)
llvm::report_fatal_error(ErrorMessage);
}
ClangTool Tool(*Compilations, SourcePaths);
std::vector<ASTUnit *> ASTs;
if (Tool.buildASTs(ASTs) != 0)
return 1;
QuerySession QS(ASTs);
if (!Commands.empty()) {
for (cl::list<std::string>::iterator I = Commands.begin(),
E = Commands.end();
I != E; ++I) {
QueryRef Q = ParseQuery(I->c_str());
if (!Q->run(llvm::outs(), QS))
return 1;
}
} else if (!CommandFiles.empty()) {
for (cl::list<std::string>::iterator I = CommandFiles.begin(),
E = CommandFiles.end();
I != E; ++I) {
std::ifstream Input(I->c_str());
if (!Input.is_open()) {
llvm::errs() << argv[0] << ": cannot open " << *I << "\n";
return 1;
}
while (Input.good()) {
std::string Line;
std::getline(Input, Line);
QueryRef Q = ParseQuery(Line.c_str());
if (!Q->run(llvm::outs(), QS))
return 1;
}
}
} else {
History *Hist = history_init();
HistEvent Event;
history(Hist, &Event, H_SETSIZE, 100);
EditLine *EL = el_init("clang-query", stdin, stdout, stderr);
el_set(EL, EL_PROMPT, ReturnPrompt);
el_set(EL, EL_EDITOR, "emacs");
el_set(EL, EL_HIST, history, Hist);
int Count;
while (const char *Line = el_gets(EL, &Count)) {
if (Count == 0)
break;
history(Hist, &Event, H_ENTER, Line);
QueryRef Q = ParseQuery(Line);
Q->run(llvm::outs(), QS);
}
history_end(Hist);
el_end(EL);
llvm::outs() << "\n";
}
llvm::DeleteContainerPointers(ASTs);
return 0;
}

View File

@ -38,6 +38,10 @@ set(CLANG_TOOLS_TEST_DEPS
ExtraToolsUnitTests
)
if(HAVE_LIBEDIT)
list(APPEND CLANG_TOOLS_TEST_DEPS clang-query)
endif()
add_lit_testsuite(check-clang-tools "Running the Clang extra tools' regression tests"
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${CLANG_TOOLS_TEST_DEPS}

View File

@ -0,0 +1,2 @@
foo
bar

View File

@ -0,0 +1,11 @@
// RUN: not clang-query -c foo -c bar %s -- | FileCheck %s
// RUN: not clang-query -f %S/Inputs/foo.script %s -- | FileCheck %s
// RUN: not clang-query -f %S/Inputs/nonexistent.script %s -- 2>&1 | FileCheck --check-prefix=CHECK-NONEXISTENT %s
// RUN: not clang-query -c foo -f foo %s -- 2>&1 | FileCheck --check-prefix=CHECK-BOTH %s
// REQUIRES: libedit
// CHECK: unknown command: foo
// CHECK-NOT: unknown command: bar
// CHECK-NONEXISTENT: cannot open {{.*}}nonexistent.script
// CHECK-BOTH: cannot specify both -c and -f

View File

@ -0,0 +1,5 @@
// RUN: clang-query -c "match functionDecl()" %s -- | FileCheck %s
// REQUIRES: libedit
// CHECK: function-decl.c:5:1: note: "root" binds here
void foo(void) {}

View File

@ -229,3 +229,6 @@ if platform.system() not in ['Windows'] or lit_config.getBashPath() != '':
# ANSI escape sequences in non-dumb terminal
if platform.system() not in ['Windows']:
config.available_features.add('ansi-escape-sequences')
if config.have_libedit == "1":
config.available_features.add('libedit')

View File

@ -7,6 +7,7 @@ config.llvm_libs_dir = "@LLVM_LIBS_DIR@"
config.lit_tools_dir = "@LLVM_LIT_TOOLS_DIR@"
config.clang_tools_binary_dir = "@CLANG_TOOLS_BINARY_DIR@"
config.target_triple = "@TARGET_TRIPLE@"
config.have_libedit = "@HAVE_LIBEDIT@"
# Support substitution of the tools and libs dirs with user parameters. This is
# used when we can't determine the tool dir at configuration time.

View File

@ -7,4 +7,5 @@ endfunction()
add_subdirectory(clang-apply-replacements)
add_subdirectory(clang-modernize)
add_subdirectory(clang-query)
add_subdirectory(clang-tidy)

View File

@ -10,6 +10,6 @@
CLANG_LEVEL := ../../..
include $(CLANG_LEVEL)/../../Makefile.config
PARALLEL_DIRS := clang-apply-replacements clang-modernize clang-tidy
PARALLEL_DIRS := clang-apply-replacements clang-modernize clang-query clang-tidy
include $(CLANG_LEVEL)/Makefile

View File

@ -0,0 +1,18 @@
set(LLVM_LINK_COMPONENTS
support
)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/../../clang-query
)
add_extra_unittest(ClangQueryTests
QueryEngineTest.cpp
QueryParserTest.cpp
)
target_link_libraries(ClangQueryTests
clangASTMatchers
clangQuery
clangTooling
)

View File

@ -0,0 +1,24 @@
##===- unittests/clang-query/Makefile ----------------------*- Makefile -*-===##
#
# The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
##===----------------------------------------------------------------------===##
CLANG_LEVEL = ../../../..
include $(CLANG_LEVEL)/../../Makefile.config
TESTNAME = ClangQuery
LINK_COMPONENTS := asmparser bitreader support MC MCParser option \
TransformUtils
USEDLIBS = clangQuery.a clangTooling.a clangFrontend.a clangSerialization.a \
clangDriver.a clangParse.a clangSema.a clangEdit.a clangAnalysis.a \
clangAST.a clangASTMatchers.a clangDynamicASTMatchers.a clangLex.a \
clangBasic.a
include $(CLANG_LEVEL)/Makefile
MAKEFILE_UNITTEST_NO_INCLUDE_COMMON := 1
CPP.Flags += -I$(PROJ_SRC_DIR)/../../clang-query
include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest

View File

@ -0,0 +1,110 @@
//===---- QueryTest.cpp - clang-query test --------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Query.h"
#include "QuerySession.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
#include <string>
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::ast_matchers::dynamic;
using namespace clang::query;
using namespace clang::tooling;
TEST(Query, Basic) {
OwningPtr<ASTUnit> FooAST(
buildASTFromCode("void foo1(void) {}\nvoid foo2(void) {}", "foo.cc"));
ASSERT_TRUE(FooAST.get());
OwningPtr<ASTUnit> BarAST(
buildASTFromCode("void bar1(void) {}\nvoid bar2(void) {}", "bar.cc"));
ASSERT_TRUE(BarAST.get());
ASTUnit *ASTs[] = { FooAST.get(), BarAST.get() };
std::string Str;
llvm::raw_string_ostream OS(Str);
QuerySession S(ASTs);
DynTypedMatcher FnMatcher = functionDecl();
DynTypedMatcher FooMatcher = functionDecl(hasName("foo1"));
EXPECT_TRUE(NoOpQuery().run(OS, S));
EXPECT_EQ("", OS.str());
Str.clear();
EXPECT_FALSE(InvalidQuery("Parse error").run(OS, S));
EXPECT_EQ("Parse error\n", OS.str());
Str.clear();
EXPECT_TRUE(HelpQuery().run(OS, S));
EXPECT_TRUE(OS.str().find("Available commands:") != std::string::npos);
Str.clear();
EXPECT_TRUE(MatchQuery(FnMatcher).run(OS, S));
EXPECT_TRUE(OS.str().find("foo.cc:1:1: note: \"root\" binds here") !=
std::string::npos);
EXPECT_TRUE(OS.str().find("foo.cc:2:1: note: \"root\" binds here") !=
std::string::npos);
EXPECT_TRUE(OS.str().find("bar.cc:1:1: note: \"root\" binds here") !=
std::string::npos);
EXPECT_TRUE(OS.str().find("bar.cc:2:1: note: \"root\" binds here") !=
std::string::npos);
EXPECT_TRUE(OS.str().find("4 matches.") != std::string::npos);
Str.clear();
EXPECT_TRUE(MatchQuery(FooMatcher).run(OS, S));
EXPECT_TRUE(OS.str().find("foo.cc:1:1: note: \"root\" binds here") !=
std::string::npos);
EXPECT_TRUE(OS.str().find("1 match.") != std::string::npos);
Str.clear();
EXPECT_TRUE(
SetQuery<OutputKind>(&QuerySession::OutKind, OK_Print).run(OS, S));
EXPECT_TRUE(MatchQuery(FooMatcher).run(OS, S));
EXPECT_TRUE(OS.str().find("Binding for \"root\":\nvoid foo1()") !=
std::string::npos);
Str.clear();
EXPECT_TRUE(SetQuery<OutputKind>(&QuerySession::OutKind, OK_Dump).run(OS, S));
EXPECT_TRUE(MatchQuery(FooMatcher).run(OS, S));
EXPECT_TRUE(OS.str().find("FunctionDecl") != std::string::npos);
Str.clear();
EXPECT_TRUE(SetQuery<bool>(&QuerySession::BindRoot, false).run(OS, S));
EXPECT_TRUE(MatchQuery(FooMatcher).run(OS, S));
EXPECT_TRUE(OS.str().find("No bindings.") != std::string::npos);
Str.clear();
EXPECT_FALSE(MatchQuery(isArrow()).run(OS, S));
EXPECT_EQ("Not a valid top-level matcher.\n", OS.str());
}

View File

@ -0,0 +1,87 @@
//===---- QueryParserTest.cpp - clang-query test --------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "QueryParser.h"
#include "Query.h"
#include "QuerySession.h"
#include "gtest/gtest.h"
using namespace clang;
using namespace clang::query;
TEST(QueryParser, NoOp) {
QueryRef Q = ParseQuery("");
EXPECT_TRUE(isa<NoOpQuery>(Q));
Q = ParseQuery("\n");
EXPECT_TRUE(isa<NoOpQuery>(Q));
}
TEST(QueryParser, Invalid) {
QueryRef Q = ParseQuery("foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unknown command: foo", cast<InvalidQuery>(Q)->ErrStr);
}
TEST(QueryParser, Help) {
QueryRef Q = ParseQuery("help");
ASSERT_TRUE(isa<HelpQuery>(Q));
Q = ParseQuery("help me");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unexpected extra input: ' me'", cast<InvalidQuery>(Q)->ErrStr);
}
TEST(QueryParser, Set) {
QueryRef Q = ParseQuery("set");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected variable name", cast<InvalidQuery>(Q)->ErrStr);
Q = ParseQuery("set foo bar");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unknown variable: 'foo'", cast<InvalidQuery>(Q)->ErrStr);
Q = ParseQuery("set output");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected variable value", cast<InvalidQuery>(Q)->ErrStr);
Q = ParseQuery("set bind-root true foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unexpected extra input: ' foo'", cast<InvalidQuery>(Q)->ErrStr);
Q = ParseQuery("set output foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected 'diag', 'print' or 'dump', got 'foo'",
cast<InvalidQuery>(Q)->ErrStr);
Q = ParseQuery("set output dump");
ASSERT_TRUE(isa<SetQuery<OutputKind> >(Q));
EXPECT_EQ(&QuerySession::OutKind, cast<SetQuery<OutputKind> >(Q)->Var);
EXPECT_EQ(OK_Dump, cast<SetQuery<OutputKind> >(Q)->Value);
Q = ParseQuery("set bind-root foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected 'true' or 'false', got 'foo'",
cast<InvalidQuery>(Q)->ErrStr);
Q = ParseQuery("set bind-root true");
ASSERT_TRUE(isa<SetQuery<bool> >(Q));
EXPECT_EQ(&QuerySession::BindRoot, cast<SetQuery<bool> >(Q)->Var);
EXPECT_EQ(true, cast<SetQuery<bool> >(Q)->Value);
}
TEST(QueryParser, Match) {
QueryRef Q = ParseQuery("match decl()");
ASSERT_TRUE(isa<MatchQuery>(Q));
EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Decl>());
Q = ParseQuery("m stmt()");
ASSERT_TRUE(isa<MatchQuery>(Q));
EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Stmt>());
}