LLVM-ClangのASTを解析するまっとうな方法について
Clang is not just a great compiler...
clangは、モダンなコンパイラとして有名であるが、 中間形式である AST を使ったりして静的解析の基板として使えるようにもなっている。 実際に、Apple の Xcode では、Clang を使ってコード補完を実現しているそうだ。
そんな、Clang 使って AST を覗く方法の話。
昔は XML で構文木を吐き出す機能があったらしい
$ clang -cc1 -ast-print-xml source.c
今のバージョンは -ast-print-xml
オプションはない。
-emit-ast
で我慢しろ、らしい。
$ clang -emit-ast source.c
これで生成される source.ast
ファイルがバイナリ形式だから困ったものである。
AST を解析する方法
オープンソースなので、clang のソースに手を入れるとか 手段はいくらでもあるのだろうけれど、 正攻法は libclang を使ってアクセスするらしい。
最も安直な構文のなぞり方は、下記のように
clang_visitChildren()
で各要素ごとにウォークスルーする。
#include <stdio.h>
#include <clang-c/Index.h>
CXChildVisitResult visitChildrenCallback(CXCursor cursor, CXCursor parent, CXClientData client_data)
{
CXString usrStr = clang_getCursorKindSpelling(clang_getCursorKind(cursor));
printf("%s\n", usrStr);
clang_disposeString(usrStr);
return CXChildVisit_Recursive;
}
int main(int argc, char *argv[])
{
// create index w/ excludeDeclsFromPCH = 1, displayDiagnostics=1.
CXIndex index = clang_createIndex(1, 1);
// load a *.ast file.
CXTranslationUnit tu = clang_createTranslationUnit(index, argv[1]);
if (tu != NULL) {
clang_visitChildren(clang_getTranslationUnitCursor(tu),
visitChildrenCallback,
NULL);
clang_disposeTranslationUnit(tu);
}
clang_disposeIndex(index);
}
なお、コンパイルは以下のように。 (Clang でコンパイルしたかったが、なんかうまくリンクが通らなかったので GCC でやった。)
$ g++ -o astana astana.cpp -L/usr/lib64/llvm -lclang
ちなみに使い方は、引数に ast ファイルを渡してやる。 つまり、全体的な使い方としては以下のようになる。
$ clang -emit-ast source.c
$ astana source.ast > source.txt
ちょっとした XML を吐こうとすると
XML 出力には libxml2 使って
#include <clang-c/Index.h>
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
#include <stdio.h>
xmlChar *getLocationAsString(CXCursor cursor)
{
xmlChar locStr[BUFSIZ];
xmlChar *ret = NULL;
// get filename, line # and column #.
CXSourceLocation loc = clang_getCursorLocation(cursor);
CXString filename;
unsigned lineno;
unsigned column;
clang_getPresumedLocation(loc, &filename, &lineno, &column);
xmlStrPrintf(locStr, BUFSIZ-1,
BAD_CAST "%s:%d:%d",
clang_getCString(filename),
lineno,
column);
ret = xmlStrdup(locStr);
clang_disposeString(filename);
return ret;
}
CXChildVisitResult visitChildrenCallback(CXCursor cursor, CXCursor parent, CXClientData client_data)
{
xmlTextWriterPtr xmlWriter = static_cast<xmlTextWriterPtr>(client_data);
CXString usrStr = clang_getCursorUSR(cursor);
CXString kindStr = clang_getCursorKindSpelling(clang_getCursorKind(cursor));
xmlChar *location = getLocationAsString(cursor);
xmlTextWriterStartElement(xmlWriter, BAD_CAST "entity");
xmlTextWriterWriteAttribute(xmlWriter,
BAD_CAST "usr",
BAD_CAST clang_getCString(usrStr));
xmlTextWriterWriteAttribute(xmlWriter,
BAD_CAST "kind",
BAD_CAST clang_getCString(kindStr));
xmlTextWriterWriteAttribute(xmlWriter,
BAD_CAST "src",
location);
clang_disposeString(usrStr);
clang_disposeString(kindStr);
free(location);
// visits children recursive.
clang_visitChildren(cursor,
visitChildrenCallback,
xmlWriter);
xmlTextWriterEndElement(xmlWriter); // entity
return CXChildVisit_Continue;
}
int main(int argc, char *argv[])
{
// create index w/ excludeDeclsFromPCH = 1, displayDiagnostics=1.
CXIndex index = clang_createIndex(1, 1);
// load a *.ast file.
CXTranslationUnit tu = clang_createTranslationUnit(index, argv[1]);
if (tu != NULL) {
xmlBufferPtr xmlBuffer = xmlBufferCreate();
xmlTextWriterPtr xmlWriter = xmlNewTextWriterMemory(xmlBuffer, 1);
xmlTextWriterStartDocument(xmlWriter, NULL, "UTF-8", NULL);
xmlTextWriterStartElement(xmlWriter, BAD_CAST "ast");
clang_visitChildren(clang_getTranslationUnitCursor(tu),
visitChildrenCallback,
xmlWriter);
xmlTextWriterEndElement(xmlWriter); // ast
xmlTextWriterEndDocument(xmlWriter);
xmlFreeTextWriter(xmlWriter);
// print the XML text to the standard output.
xmlBufferDump(stdout, xmlBuffer);
xmlBufferFree(xmlBuffer);
clang_disposeTranslationUnit(tu);
} else {
fprintf(stderr, "Could not load \"%s\" as an AST file.\n", argv[1]);
}
clang_disposeIndex(index);
return 0;
}
Makefile は、例えば、
CXX = g++
CXXFLAGS = -O2 -g
CLANGFLAGS = -L/usr/lib64/llvm -lclang
LIBXML2FLAGS = `pkg-config --libs --cflags libxml-2.0`
astana: astana.cpp
$(CXX) $(CXXFLAGS) -o $@ $^ $(CLANGFLAGS) $(LIBXML2FLAGS)
使い方は、前のと同じく
$ clang -emit-ast source.c
$ astana source.ast > source.xml
しかし、さすがに自力で XML を吐こうとするとメンドイ。 これでも表示項目足りないくらいだし。
References
- Doug Gregor, "libclang: Thinking Beyond the Compiler", 2010 LLVM Developers Meeting, Nov 4, 2010
- Can I get an XML AST dump of C/C++ code with clang without using the compiler? - Stack Overflow
/usr/include/clang-c/Index.h
- Clang