ClangのlibToolingでASTをダンプするツールを作ってみた

  • 31
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

C++の構文解析ライブラリが欲しい!!

 C++の構文解析ライブラリが欲しくて、いろいろ調べたところ、clang / LLVMのlibToolingが正にぴったりです。しかも、BSDライクなライセンスのオープン・ソース!! そこで、これを使ってC++のAST(の最低限)をダンプするごく小さなツールを作ってみました。(200行ちょっとのcppを1つだけ)

 AST簡易ダンプ・ツールは32bitsアプリとしてビルドしています。
 Windows 7 Professionalの64bit版で動作確認しましたが、Windows 7 32bit版でも動くと思います。

1.使用ツールのインストール

 LLVMはWindows上ではVisual Studioのみ動作確認されているようですので、Visual Studioを使った方がよいのですが、今回はMinGW+QtCreatorを使いました。(Visual Studioはまだインストールしてないとです。)

ツール 使用目的
MinGW-w64 今回はgccをコンパイラとして使いました
QtCreator Qtという強力なRADツールIDEjom
Subversion clang / LLVMをダウンロードするのに使いました(clang指定)
CMake Makefileの構成(clang指定)

 下記ようにインストールしました。

ソフトウェア ダウンロードするファイル インストール方法
Qt5.4.0 qt-opensource-windows-x86-1.6.0-7-online.exe ここを参考にインストールして下さい。MinGWもインストールされます。
TortoiseSVN(*1) TortoiseSVN-1.8.10.26129-win32-svn-1.8.11.msi 普通にインストールして下さい。(私が使ったバージョンは1.8.8の64bits版ですが、最新32bits版のこれも問題ない筈です。)
CMake cmake-3.1.1-win32-x86.exe 普通にインストールして下さい。(私が使ったバージョンは3.0.2ですが、最新版のこれも問題ない筈です。)

(*1)SubversionをExplorerから呼び出せるようにした非常に便利なツールです。これにSubversionも含まれています。
 同じ所に日本語パック(LanguagePack_1.8.10.26129-win32-ja.msi)もあります。インストールすると日本語化されます。

2.clang / LLVMのダウンロードとビルド

2.1 ここに従ってSubversionでソース群をダウンロード

 ダウンロードするバッチ・ファイルを作りました。clangをダウンロードするフォルダで実行して下さい。

checkout.bat
rem Checkout LLVM
    svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

rem Checkout Clang:
    cd llvm/tools
    svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
    cd ../..

rem Checkout extra Clang Tools: (optional)
    cd llvm/tools/clang/tools
    svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
    cd ../../../..

rem Checkout Compiler-RT:
    cd llvm/projects
    svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
    cd ../..

2.2 ここを参考にしつつ、clang / LLVMをビルド

 手順としては下記となります。
  1.MinGWとQtCreatorにパスを通しておく。
  2.ビルド用フォルダを作り、そこへ移動する
  3.構成し、ビルドし、インストールする

 パスを通し、clangをダウンロードしたフォルダにいるとして、2と3の手順をbatファイル形式で示します。

rem デバッグ・ビルドする
    mkdir build-llvm-debug
    cd build-llvm-debug
    CMake -G "MinGW Makefiles" -DCMAKE_C_FLAGS:STRING="-static -static-libgcc -static-libstdc++" -DCMAKE_CXX_FLAGS:STRING="-static -static-libgcc -static-libstdc++" -DCMAKE_CXX_FLAGS=-Og -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:PATH="../install/debug" ..\llvm\
    jom
    jom install

rem リリース・ビルドする
    mkdir build-llvm-release
    cd build-llvm-release
    CMake -G "MinGW Makefiles" -DCMAKE_C_FLAGS:STRING="-static -static-libgcc -static-libstdc++" -DCMAKE_CXX_FLAGS:STRING="-static -static-libgcc -static-libstdc++" -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH="../install/release" ..\llvm\
    jom
    jom install

 デバッグ・ビルドのポイントは-Ogオプションです。これを付けないとファイルが大きすぎると怒られます。
 デバッグ・ビルドはビルドに60分以上、インストールにも10分以上かかりました。
 リリース・ビルドはそれぞれ35分と数分でした。(Core™ i7-3820 3.60 GHzのパソコンを使ってます。)

Note
 libToolingをリンクしたアプリケーションのデバッグ・ビルドに時間がかかります。今回のごく小さなソースでも数十秒かかります。リリース・ビルドなら数秒です。しかし、リリース・ビルドではブレークを張っても止まってくれません。
 なので、デバッグ・ビルドとリリース・ビルドの両方を使った方が楽と思います。

3.AST簡易ダンプ・ツールのソースとビルド

 主にここを参考にしました。
 プリプロセス周りはclangソースの"llvm\tools\clang\tools\extra\pp-trace\"を参考にしました。

3.1.ソースとその概要説明

 ASTをウォークスルーするために必要なことは意外に簡単です。(なのに大抵のことができそう。凄い!!)

  ・プリプロセス追跡をするなら、PPCallbacksを継承して、必要なコールバック関数をオーバーライドする。
  ・ASTウォークスルーのため、DeclVisitorを継承して、必要なVisit関数をオーバーライドする。
  ・定番の記述(mainとFrontendActionとASTConsumer。全部で30行くらい。)

 200行ちょっとですので、AST簡易ダンプ・ツールのソースを下記に載せます。main.cpp1つだけです。

main.cpp
#include "clang/AST/AST.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclVisitor.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"

using namespace std;
using namespace clang;
using namespace clang::tooling;
using namespace llvm;

// ***************************************************************************
//          プリプロセッサからのコールバック処理
// ***************************************************************************

class PPCallbacksTracker : public PPCallbacks {
private:
    Preprocessor &PP;
public:
    PPCallbacksTracker(Preprocessor &pp) : PP(pp) {}
    void InclusionDirective(SourceLocation HashLoc,
                          const Token &IncludeTok,
                          llvm::StringRef FileName, 
                          bool IsAngled,
                          CharSourceRange FilenameRange,
                          const FileEntry *File,
                          llvm::StringRef SearchPath,
                          llvm::StringRef RelativePath,
                          const Module *Imported) override {
        errs() << "InclusionDirective : ";
        if (File) {
            if (IsAngled)   errs() << "<" << File->getName() << ">\n";
            else            errs() << "\"" << File->getName() << "\"\n";
        } else {
            errs() << "not found file ";
            if (IsAngled)   errs() << "<" << FileName << ">\n";
            else            errs() << "\"" << FileName << "\"\n";
        }
    }
};

// ***************************************************************************
//          ASTウォークスルー
// ***************************************************************************

//llvm\tools\clang\include\clang\AST\DeclVisitor.hの36行目参照↓voidは指定できない。
class ExampleVisitor : public DeclVisitor<ExampleVisitor, bool> {
private:
    PrintingPolicy      Policy;
    const SourceManager &SM;
public:
    ExampleVisitor(CompilerInstance *CI) : Policy(PrintingPolicy(CI->getASTContext().getPrintingPolicy())),
                                           SM(CI->getASTContext().getSourceManager()) {
        Policy.Bool = 1;    // print()にてboolが_Boolと表示されないようにする
    }   //↑http://clang.llvm.org/doxygen/structclang_1_1PrintingPolicy.html#a4a4cff4f89cc3ec50381d9d44bedfdab
private:
    // インデント制御
    struct CIndentation {
        int             IndentLevel;
        CIndentation() : IndentLevel(0) { }
        void Indentation(raw_ostream &OS) const {
            for (int i=0; i < IndentLevel; ++i) OS << "  ";
        }
        // raw_ostream<<演算子定義
        friend raw_ostream &operator<<(raw_ostream &OS, const CIndentation &aCIndentation) {
            aCIndentation.Indentation(OS);
            return OS;
        }
    } indentation;
    class CIndentationHelper {
    private:
        ExampleVisitor  *parent;
    public:
        CIndentationHelper(ExampleVisitor *aExampleVisitor) : parent(aExampleVisitor) {
            parent->indentation.IndentLevel++;
        }
        ~CIndentationHelper() { parent->indentation.IndentLevel--; }
    };
    #define INDENTATION CIndentationHelper CIndentationHelper(this)
public:
    // DeclContextメンバーの1レベルの枚挙処理
    void EnumerateDecl(DeclContext *aDeclContext) {
        for (DeclContext::decl_iterator i = aDeclContext->decls_begin(), e = aDeclContext->decls_end(); i != e; i++) {
            Decl *D = *i;
            if (indentation.IndentLevel == 0) {
                errs() << "TopLevel : " << D->getDeclKindName();                                    // Declの型表示
                if (NamedDecl *N = dyn_cast<NamedDecl>(D))  errs() << " " << N->getNameAsString();  // NamedDeclなら名前表示
                errs() << " (" << D->getLocation().printToString(SM) << ")\n";                      // ソース上の場所表示
            }
            Visit(D);       // llvm\tools\clang\include\clang\AST\DeclVisitor.hの38行目
        }
    }

