Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

おしまい。

Chironian
こんにちは、ケイロニアンです。 最近、実践的なC++入門講座を始めました。 高速でメンテナンス性の高いプログラムを開発するためのノウハウと共にC++の使い方を解説してます。下記サイトの「技術解説」です。無料ですので是非見に来て下さい。 Twitter始めました。https://twitter.com/TheorideTech
http://theolizer.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした