Clangのソースツリーにpluginのサンプルが格納されているので試してみる。
1. ビルド方法
Clang/LLVMをビルドした状態でexample pluginのビルドを実行。
$ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm $ cd llvm/tools $ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang $ cd ../.. $ cd llvm/tools/clang/tools $ svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra $ cd ../../../.. $ cd llvm/projects $ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt $ cd ../.. $ mkdir llvm.build $ cd llvm.build
1.1. configureを用いた場合
llvm/tools/clang/examples配下にpluginのサンプルコードがある。ビルドす るにはBUILD_EXAMPLES=1を設定してmake。
$ ../llvm/configure --prefix=$HOME --enable-optimized $ make BUILD_EXAMPLES=1
以下のファイルが作成される。
./Release+Asserts/lib/libPrintFunctionNames.a ./Release+Asserts/lib/libPrintFunctionNames.dylib ./Release+Asserts/lib/SampleAnalyzerPlugin.dylib ./Release+Asserts/bin/clang-interpreter
1.2. cmakeを用いた場合
-DCLANG_BUILD_EXAMPLES:BOOL=ONをcmakeに渡してMakefileを生成。
$ cmake -G "Unix Makefiles" ../llvm/ \ -DCMAKE_INSTALL_PREFIX="$HOME" \ -DCMAKE_BUILD_TYPE="Release" \ -DCLANG_BUILD_EXAMPLES:BOOL=ON $ make
以下のファイルが作成される。PrintFunctionNamesのプレフィックスにlibが つかないようですね。
./lib/SampleAnalyzerPlugin.so ./lib/PrintFunctionNames.so ./bin/clang-interpreter
2. PrintFunctionNames.cpp
translation unit単位(ヘッダーファイルのインクルードが済んだc/c++ソー スコード単位)に呼ばれるASTConsumerを利用して、NamedDecl(変数宣言、関 数宣言、構造体宣言等のスーパークラスを出力するサンプル。
ASTConsumerを継承したクラスを用いてvirtualなメソッドを実装することで、 ClangによるAST作成後の各種エントリポイントからメソッドが呼ばれる。
PrintFunctionNames.cppではvirtual bool HandleTopLevelDecl(DeclGroupRef DG)を使用している。
class PrintFunctionsConsumer : public ASTConsumer { public: virtual bool HandleTopLevelDecl(DeclGroupRef DG) { for (DeclGroupRef::iterator i = DG.begin(), e = DG.end(); i != e; ++i) { const Decl *D = *i; if (const NamedDecl *ND = dyn_cast<NamedDecl>(D)) llvm::errs() << "top-level-decl: \"" << ND->getNameAsString() << "\"\n"; } return true; } };
以下のおまじないでpluginとして登録。
class PrintFunctionNamesAction : public PluginASTAction { <snip> }; <snip> static FrontendPluginRegistry::Add<PrintFunctionNamesAction> X("print-fns", "print function names");
2.1. 実行
以下のhello.cを入力ファイルとして指定。
#include <stdio.h> int main(void) { int i; for (i = 0; i < 10; i++) printf("hello, world\n"); return 0; }
Clang/LLVMをビルドしたディレクトリにて、以下のコマンドでpluginを実行 (pluginのパスを指定すればどこでも良い)。ヘッダファイルの定義が大量に 出力される。
$ clang -cc1 -load ./Release+Asserts/lib/libPrintFunctionNames.dylib \ -plugin print-fns hello.c top-level-decl: "__uint8_t" top-level-decl: "__int16_t" <snip> top-level-decl: "__vsnprintf_chk" top-level-decl: "main"
2.2. AST (Abstract Syntax Tree)
Clang内部で定義されているASTをダンプすることができる。先ほどのhello.c のASTをダンプしてみる。
$ clang -Xclang -ast-dump -fsyntax-only hello.c TranslationUnitDecl 0x7fd7bb0216d0 <<invalid sloc>> |-TypedefDecl 0x7fd7bb021bd0 <<invalid sloc>> __int128_t '__int128' <snip> `-FunctionDecl 0x7fd7bb898720 <hello.c:3:1, line:9:1> main 'int (void)' `-CompoundStmt 0x7fd7bb898b80 <line:4:1, line:9:1> |-DeclStmt 0x7fd7bb898868 <line:5:5, col:10> | `-VarDecl 0x7fd7bb898810 <col:5, col:9> i 'int' |-ForStmt 0x7fd7bb898b00 <line:6:5, line:7:32> | |-BinaryOperator 0x7fd7bb8988c8 <line:6:10, col:14> 'int' '=' | | |-DeclRefExpr 0x7fd7bb898880 <col:10> 'int' lvalue Var 0x7fd7bb898810 'i' 'int' | | `-IntegerLiteral 0x7fd7bb8988a8 <col:14> 'int' 0 | |-<<<NULL>>> | |-BinaryOperator 0x7fd7bb898950 <col:17, col:21> 'int' '<' | | |-ImplicitCastExpr 0x7fd7bb898938 <col:17> 'int' <LValueToRValue> | | | `-DeclRefExpr 0x7fd7bb8988f0 <col:17> 'int' lvalue Var 0x7fd7bb898810 'i' 'int' | | `-IntegerLiteral 0x7fd7bb898918 <col:21> 'int' 10 | |-UnaryOperator 0x7fd7bb8989a0 <col:25, col:26> 'int' postfix '++' | | `-DeclRefExpr 0x7fd7bb898978 <col:25> 'int' lvalue Var 0x7fd7bb898810 'i' 'int' | `-CallExpr 0x7fd7bb898aa0 <line:7:9, col:32> 'int' | |-ImplicitCastExpr 0x7fd7bb898a88 <col:9> 'int (*)(const char *, ...)' <FunctionToPointerDecay> | | `-DeclRefExpr 0x7fd7bb8989c0 <col:9> 'int (const char *, ...)' Function 0x7fd7bb883260 'printf' 'int (const char *, ...)' | `-ImplicitCastExpr 0x7fd7bb898ae8 <col:16> 'const char *' <BitCast> | `-ImplicitCastExpr 0x7fd7bb898ad0 <col:16> 'char *' <ArrayToPointerDecay> | `-StringLiteral 0x7fd7bb898a28 <col:16> 'char [14]' lvalue "hello, world\n" `-ReturnStmt 0x7fd7bb898b60 <line:8:5, col:12> `-IntegerLiteral 0x7fd7bb898b40 <col:12> 'int' 0
TranslationUnitDeclが大元にいて、そこから各関数と変数定義(extern、本 体を持つもの)に繋がっている。特に本体を持つmain関数は本体の内容も出力されて いる。
Stmt(ステートメント)とDecl(関数宣言、変数宣言)はクラス階層になって おり、dyn_cast<Class>()でどのクラスかを判別することができる(これとは 別にenumでも判別可能)。
FunctionDeclは引数を辿るgetParamDecl、関数本体を辿るgetBodyを持つ (externな関数宣言は本体を持たない)。
Clang ASTについての資料。
* The Clang AST Tutorial by Manuel Klimek * Clang 3.5 documentation Introduction to the Clang AST
3. MainCallChecker.cpp
このCheckerはmain関数を呼び出している箇所をwarningとして出力 する。
下記でPreStmt<CallExpr>(関数呼び出し前のエクスプレッション)を使用し ている。筆者はエクスプレッションはステートメントを構成する部品と理解 してます。
class MainCallChecker : public Checker < check::PreStmt<CallExpr> >
CallExprの関数定義を取得し、その名前が"main"か確認する。
void MainCallChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { <snip> const Expr *Callee = CE->getCallee(); const FunctionDecl *FD = state->getSVal(Callee, LC).getAsFunctionDecl(); <snip> IdentifierInfo *II = FD->getIdentifier(); if (!II) // if no identifier, not a simple C function return; <snip> if (II->isStr("main")) {
以下のおまじないを実行してpluginとして登録。
// Register plugin! extern "C" void clang_registerCheckers (CheckerRegistry ®istry) { registry.addChecker<MainCallChecker>("example.MainCallChecker", "Disallows calls to functions called main"); } extern "C" const char clang_analyzerAPIVersionString[] = CLANG_ANALYZER_API_VERSION_STRING;
3.1. 実行
以下のhello.cを入力ファイルとして指定。
#include <stdio.h> int main(void); void call_main(void) { printf("call_main\n"); main(); } int main(void) { static int once = 0; printf("main\n"); if (!once) once = 1; else return 0; call_main(); return 0; }
Clang/LLVMをビルドしたディレクトリにて、以下のコマンドでpluginを実行 (pluginのパスを指定すればどこでも良い)。
$ clang -cc1 -load ./Release+Asserts/lib/SampleAnalyzerPlugin.dylib \ -analyzer-checker=example.MainCallChecker -analyze hello.c <path-to>/hello.c:8:3: warning: call to main main(); ^~~~~~ 1 warning generated.
Checker作成方法については以下の資料が分かりやすい。
* How to Write a Checker in 24 Hours Clang Static Analyzer
4. clang-interpreter
pluginというよりは、clangとは独立したツール。C/C++のソースコードをイン タープリタとして実行。Clang/LLVMをビルドしたディレクトリにて、 MainCallCheckerのhello.cを実行した場合、以下の出力が得られる。
$ ./Release+Asserts/bin/clang-interpreter hello.c main call_main main
以下の変更を加えて、clang-interpreterを再ビルド。
$ svn diff llvm/tools/clang/examples/clang-interpreter/main.cpp Index: llvm/tools/clang/examples/clang-interpreter/main.cpp =================================================================== --- llvm/tools/clang/examples/clang-interpreter/main.cpp (revision 201037) +++ llvm/tools/clang/examples/clang-interpreter/main.cpp (working copy) @@ -53,7 +53,7 @@ return 255; } - llvm::Function *EntryFn = Mod->getFunction("main"); + llvm::Function *EntryFn = Mod->getFunction("call_main"); if (!EntryFn) { llvm::errs() << "'main' function not found in module.\n"; return 255;
再ビルドしたclang-interpreterでhello.cを再実行。call_mainから実行され ている。
./Release+Asserts/bin/clang-interpreter ~/Sources/c/hello.c call_main main call_main main
llvm::ExecutionEngineを用いてJITでエントリ関数を実行するらしいのですが、 おまじないが多くてよく分かりません。ただし、Clang/LLVMのAPIを使用して いるおかげでコードはとてもコンパクトです。
static int Execute(llvm::Module *Mod, char * const *envp) { <snip> OwningPtr<llvm::ExecutionEngine> EE( llvm::ExecutionEngine::createJIT(Mod, &Error)); <snip> return EE->runFunctionAsMain(EntryFn, Args, envp); }
5. まとめ
ASTConsumerを用いればコールグラフのビューアが作れそうです。Checkerを用 いれば独自の静的解析コードが追加できます。加えてclang-interpreterのように面白い独自ツールも作成できそうです。