    // class/struct/unionの処理
    virtual bool VisitCXXRecordDecl(CXXRecordDecl *aCXXRecordDecl, bool aForce=false) {
        // 参照用(class foo;のような宣言)なら追跡しない
        if (!aCXXRecordDecl->isCompleteDefinition()) {
    return true;
        }

        // 名前無しなら表示しない(ただし、強制表示されたら表示する:Elaborated用)
        if (!aCXXRecordDecl->getIdentifier() && !aForce) {
    return true;
        }

        errs() << indentation << "<<<====================================\n";

        // TopLevelなら参考のためlibToolingでも表示する
        if (indentation.IndentLevel == 0) {
            aCXXRecordDecl->print(errs(), Policy);
            errs() << "\n";
            errs() << indentation << "--------------------\n";
        }

        // クラス定義の処理
        errs() << indentation << "CXXRecordDecl : " << aCXXRecordDecl->getNameAsString() << " {\n";
        {
            INDENTATION;

            // 基底クラスの枚挙処理
            for (CXXRecordDecl::base_class_iterator Base = aCXXRecordDecl->bases_begin(), BaseEnd = aCXXRecordDecl->bases_end();
                 Base != BaseEnd;
                 ++Base) {                                          // ↓型名を取り出す(例えば、Policy.Bool=0の時、bool型は"_Bool"となる)
                errs() << indentation << "Base : " << Base->getType().getAsString(Policy) << "\n";
            }

            // メンバーの枚挙処理
            EnumerateDecl(aCXXRecordDecl);
        }
        errs() << indentation << "}\n";
        errs() << indentation << "====================================>>>\n";
        return true;
    }

    // メンバー変数の処理
    virtual bool VisitFieldDecl(FieldDecl *aFieldDecl) {
        // 名前無しclass/struct/unionでメンバー変数が直接定義されている時の対応
        CXXRecordDecl *R=NULL;      // 名前無しの時、内容を表示するためにCXXRecordDeclをポイントする
        const Type *T = aFieldDecl->getType().split().Ty;
        if (T->getTypeClass() == Type::Elaborated) {
            R = cast<ElaboratedType>(T)->getNamedType()->getAsCXXRecordDecl();
            if (R && (R->getIdentifier()))  R = NULL;
        }
        // 内容表示
        if (R) {
            errs() << indentation << "FieldDecl : <no-name-type> " << aFieldDecl->getNameAsString() << "\n";
            VisitCXXRecordDecl(R, true);    // 名前無しclass/struct/unionの内容表示
        } else {
            errs() << indentation << "FieldDecl : " << aFieldDecl->getType().getAsString(Policy) << " " << aFieldDecl->getNameAsString() << "\n";
        }
        return true;
    }

    // namespaceの処理(配下を追跡する)
    virtual bool VisitNamespaceDecl(NamespaceDecl *aNamespaceDecl) {
        errs() << "NamespaceDecl : namespace " << aNamespaceDecl->getNameAsString() << " {\n";
        EnumerateDecl(aNamespaceDecl);
        errs() << "} end of namespace " << aNamespaceDecl->getNameAsString() << "\n";
        return true;
    }

