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ツールのIDE と jom |
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をダウンロードするフォルダで実行して下さい。
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のパソコンを使ってます。)
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つだけです。
#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をウォークスルーします。
参考ページとの相違について①
参考ページでは最初に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 |
: | : |
???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名を調べることができると思います。
参考ページとの相違について②
参考ページでは、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と言う名前でプロジェクトを作りました。
次に、このソースをビルドする際、当たり前ですが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を指定しています。
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
最後に、デバッグ・ビルドとリリース・ビルドは下図の左下のボタンを押して切り替えることができます。最初はリリース・ビルドすると良いです。(デバッグ・ビルドは時間がかかります。)
以上の設定ができたら、QtCreatorのビルド・ボタン(左下にあるトンカチ・ボタンです)を押してビルドします。
エラーがでなければ成功です。おめでとう!!
##4.AST簡易ダンプ・ツールの実行
さて、いよいよ実行です。ちゃんとやるにはもう少し設定が必要なのですが、まずは設定がほぼ不要なダミー・ソースのASTをダンプしてみます。
###4.1.テスト用のダミー・ソースで通常実行
プロジェクトを新しく作った時に「パス」を指定した筈ですので、その「パス」のフォルダに下記の2つのソースをおいて下さい。(main.cppやexample.proのあるフォルダの1つ上になります。)
struct StructFoo {
int m_Int;
};
#include "test.h"
int main(void) {
StructFoo foo;
foo.m_Int=0;
return foo.m_Int;
}
QtCreatorで実行する時のカレント・フォルダのデフォルトは、上記test.cppを置いたフォルダの1つ下で、ビルドしたexeのあるフォルダの1つ上になります。
これを踏まえてQtCreatorを設定します。
①「プロジェクト」をクリック。
②「実行」をクリック。(これはこっそりラジオ・ボタンです)
③ここにtest.cppを解析するようAST簡易ダンプ・ツールへのパラメータを設定します。
..\test.cpp --
これで設定終了です。
④の緑▲ボタンを押すと実行されます!!
黒いウィンドウが表示されて、下記のような解析結果が表示されたら成功です。
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へ明示的に与える」必要があります。(ああ、ややこしい。)
#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へ設定する形式で書いています。
少しなりと短くしたかったので???/../を削除してますが、この削除は不要です。
寧ろ削除作業のミスがすっごく痛いので削除しない方が良いかも。私はこれでハマりました。orz
####4.3.2.AST簡易ダンプ・ツールへ与えるオプションの設定と実行
main.cppがインクルードしているヘッダは本当に多くのヘッダをインクルードするので、結果は凄まじい行数になります。
私のところでは1,000回以上インクルードし、10万行以上の結果が出力されました。
そこで、出力をファイルへリダイレクトします。
さて、main()関数の最初のCommonOptionsParserでコマンドライン・オプションを解析しています。これは、解析したいソース・ファイルのリストを"--"の前へ置き、"--"の後へlibToolingへ与えるオプションを置けば良いようです。そして、libToolingのオプションはgccとコンパチのようです。(CAUTION : この辺についてはあまり確認していません。なので間違っている可能性が高いです。ごめん。)
以上を踏まえてQtCreatorを設定します。
①ここにオプションを設定します。
②「詳細」をクリックすると⑤が表示されます。
③「一括編集」を押すと「環境変数の編集」ウィンドウが開きます。
そこへ各自の環境に合わせた「インクルードパス・オプション」をコピペして、OKを押して下さい。
..\example\main.cpp -- -std=c++11 %INC% >..\main.log 2>&1
④の緑▲ボタンを押すと実行されます!!
数秒かけてAST簡易ダンプが、main.logに出力されます。
なお、上記のように標準出力と標準エラー出力をリダイレクトしていると、デバッグ実行できません。デバッグ実行したい時は、">..\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
おしまい。