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のように面白い独自ツールも作成できそうです。