    // extern "C"/"C++"の処理(配下を追跡する)
    virtual bool VisitLinkageSpecDecl(LinkageSpecDecl*aLinkageSpecDecl) {
        string lang;
        switch (aLinkageSpecDecl->getLanguage()) {
        case LinkageSpecDecl::lang_c:   lang="C"; break;
        case LinkageSpecDecl::lang_cxx: lang="C++"; break;
        }
        errs() << "LinkageSpecDecl : extern \"" << lang << "\" {\n";
        EnumerateDecl(aLinkageSpecDecl);
        errs() << "} end of extern \"" << lang << "\"\n";
        return true;
    }
};

// ***************************************************************************
//          定番の処理
// ***************************************************************************

class ExampleASTConsumer : public ASTConsumer {
private:
    ExampleVisitor *visitor; // doesn't have to be private
public:
    explicit ExampleASTConsumer(CompilerInstance *CI) : visitor(new ExampleVisitor(CI)) {
        // プリプロセッサからのコールバック登録
        Preprocessor &PP = CI->getPreprocessor();
        PP.addPPCallbacks(llvm::make_unique<PPCallbacksTracker>(PP));
    }
    // AST解析結果の受取
    virtual void HandleTranslationUnit(ASTContext &Context) {
        errs() << "\n\nHandleTranslationUnit()\n";
        visitor->EnumerateDecl(Context.getTranslationUnitDecl());
    }
};

class ExampleFrontendAction : public SyntaxOnlyAction /*ASTFrontendAction*/ {
public:
    virtual std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef file) {
        return llvm::make_unique<ExampleASTConsumer>(&CI); // pass CI pointer to ASTConsumer
    }
};

static cl::OptionCategory MyToolCategory("My tool options");
int main(int argc, const char **argv)
{
    CommonOptionsParser op(argc, argv, MyToolCategory);
    ClangTool Tool(op.getCompilations(), op.getSourcePathList());
    return Tool.run(newFrontendActionFactory<ExampleFrontendAction>().get());
}

3.1.1.ソースの説明 - 定番の処理

 AST解析を行う場合に「お約束」的な記述になるようです。
 目的によっては、それぞれの部分をそれなりに修正したり、間に処理を追加したりするケースもあると思いますが、大きな流れは変わらないのではないかと思います。

・main()関数 実質3行です。
 CommonOptionsParserクラスで、コマンドラインからソース・ファイルのリストとオプション・リストを取り出します。
 それらを与えて、ClangToolクラスのオブシェクトを生成します。
 そのオブジェクトのrun()を実行します。この時、FrontendActionを継承した自分のオブジェクトを与えることで、AST解析結果を受け取ります。

・ExampleFrontendActionクラス 中身は2行です。
 これが自分のFrontendActionクラスです。
 と言っても、ASTConsumerを継承した自分用オブジェクトの生成関数をオーバーライドするだけです。

・ExampleASTConsumerクラス
 これは自分用ASTConsumerクラスです。役割は2つあります。
 ①コンストラクタで、PPCallbacksを継承した自分用オブジェクトを登録しています。
 ②HandleTranslationUnit()をオーバーライドしています。
  これは全てのAST解析が終了してから呼ばれます。
  この中でASTをウォークスルーします。

参考ページとの相違について①

相違点1
参考ページでは最初にHandleTopLevelDecl()を紹介し、次により良い実装としてHandleTranslationUnit()を紹介しています。このHandleTopLevelDecl()ですが、AST解析中にトップレベル要素の解析が1つ終わる度に呼ばれます。namespaceがあるとそれがトップレベル要素になるため、namespaceが閉じるまで呼ばれませんからHandleTopLevelDecl()はイマイチ使いドコロが難しそうです。

3.1.2.ソースの説明 - プリプロセス追跡

・PPCallbacksTrackerクラス(これ1つです)
 ExampleASTConsumerのコンストラクタで登録する、自分用にカスタマイズしたPPCallbacksクラスです。
 今回はInclusionDirective()メンバー関数だけをオーバーライドしています。
 これは、プリプロセス中(同時にAST解析も進んでいます)に#includeに出会う度にコールバックされ、#include情報を教えてくれます。今回は#includeしたファイルのフル・パスをダンプしています。
 コールバック関数は非常に数多く用意されているようです。詳細は公式ドキュメントやpp-traceを見るのが良いと思います。

3.1.3.ソースの説明 - ASTウォークスルー 【ここが今回の本題です】

・ExampleVisitorクラス(これ1つです)
 DeclVisitorクラスを継承してExampleVisitorを定義しています。

 ここで、ウォークスルーしたい要素に対応するVisit???Decl()関数をオーバーライドします。
 これは、ASTの中に???Declが現れる度によばれます。
 ???Declは言語の様々な要素を示します。今回作ったASTダンプ・ツールでは下記を使っています。

???Decl C++要素
CXXRecordDecl class, struct, union
FieldDecl メンバー変数
NamespaceDecl namespace定義
LinkageSpecDecl extern "C", extern "C++"

 ???Declは本当に多数あり、調べただけでも下記のようなものもありました。

???Decl C++要素
CXXMethodDecl メンバー関数
ClassTemplateDecl クラス・テンプレート定義
ClassTemplateSpecializationDecl クラス・テンプレートの特殊化定義
ClassTemplatePartialSpecializationDecl クラス・テンプレートの部分特殊化定義
FunctionTemplateDecl 関数テンプレート
TypedefDecl typedef
UsingDirectiveDecl using
  :     :  
Note
 ???Declの一覧があると良いのですが、見つけることができませんでした。
 これらはExampleVisitor::EnumerateDecl()が呼び出しているVisit()関数の中でディスパッチされています。
 このVisit()関数は、clangソースのllvm\tools\clang\include\clang\AST\DeclVisitor.hの38行目で定義されています。これは幾つかのマクロを定義して、clangをビルドしたフォルダの\build-llvm-release\tools\clang\include\clang\AST\DeclNodes.incをインクルードして、case文を生成しています。
 ということは、一覧がDeclNodes.incにあることになるのですが、このファイル自体がclangをビルドする時に生成されているようなのです。その生成元までは追求しませんでした。
 今回のASTダンプ・ツールでは、検出された全てのDecl名を表示するようにしていますので、追跡したい言語要素が分かっていれば、それを含むソースをAST簡易ダンプすることでそのDecl名を調べることができると思います。

参考ページとの相違について②

相違点2
 参考ページでは、RecursiveASTVisitorクラスを派生してExampleVisitorを作り、HandleTopLevelDecl()からvisitor->TraverseDecl(D);を呼び出してウォクスルーしています。
 しかし、私はDeclVisitorクラスから派生し、自作のEnumerateDecl()を呼び出すように変更しました。
 私は、名前無しstructで定義された変数(struct {...} foo;のような定義)についても、変数名と結びつけて名前無しstructの内容を追跡したかったので、その変数用のVisitFieldDeclが呼ばれた時に名前無しstructの内容も追跡するようにしました。その場合にRecursiveASTVisitorを使うと、まず名前無しstructでVisitCXXRecordDecl()が呼ばれ、次にそれを使って定義した変数で呼ばれるため、2度同じ名前無しstructを追跡することになります。
 これを避けたかったので、1レベルだけ枚挙する再帰呼出可能なEnumerateDecl()関数を作って対応しています。そのため、一部自力で追跡処理する必要が出てきました。今回の場合はnamespaceとextern "C"/"C++"です。

3.2.QtCreatorでのビルド方法

 まず、QtCreatorで新しいプロジェクトで「非Qtプロジェクト」の「C++プロジェクト」を作ります。
 exampleと言う名前でプロジェクトを作りました。

新しいプロジェクト.png

 次に、このソースをビルドする際、当たり前ですがlibToolingのインクルードパスとライブラリを指定する必要があります。
 インクルードパスとライブラリパスは、clangをインストールしたフォルダの下のincludeとlibです。
 そして、ライブラリですが、参考ページソースが公開されているので、そのMakefileを参考に抽出しました。
 ちょっと苦労したのがLINK_COMPONENTSでの指定です。ここによるとllvm-config --libsと言うコマンドでライブラリがリストできるということでした。

 このままビルドすると「C++11規格でコンパイルするように」等のエラーやその他の警告がでますので、エラーや警告が出ないようにするために幾つかのビルド・オプション(-std=c++11 -Wno-unused-parameter -fno-strict-aliasing)も与えました。
 更に、gccのライブラリをスタティック・リンクするオプションを与えました。(でないとgccのライブラリdllをコピーしないと単独で走らないから面倒なのです。)

 以上より、私のところではプロジェクト・ファイルであるexample.proを下記のように設定しました。(add for libTooling以下を追加してます。)
 なお、clangをビルドする際にインストール先としてN:\FOSS\LLVM-clang\install\releaseとN:\FOSS\LLVM-clang\install\debugを指定しています。

example.pro
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += main.cpp

include(deployment.pri)
qtcAddDeployment()

# add for libTooling
QMAKE_CXXFLAGS += -std=c++11 -Wno-unused-parameter -fno-strict-aliasing
LIBS += -static -static-libgcc -static-libstdc++

win32:CONFIG(release, debug|release){
    INCLUDEPATH = N:\FOSS\LLVM-clang\install\release\include
    LIBS += -LN:\FOSS\LLVM-clang\install\release\lib
}
else:win32:CONFIG(debug, debug|release){
    INCLUDEPATH = N:\FOSS\LLVM-clang\install\debug\include
    LIBS += -LN:\FOSS\LLVM-clang\install\debug\lib
}

LIBS += -lclangFrontend -lclangSerialization -lclangDriver
LIBS += -lclangTooling -lclangParse -lclangSema
LIBS += -lclangAnalysis -lclangRewriteFrontend -lclangRewrite
LIBS += -lclangEdit -lclangAST -lclangLex -lclangBasic
LIBS += $(shell N:\FOSS\LLVM-clang\install\release\bin\llvm-config --libs mcparser bitreader support mc option)
# for MinGW
LIBS += -limagehlp

 最後に、デバッグ・ビルドとリリース・ビルドは下図の左下のボタンを押して切り替えることができます。最初はリリース・ビルドすると良いです。(デバッグ・ビルドは時間がかかります。)

デバッグとリリース切替.png

 以上の設定ができたら、QtCreatorのビルド・ボタン(左下にあるトンカチ・ボタンです)を押してビルドします。
 エラーがでなければ成功です。おめでとう!!

4.AST簡易ダンプ・ツールの実行

 さて、いよいよ実行です。ちゃんとやるにはもう少し設定が必要なのですが、まずは設定がほぼ不要なダミー・ソースのASTをダンプしてみます。

4.1.テスト用のダミー・ソースで通常実行

 プロジェクトを新しく作った時に「パス」を指定した筈ですので、その「パス」のフォルダに下記の2つのソースをおいて下さい。(main.cppやexample.proのあるフォルダの1つ上になります。)

test.h
struct StructFoo {
    int m_Int;
};
test.cpp
#include "test.h"

int main(void) {
  StructFoo foo;
  foo.m_Int=0;
  return foo.m_Int;
}

 QtCreatorで実行する時のカレント・フォルダのデフォルトは、上記test.cppを置いたフォルダの1つ下で、ビルドしたexeのあるフォルダの1つ上になります。
 これを踏まえてQtCreatorを設定します。

実行①.png

①「プロジェクト」をクリック。
②「実行」をクリック。(これはこっそりラジオ・ボタンです)
③ここにtest.cppを解析するようAST簡易ダンプ・ツールへのパラメータを設定します。

パラメータ
..\test.cpp --

 これで設定終了です。
④の緑▲ボタンを押すと実行されます!!
 黒いウィンドウが表示されて、下記のような解析結果が表示されたら成功です。

Result
InclusionDirective : "C:\example\build-example-Desktop_Qt_5_4_0_MinGW_32bit-Release\../test.h"


HandleTranslationUnit()
TopLevel : Typedef __builtin_va_list (<invalid loc>)
TopLevel : CXXRecord StructFoo (C:\example\build-example-Desktop_Qt_5_4_0_MinGW_32bit-Release\../test.h:1:11)
<<<====================================
struct StructFoo {
    int m_Int;
}
--------------------
CXXRecordDecl : StructFoo {
  FieldDecl : int m_Int
}
====================================>>>
TopLevel : Function main (C:\example\build-example-Desktop_Qt_5_4_0_MinGW_32bit-Release\..\test.cpp:3:5)

4.2.テスト用のダミー・ソースでデバッグ実行

 デバック実行すれば、ブレークを張って中断・再開やステップ実行ができるので便利です。

 緑▲ボタンの上のボタンを押して「デバッグ」へ切り替え、「トンカチ」ボタンを押してビルドします。
 ブレークの張り方は多くのIDEと同じで、ソースを表示してブレークを張りたい行の頭付近をクリックです。
 そして、④の緑▲ボタンの下にあるルーペ付き緑▲ボタンを押すとデバッグ実行できます。

 ・・・

 の筈だったのですが、Qt5.4.0のQtCreatorにはバグがあり、ひっかかりました。
 しかし、上記のリンクしているページに既に対策が載っていました。
 Qtを標準のC:\Qtへインストールした場合、
  C:\Qt\Tools\QtCreator\share\qtcreator\QtProject\qtcreator\debuggers.xml
を下記のように修正して、QtCreatorを再起動すれば良いです。

  変更前>  <value type="uint" key="EngineType">0</value>
  変更後>  <value type="uint" key="EngineType">1</value>

4.3.AST簡易ダンプ・ツールのソースをAST簡易ダンプする

 上記のダミー・ソースは本当に単純で、インクルードパスの指定さえ不要でした。
 でも、一般には全てのヘッダがカレントにある筈もなく、インクルードパスの指定は必須です。
 また、libToolingはC++11規格に対応する必要があります。(gccと同様libToolingでもC++を使えってエラーが出ます。)
 そこで、一般的なソースとしてmain.cppを簡易ダンプしてみます。

 そのためはもう少し設定が必要です。

4.3.1.MinGWのインクルードパスについて

 今回、私はAST簡易ダンプ・ツールをMinGWでビルドしました。
 MinGWはコンパイルする際にインクルードパスを自動生成します。つまり、指定していないパスからもインクルードしちゃうのです。IDEにインクルードパスを設定しなくて良いので便利なのですが、今回のようなケースではハマります。

 libToolingも似た動作をするようですが、MinGWが自動生成するインクルードパスとは異なるのです。
 従って、「MinGWでビルドできるソース」を「libToolingで解析する」場合には、「MinGWが自動生成するインクルードパス」を「libToolingへ明示的に与える」必要があります。(ああ、ややこしい。)

私の環境のMinGWインクルードパス
#include "..." search starts here:
#include <...> search starts here:
 N:/Qt/Tools/mingw491_32/bin/../lib/gcc/i686-w64-mingw32/4.9.1/include
 N:/Qt/Tools/mingw491_32/bin/../lib/gcc/i686-w64-mingw32/4.9.1/include-fixed
 N:/Qt/Tools/mingw491_32/bin/../lib/gcc/i686-w64-mingw32/4.9.1/../../../../i686-w64-mingw32/include
 N:/Qt/Tools/mingw491_32/lib/gcc/../../i686-w64-mingw32/include/c++
 N:/Qt/Tools/mingw491_32/lib/gcc/../../i686-w64-mingw32/include/c++/i686-w64-mingw32
 N:/Qt/Tools/mingw491_32/lib/gcc/../../i686-w64-mingw32/include/c++/backward
End of search list.

 私の環境では、"g++ -v test.cpp"で表示して抜き出したところ、上記のようにインクルードパスが表示されました。また、clangのライブラリをインクルードするソースを解析する時はclangヘッダのインクルードパスも与える必要があります。
 従って、AST簡易ダンプ・ツールへ与えるインクルードパスのオプションは下記のようになります。

インクルードパス・オプション
INC=-IN:/Qt/Tools/mingw491_32/lib/gcc/i686-w64-mingw32/4.9.1/include -IN:/Qt/Tools/mingw491_32/lib/gcc/i686-w64-mingw32/4.9.1/include-fixed -IN:/Qt/Tools/mingw491_32/i686-w64-mingw32/include -IN:/Qt/Tools/mingw491_32/i686-w64-mingw32/include/c++ -IN:/Qt/Tools/mingw491_32/i686-w64-mingw32/include/c++/i686-w64-mingw32 -IN:/Qt/Tools/mingw491_32/i686-w64-mingw32/include/c++/backward -IN:\FOSS\LLVM-clang\install\release\include

 あまりに長いので一旦環境変数へ記録することにしました。なので、環境変数INCへ設定する形式で書いています。

Note
少しなりと短くしたかったので???/../を削除してますが、この削除は不要です。
寧ろ削除作業のミスがすっごく痛いので削除しない方が良いかも。私はこれでハマりました。orz

4.3.2.AST簡易ダンプ・ツールへ与えるオプションの設定と実行

 main.cppがインクルードしているヘッダは本当に多くのヘッダをインクルードするので、結果は凄まじい行数になります。
 私のところでは1,000回以上インクルードし、10万行以上の結果が出力されました。
 そこで、出力をファイルへリダイレクトします。

 さて、main()関数の最初のCommonOptionsParserでコマンドライン・オプションを解析しています。これは、解析したいソース・ファイルのリストを"--"の前へ置き、"--"の後へlibToolingへ与えるオプションを置けば良いようです。そして、libToolingのオプションはgccとコンパチのようです。(CAUTION : この辺についてはあまり確認していません。なので間違っている可能性が高いです。ごめん。)

 以上を踏まえてQtCreatorを設定します。

実行②.png

①ここにオプションを設定します。
②「詳細」をクリックすると⑤が表示されます。
③「一括編集」を押すと「環境変数の編集」ウィンドウが開きます。
 そこへ各自の環境に合わせた「インクルードパス・オプション」をコピペして、OKを押して下さい。

オプション
..\example\main.cpp -- -std=c++11 %INC% >..\main.log 2>&1

④の緑▲ボタンを押すと実行されます!!
 数秒かけてAST簡易ダンプが、main.logに出力されます。

Note
 なお、上記のように標準出力と標準エラー出力をリダイレクトしていると、デバッグ実行できません。デバッグ実行したい時は、">..\main.log 2>&1"を付けないようにして下さい。

 結果を見ると_builtin???の未定義エラーが多量にでてます。
 clangはgccと基本的に互換性があるようですが、一部非互換です。
 特にSIMD命令の実装方法の違いが大きく、main.cppのAST解析でbuiltin関数に関連するエラーが多数(-ferror-limit=1000を与えると700回以上)出てしまうのです。

 たぶん、これらのbuiltin関数を宣言した独自ヘッダを内部的にインクルードする等で対策できるだろうと思いますか、なかなかたいへんそうです。
 builtin関数に関連したエラーしかでてないし、AST解析はできるので対策してません。

5.あとがき

 QtCreatorは32bit版でも64bits版アプリをビルド・デバッグできることが分かっていたし、Qt本体は使わないので、Qtの64bits版はなくてもAST簡易ダンプ・ツールを64bitsビルドできると思い、64bitsビルドでこの投稿を作り始めました。が、間違いでした。(あうちっ)

 QtCreatorは、ビルドするための環境をkitとして管理しています。ここにコンパイラやデバッガを登録して使います。コンパイラとデバッガはMinGWに含まれていますが、もう一つqmakeも必要なことを忘れていました。

 qmakeはQtをビルドすればできますが、MinGWの64bits用のqmakeはQt公式からは配布されていません。つまり、上記のAST簡易ダンプ・ツールをMinGWで64bitsビルドする際にIDEとしてQtCreatorを使う場合に限り、QtをMinGWの64bitsビルドしておく必要があります。
 qmakeのためだけにQt一式をビルド(かなり時間がかかります)するのはあまりに勿体無いので、ターゲットを32bitsへ急遽変更しました。なので、なにかミスっているかも。もし、ミスを見つけたら教えて下さい。m(__)m

おしまい。