LoginSignup
15
17

More than 5 years have passed since last update.

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

Posted at

Microsoft Visual Studio Community 2013は?

 前回は、clang / LLVMlibToolingによるAST簡易ダンプ・ツールを、MinGWでビルドしQtCreatorでデバッグできるようにしました。
 でも、王道(?)はMSVCの方っぽいので、勉強がてらMSVCでもビルドしてみました。と言ってもビルドだけならやはり簡単でしたので、MSVCのソリューション・ファイルをCMakeで生成できるようにしてみました。

 ついでにAST簡易ダンプ・ツールも少し成長させました。目玉はアノテーションです。他にクラス・テンプレートもダンプしてますぞ。(ザルですが)

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

 今回はVisual Studioを使いますので、下記のようにツールをインストールします。(コンパイラとIDE以外は前回と一緒です。)

ツール 使用目的
Visual Studio 主に個人向けにProfessional相当が無料になりましたね
Subversion clang / LLVMをダウンロードするのに使いました(clang指定)
CMake Makefileの構成(clang指定)

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

ソフトウェア ダウンロードするファイル インストール方法
Visual Studio vs_community.exe vs_community.exeをクリックするとインストールが始まります。オプションはお好みで。
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 ../..
Note
 公式のVisual Studioの説明では、LLVMとClangをチェックアウトするよう書かれてます。その心はCompiler-RTが入っているとビルドできないようです。(ち-ん)MinGW用にダウンロードしたものをそのまま使うとハマりますぞ。

2.2.clang / LLVMをビルド

 ここを参考にしてビルドしました。ざっくり手順は以下のとおりです。
  1.MSVCの環境変数を設定する。
  2.ビルド用フォルダを作り、そこへ移動する
  3.構成し、ビルドし、インストールする(インストールはオプションです。)

 フォルダを下記のように構成しました。(C:は空きが少ないとです。)
 下記フォルダ構成を前提に説明していますので、もし、皆さんのところで実施される場合には適宜読み替えて下さいな。

フォルダ 説明
N:\FOSS\LLVM-clang\llvm clang / LLVMのチェックアウト・フォルダ
N:\FOSS\LLVM-clang\build-msvc2013 MSVCでの32bitビルド・フォルダ・・・①
N:\FOSS\LLVM-clang\build-msvc2013x64 MSVCでの64bitビルド・フォルダ
N:\FOSS\LLVM-clang\install\msvc2013 MSVCでの32bitビルドのインストール先
N:\FOSS\LLVM-clang\install\msvc2013x64 MSVCでの64bitビルドのインストール先

(あう~。今更ですが、"install\msvc2013"ではなくて"install-msvc2013"としておいた方が分かりやすいですね。)

 上記のようにフォルダを構成し、Visual Studio 2013をデフォルトのインストール先へインストールしたとして、batファイル形式で1, 2, 3の手順を説明します。

rem MSVCの環境変数設定
    call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\vcvars32.bat"

rem 32bitビルド・フォルダへ移動する
    N:
    cd \FOSS\LLVM-clang\build-msvc2013

rem 32bitビルド用に構成する
    cmake -G "Visual Studio 12" ..\llvm -DCMAKE_INSTALL_PREFIX=..\install\msvc2013 -DCMAKE_DEBUG_POSTFIX=d

rem ビルドする(デバッグとリリースの両方)
    cmake --build . --config Debug
    cmake --build . --config Release

rem インストールする(やらなくても問題ないです)
    cmake --build . --config Debug --target install
    cmake --build . --config Release --target install

rem 32bitビルド・フォルダへ移動する
    cd \FOSS\LLVM-clang\build-msvc2013x64

rem 64bitビルド用に構成する
    cmake -G "Visual Studio 12 Win64" ..\llvm -DCMAKE_INSTALL_PREFIX=..\install\msvc2013x64 -DCMAKE_DEBUG_POSTFIX=d

rem ビルドとインストールは32bitと同じなので省略

 -DCMAKE_DEBUG_POSTFIX=dにて、デバッグ・ビルドしたライブラリのファイル名の最後にdを追加します。インストールすると、デバッグもリリースも同じフォルダへコピーされるため、そのままだと上書きされてしまいます。ライブラリ以外はそれでも問題ないのですが、ライブラリはどちらとも必要です。そこで、デバッグ・ビルドの方にdを付加することで両方を残すようにしました。
 ということは、インストールしない場合、-DCMAKE_DEBUG_POSTFIX=dは必要はないのですが、MSVCの慣例でもありますので以下の説明はdを付加していることを前提としています。

 寝ている間にコマンド・ラインでビルドしたかったので、cmake --buildを使いました。Visual StudioのIDEを使ってビルドする場合は、LLVM.slnを開いてビルドすれば良いです。

 デバッグ・ビルドはビルドに40分程、インストールに5分程かかりました。
 リリース・ビルドはそれぞれ20分強と1分程でした。(Core™ i7-3820 3.60 GHzのパソコンを使ってます。)
 なお、MinGWより時間が短いですが、MinGW版はCompiler-RTをビルドしてますので、性能比較にはならないです。

インストールしてビルド・フォルダを削除するべきか?
 MSVC版の場合、インストール先のサイズは2GBytes(32bits)~3GBytes(64Bits)程度です。
 ビルド・フォルダは32bits版は14GBytes、64bits版は18GBytesを超えてました!!
 ただ、clang/LLVMは毎日10回以上コミットされています。これだけ更新が早いとビルド・フォルダを消しちゃってよいのか、ちょっと悩ましいです。

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

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

 今回のソースはちょっと長くなった(350行くらい)ので、この投稿の最後に載せます。
 前回からの変更点をざっくり説明します。

3.1.1.clan/LLVMヘッダインクルード時の警告ディセーブル

 前回のソースをMSVCでビルドすると「int型をbool型へ変換したぞ!」等の警告が多量にでます。これらの警告はMSVCの大きなお世話親切なのですが、clang/LLVMはこの警告がでないこと前提で書かれているようです。(clang/LLVM自身のビルド時もディセーブルされていました。)ですので、clang/LLVMのヘッダをインクルードする際には警告をディセーブルしておかないと、本当に見たい警告が埋もれてしまいます。
 そこで、Clang/LLVM用のLLVM.slnに設定されていた下記の/wdオプションをソース側で#pragmaを使ってディセーブルしました。

ディセーブルされていた警告
/wd"4146" /wd"4180" /wd"4244" /wd"4258" /wd"4267" /wd"4291" /wd"4345" /wd"4351" /wd"4355" 
/wd"4456" /wd"4457" /wd"4458" /wd"4459" /wd"4503" /wd"4624" /wd"4722" /wd"4800"

 それでも、C4996が残りました。ここによると問題なさそうなのでこれもディセーブルしています。

3.1.2.アノテーションのダンプ

 C++にもアトリビュートと呼ばれるものがあります。リンク先を見て頂ければ判りますが、アトリビュートはコンパイラへの指示ですね。そして、clangは今までにない新たなアトリビュートを追加することができます。(clangのソースを修正してビルドする必要がありますが。)
 clangを改造する訳ではない人が追加したいケースってないような気がしますが、ASTを使いたい時もたいへんありがたいです。
 そして、clangを修正しないで済めば更にありがたいです。なんと! clangはannotateと言うアトリビュートを最初から用意してくれています。アノテーションと呼ばれているようです。

 具体的には、例えば__attribute__((annotate("This is an annotation.")))をソース・コード中のアトリビュートを付けられるところに書くと、そのアイテムがannotate("This is an annotation.")で修飾されASTの中で取り出すことができます。
 このアノテーションをダンプしてみました。

 なお、当然ですが、MSVCもGCCもannotateを理解できないので、これを残したままこれらのコンパイラでコンパイルしようとするとエラーになります。(clangなら無視してくれそうな気がします。)
 なので、一旦マクロで定義して、AST解析する時はannotateとし、コンパイルする時は消し去るのが良いと思います。

annotateマクロのイメージ
#ifdef AST解析中シンボル
  #define ANNO(A) __attribute__((annotate(#A)))
#else
  #define ANNO(A)
#endif

3.1.3.クラス・テンプレート周りのダンプ

 クラス・テンプレートのダンプに対応しました。内部的には下記の3種類あります。
  1.クラス・テンプレートの定義(ClassTemplateDecl)
  2.クラス・テンプレートの特殊化定義(ClassTemplateSpecializationDecl : CXXRecordDeclを継承)
  3.クラス・テンプレートの部分特殊化定義
   (ClassTemplatePartialSpecializationDecl : CXXRecordDeclとClassTemplateSpecializationDeclを継承)

 2, 3はCXXRecordDeclを継承しているのでVisitCXXRecordDecl()が呼ばれます。
 なので、追加したコードは主に1です。
 2と3についてはisa<ClassTemplateSpecializationDecl>(aCXXRecordDecl)等を使って特殊化なのかどうか表示するようにしました。

 なお、テンプレート引数のダンプには対応していません。
 私自身のテンプレート周りの理解が浅く、部分特殊化で非常に苦労しそうな気がしたのでさぼりました。m(_ _;)m

Note
 2と3については、それぞれ、VisitClassTemplateSpecializationDecl()とVisitClassTemplatePartialSpecializationDecl()をオーバーライドすると、そちらが呼ばれます。

 CXXRecordDecl::getTemplateInstantiationPattern()と言うメンバー関数があります。
 名称や説明から、クラス・テンプレートが実体化された時に非NULL値が返ってくると思ったのですが、そうでもないようです。よく分からなかったので実体化のダンプは見送りました。

3.1.4.細々追加したダンプ

 他にも下記のダンプを追加しています。
  ・typedef
  ・usingディレクティブ
  ・アクセス・スペック(public/protected/private)
  ・型の種類(struct/class/unionなどなど)
  ・struct/class/unicon(CXXRecordDecl)の特徴情報(空とか抽象クラスなどなど)

 これらはソースを見れば判ると思うので説明省略します。

3.2.MSVC用ソリューションの生成

 CMakeでソリューション・ファイルを生成できるようにCMakeLists.txtを作りました。MinGW用のMakefileも生成できるようにしてます。

 基本はここに従って作りました。
 リンクするコンポーネントとライブラリについては、元々の簡易ダンプ・ツールを作った時に参考にしたページソースが公開されているので、そのMakefileから抽出しました。

3.2.1.お約束設定とRelWithDebInfoとMinSizeRelとZERO_CHECKの解除

 まず、CMakeのバージョンを指定します。
  cmake_minimum_required(VERSION 2.8.8)

 次に、CMakeのデフォルトでは、構成(Configuration)としてDebug, Release, RelWithDebInfo, MinSizeRelの4つを生成するようです。後ろ2つは便利そうな気もしますがあまり使わないので、下記で取り除きました。
  set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configs" FORCE)

 何故か分からないのですが、Visual Studioでデバッグ実行しようとすると、常に(変更してなくても)ZERO_CHECKが毎回ビルドして良いか?って聞いてくるので、下記で取り除きました。
  set(CMAKE_SUPPRESS_REGENERATION TRUE)

お約束の設定まとめ
cmake_minimum_required(VERSION 2.8.8)
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configs" FORCE)
set(CMAKE_SUPPRESS_REGENERATION TRUE)
Help!!
 今回の場合、1つしかプロジェクトがないのでALL_BUILDも取り除きたかったのですが、方法を見つけることが出来ませんでした。ご存知の方がいたら、ぜひ教えてください。

3.2.2.プロジェクト名の指定

 あちこちでプロジェクト名を使うので一旦変数に設定し、それをプロジェクト名として設定します。

プロジェクト名の指定
set(PROJECT_NAME example)
project(${PROJECT_NAME})

3.2.3.コンポーネントの指定

 参考にしたページが指定しているコンポーネントを一旦COMPONENT_LISTに設定後、公式ページの説明に従ってライブラリへ展開し、設定します。

コンポーネントの指定
set(COMPONENT_LIST mcparser bitreader support mc option)
llvm_map_components_to_libnames(llvm_libs ${COMPONENT_LIST})
target_link_libraries(${PROJECT_NAME} ${llvm_libs})

3.2.4.ライブラリの指定

 参考にしたページが指定しているコンポーネントを一旦LIBRARY_LISTに設定後、設定します。
 この時、MSVCの時はデバッグ・モード用にサフィックス"d"を不可します。
 ついでに、折角なのでMinGWにも対応しておきたいのでMinGW用のオプションをここで指定します。

ライブラリの指定とMinGW用のオプション指定
# ライブラリのリストアップ
set(LIBRARY_LIST clangFrontend clangSerialization clangDriver clangTooling clangParse clangSema)
set(LIBRARY_LIST ${LIBRARY_LIST} clangAnalysis clangEdit clangAST clangLex clangBasic)

# ライブラリの指定とMinGW用のオプション指定
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
  foreach(link_lib IN LISTS LIBRARY_LIST)
    target_link_libraries(${PROJECT_NAME} optimized ${link_lib})
    target_link_libraries(${PROJECT_NAME} debug     ${link_lib}d)
  endforeach()
else()
  target_link_libraries(${PROJECT_NAME} ${LIBRARY_LIST})
  set(CMAKE_CXX_FLAGS "-std=c++11 -Wno-unused-parameter -fno-strict-aliasing")
  set(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++")
endif()

3.2.5.インクルード・パスとライブラリ・パスとマクロ定義の指定

 インクルード・パスとマクロ定義は概ね公式ページの説明通り、下記設定でできます。

インクルード・パスとマクロ定義
find_package(LLVM REQUIRED CONFIG)
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

 しかし、これだけではライブラリ・パスの指定がありません。下記で指定できました。

ライブラリ・パスの指定
link_directories(${LLVM_LIBRARY_DIRS})

 インストール先を使う時はインクルード・パスは以上で十分です。
 しかし、ビルド・フォルダを使う場合、上記だけではLLVM側しか設定されません。clang側のインクルード・パス指定の追加が必要になります。それぞれのフォルダが下記のように分かれているためです。

フォルダ 内容
<LLVM>\include LLVMのヘッダ・ファイル群
<BUILD>\include LLVMの*.incファイル群(*1)
<LLVM>\tools\clang\include clangのヘッダ・ファイル群
<BUILD>\tools\clang\include clangの*.incファイル群(*1)
<INSTALL>\include clang/LLVM両方のヘッダ・ファイル群と*.incファイル群

(*1)前回のNoteで書いたようなDeclNodes.inc等のファイル群です。
<LLVM> : clang/LLVMをチェックアウトしたフォルダ(2.2.の例ではN:\FOSS\LLVM-clang\llvm)
<BUILD> : ビルドしたフォルダ(2.2.の例ではN:\FOSS\LLVM-clang\build-msvc2013など)
<INSTALL> : インストール先フォルダ(2.2.の例ではN:\FOSS\LLVM-clang\install\msvc2013など)

 そこで、下記の指定を追加します。(LLVM_DIRにビルド・フォルダを指定した時はfind_package()でLLVM_BUILD_MAIN_SRC_DIRが定義されます。)

インクルード・パスの追加指定
if(LLVM_BUILD_MAIN_SRC_DIR)
  include_directories(${LLVM_BUILD_MAIN_SRC_DIR}/tools/clang/include)
  include_directories(${LLVM_BUILD_BINARY_DIR}/tools/clang/include)
endif()

3.2.6.Clang/LLVMの場所を設定する

 3.2.3.のfind_package(LLVM REQUIRED CONFIG)はLLVM_DIRに設定されているパスにあるLLVMConfig.cmakeを実行して、必要な変数を設定しています。
 これは、CMakeを実行する時のコマンド・ライン・パラメータとして下記のように指定します。

  ・インストールまで行い、インストール先(=<INSTALL_PREFIX>)を指定する時
    -DLLVM_DIR=<INSTALL_PREFIX>/share/llvm/cmake/

  ・ビルド・フォルダ(=<LLVM_BUILD_ROOT>)を指定する時
    -DLLVM_DIR=<LLVM_BUILD_ROOT>/share/llvm/cmake/

Note
 このLLVMConfig.cmakeが結構ポイントでした。
 ビルド・フォルダにあるものとインストール・フォルダにあるものは似てますが、そこそこ違っています。よく見るとそれぞれがCMakeLists.txtで使用する変数を定義しています。
 当然、ビルド・フォルダの場合とインストール・フォルダの場合で異なるので、両者のLLVMConfig.cmakeが異なっているということなのです。

3.2.7.ソースファイルを指定する

 最後になりましたが、ソース・ファイルを指定します。

ソース・ファイルの指定
add_executable(${PROJECT_NAME} main.cpp)

3.2.8.CMakeLists.txtまとめ

 以上をCMakeLists.txtに入れて、ソース・ファイル(main.cpp)のあるフォルダに置きます。

CMakeLists.txt
############################################################
#   base
############################################################

cmake_minimum_required(VERSION 2.8.8)

set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configs" FORCE)
set(CMAKE_SUPPRESS_REGENERATION TRUE)

############################################################
#   setting Project informations
############################################################

set(PROJECT_NAME example)

set(LIBRARY_LIST clangFrontend clangSerialization clangDriver clangTooling clangParse clangSema)
set(LIBRARY_LIST ${LIBRARY_LIST} clangAnalysis clangEdit clangAST clangLex clangBasic)
set(COMPONENT_LIST mcparser bitreader support mc option)

############################################################
#   generate makefiles
############################################################

project(${PROJECT_NAME})

find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

include_directories(${LLVM_INCLUDE_DIRS})
if(LLVM_BUILD_MAIN_SRC_DIR)
  include_directories(${LLVM_BUILD_MAIN_SRC_DIR}/tools/clang/include)
  include_directories(${LLVM_BUILD_BINARY_DIR}/tools/clang/include)
endif()
link_directories(${LLVM_LIBRARY_DIRS})
add_definitions(${LLVM_DEFINITIONS})

add_executable(${PROJECT_NAME} main.cpp)

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
  foreach(link_lib IN LISTS LIBRARY_LIST)
    target_link_libraries(${PROJECT_NAME} optimized ${link_lib})
    target_link_libraries(${PROJECT_NAME} debug     ${link_lib}d)
  endforeach()
else()
  target_link_libraries(${PROJECT_NAME} ${LIBRARY_LIST})
  set(CMAKE_CXX_FLAGS "-std=c++11 -Wno-unused-parameter -fno-strict-aliasing")
  set(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++")
endif()

llvm_map_components_to_libnames(llvm_libs ${COMPONENT_LIST})
target_link_libraries(${PROJECT_NAME} ${llvm_libs})

message(STATUS "User selected librarys = ${LIBRARY_LIST}")
message(STATUS "User selected components = ${COMPONENT_LIST}")
message(STATUS "    = ${llvm_libs}")

3.3.ソリューション・ファイルを生成してビルドする

 まず、ソース(main.cpp)とCMakeLists.txtを同じフォルダへ置きます。
 例えば下記のようにします。

フォルダ 説明
C:\example\example main.cppとCMakeLists.txt
C:\example\build-msvc2013 AST簡易ダンプ・ツールのビルド・フォルダ

 このビルド・フォルダで下記コマンドを叩くと、たくさんのビルド用のファイルが生成されます。
 生成されたファイル群の中にMSVCのソリューション・ファイル(example.sln)がありますので、それをVisual Studioで開いてビルドして下さい。

cmake -G "Visual Studio 12" -DLLVM_DIR="N:/FOSS/LLVM-clang/build-msvc2013/share/llvm/cmake" ..\example
Note
 -G "Visual Studio 12"を-G "Visual Studio 12 Win64"に変えれば64bitsビルド用のソリーション・ファイルが生成されます。64bits用の設定は何も書いてないのにです。凄いぞCMake!
MinGWについて
 因みに、MinGWにパスを通しておけば、下記コマンドでMinGW用のMakefileが生成されます。こちらはシングル・コンフィグなので指定したDebugかReleaseのどちらか指定した方のビルド用Makefile群ができます。(N:\FOSS\LLVM-clang\build-mingw-releaseファルダで、clang/LLVMをMinGW32ビットでリリース・ビルドしています。)

cmake -G "MinGW Makefiles" -DLLVM_DIR="N:\FOSS\LLVM-clang\build-mingw-release\share\llvm\cmake" -DCMAKE_BUILD_TYPE="Release" ..\example

 更に、QtQreatorの「プロジェクトを開く」でCMakeLists.txtを指定する方法もあります。CMakeに与える引数を問い合わせてくるので上記の-DLLVM_DIR~"Release"までを指定するとOKです。

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

 Visutal Stduioの使い方はあちらこちらで解説されていますし、特殊なことは何もしていないのでさらっと行きます。

 まず、テスト用のダミーソースです。
 今回追加した機能がざっくり見れるよう少しアップグレードしました。
 ビルド・フォルダの1つ上(上記例の場合C:\example)に置いて下さい。

test.h
#ifdef AST
  #define ANNO(A) __attribute__((annotate(#A)))
#else
  #define ANNO(A)
#endif

struct ANNO(Annotation is attached to StructBase1.) StructBase1 {
    ANNO(Annotation is attached to m_Int.) int m_Int;
};

struct StructBase2 {
    double  m_double;
    virtual ~StructBase2() = 0;
};

struct StructDerived : public StructBase1, private StructBase2 {
    class ClassInner {
        float   m_float;
    } m_ClassInner;

    struct {
        float   m_float2;
    } m_StructInner;
};

template<typename T>
class classFoo {
    typedef T GeneralFoo;
};

// Specialization
template<>
class classFoo<int> {
    typedef int SpecializationFoo;
};

// Parcial Specialization
template<typename T, int N>
class classFoo<T[N]> {
    T ParcialSpecializationFoo[N];
};
test.cpp
namespace foo {

#include "test.h"

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

 Visual Studioでexampleプロジェクトのプロパティのデバッグにあるコマンド引数に「..\test.cpp -- -DAST」を設定した後、Ctrl+F5を押せば実行され、下記のように表示されます。

Result
InclusionDirective : "L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\../test.h"


HandleTranslationUnit()
TopLevel : CXXRecord type_info (<invalid loc>)
TopLevel : Typedef size_t (<invalid loc>)
<<<====================================
typedef unsigned int size_t
--------------------
TypedefDecl : unsigned int size_t
====================================>>>
TopLevel : Typedef __builtin_va_list (<invalid loc>)
<<<====================================
typedef char *__builtin_va_list
--------------------
TypedefDecl : char * __builtin_va_list
====================================>>>
TopLevel : Namespace foo (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\..\test.cpp:1:11)
NamespaceDecl : namespace foo {
TopLevel : CXXRecord StructBase1 (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\../test.h:7:53)
<<<====================================
struct __attribute__((annotate("Annotation is attached to StructBase1."))) StructBase1 {
    int m_Int __attribute__((annotate("Annotation is attached to m_Int.")));
}
--------------------
CXXRecordDecl : StructBase1 {
  Kind : struct
    Annotation : Annotation is attached to StructBase1. 
    Referenced
  FieldDecl : int m_Int
    [AccessSpec : public ]
    [Annotation : Annotation is attached to m_Int. ]
}
====================================>>>
TopLevel : CXXRecord StructBase2 (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\../test.h:11:8)
<<<====================================
struct StructBase2 {
    double m_double;
    virtual ~foo::StructBase2() noexcept = 0;
}
--------------------
CXXRecordDecl : StructBase2 {
  Kind : struct
    Referenced
    Polymorphic
    Abstract
  FieldDecl : double m_double
    [AccessSpec : public ]
}
====================================>>>
TopLevel : CXXRecord StructDerived (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\../test.h:16:8)
<<<====================================
struct StructDerived : public foo::StructBase1, private foo::StructBase2 {
    class ClassInner {
        float m_float;
    };
    class ClassInner m_ClassInner;
    struct {
        float m_float2;
    } m_StructInner;
}
--------------------
CXXRecordDecl : StructDerived {
  Kind : struct
    Referenced
    Polymorphic
  Base : public foo::StructBase1
  Base : private foo::StructBase2
  <<<====================================
  CXXRecordDecl : ClassInner {
    Kind : class
    FieldDecl : float m_float
      [AccessSpec : private ]
  }
  ====================================>>>
  FieldDecl : class ClassInner m_ClassInner
    [AccessSpec : public ]
  FieldDecl : <no-name-type> m_StructInner
    [AccessSpec : public ]
  <<<====================================
  CXXRecordDecl :  {
    Kind : struct
    FieldDecl : float m_float2
      [AccessSpec : public ]
  }
  ====================================>>>
}
====================================>>>
TopLevel : ClassTemplate classFoo (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\../test.h:27:7)
<<<====================================
template <typename T = int> class classFoo {
    typedef int SpecializationFoo;
}
template <typename T> class classFoo {
    typedef T GeneralFoo;
}
--------------------
ClassTemplateDecl : classFoo {
  <<<====================================
  CXXRecordDecl : classFoo {
    Kind : class
      isEmpty
    <<<====================================
    TypedefDecl : T GeneralFoo
    ====================================>>>
  }
  ====================================>>>
}
====================================>>>
TopLevel : ClassTemplateSpecialization classFoo (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\../test.h:33:7)
<<<====================================
class classFoo {
    typedef int SpecializationFoo;
}
--------------------
CXXRecordDecl : classFoo {
  Kind : class
    isEmpty
    This is a Template-Specialization.
  <<<====================================
  TypedefDecl : int SpecializationFoo
  ====================================>>>
}
====================================>>>
TopLevel : ClassTemplatePartialSpecialization classFoo (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\../test.h:39:7)
<<<====================================
class classFoo {
    T ParcialSpecializationFoo[N];
}
--------------------
CXXRecordDecl : classFoo {
  Kind : class
    This is a Template-Parcial-Specialization.
  FieldDecl : T [N] ParcialSpecializationFoo
    [AccessSpec : private ]
}
====================================>>>
TopLevel : Function main (L:\Data\DataOrig\3.Projects\1.FirstTries\3.Serializer-1st\5.TryE\build-example-msvc2013-build\Release\..\..\test.cpp:5:5)
} end of namespace foo
TopLevel : Function operator new (<invalid loc>)
TopLevel : Function operator new[] (<invalid loc>)
TopLevel : Function operator delete (<invalid loc>)
TopLevel : Function operator delete[] (<invalid loc>)

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

 前回ほどではないですが、インクルード・パスの設定は面倒です。

4.3.1.インクルード・パスの指定

 clang/LLVMのインクルード・パスはMSVCに既に設定しているので、それをマクロで使えれば簡単だったのですが、見当たりませんでした。
 なので、1つ1つ指定します。
 2.2のフォルダ構成の32bits版の場合、下記のパスを指定します。

  ・ビルド・フォルダの場合のインクルード・パス
    N:\FOSS\LLVM-clang\llvm\include
    N:\FOSS\LLVM-clang\build-msvc2013\include
    N:\FOSS\LLVM-clang\llvm\tools\clang\include
    N:\FOSS\LLVM-clang\build-msvc2013\tools\clang\include

  ・インストール・フォルダの場合のインクルード・パス
    N:\FOSS\LLVM-clang\install\msvc2013\include

 後、MSVC自身のインクルード・パスの指定も必要です。こちらはマクロ$(VC_IncludePath);$(WindowsSDK_IncludePath)で指定されていました。
 セミコロンで区切られていますが、これを-Iオプションで与えたところlibToolingは受け入れてくれました。

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

 他にMSVCとの互換性を保つパラメータも与えた方が良いような気がします。
 このページによると、下記3つを与えておけば悪くはなさそうです。(ちゃんと読んでないので意味のないパラメータも指定していそうですが...)
  -fms-extensions
  -fms-compatibility
  -fdelayed-template-parsing

 4.3.1.と4.3.2.のパラメータは長くなりすぎるので、プロパティのデバッグにある環境で下記のように設定しました。(ビルド・フォルダの場合。)

環境
PARAM=-fms-extensions -fms-compatibility -fdelayed-template-parsing -std=c++11 -I"$(VC_IncludePath);$(WindowsSDK_IncludePath)" -IN:\FOSS\LLVM-clang\llvm\include -IN:\FOSS\LLVM-clang\build-msvc2013\include -IN:\FOSS\LLVM-clang\llvm\tools\clang\include -IN:\FOSS\LLVM-clang\build-msvc2013\tools\clang\include"
以下は前回と同じなので、ほとんどコピペです
 main.cppがインクルードしているヘッダは本当に多くのヘッダをインクルードするので、結果は凄まじい行数になります。
 私のところでは1,000回以上インクルードし、27万行以上の結果が出力されました。
 そこで、出力をファイルへリダイレクトします。

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

 以上を踏まえてMSVCのコマンド引数を設定します。

コマンド引数
..\example\main.cpp -- %PARAM% >..\main.log 2>&1

 Ctrl+F5等で実行してちょっと待てば、main.logが出力されます。

 結果を見ると__asmで「MS-style inline assembly is not available」エラーが5回出てます。
 ここにも非互換があるようです。
 これもAST解析に実害無いと思いますので、このままにしています。

 なお、64bits版では__asmエラーはでませんでした。

5.あとがき

 いつかはMSVC対応するつもりだったので、この機会に対応してみました。
 MSVC対応そのものは簡単でしたが、Visual Studioはマルチ・コンフィグなのでそれに対応させたくて、時間がかかりました。
 Visutal Studioをシングル・コンフィグで使うって、DebugとRelaseを切り替えて使いたい場合に、Visual Studioを2つ起動しておくとか、いちいちVisual Studioを再起動することになるのですよ。
 なんかそれには耐えれなかったので、CMakeLists.txtを作るのは初めてでしたが頑張ってみました。

 CMakeは本当に強力なだけあって使い方が難しいことと、MSVCに対応した日本語のチュートリアルが見つからなくてたいへんでした。(英語版はあるかも?)
 結局、ここにかなりお世話になりました。また、いろいろググッているうちに出てきたStack OverflowのQAが有用でした。CMAKE_CXX_COMPILER_IDtarget_link_librariesを見つけてやっと問題が解決しましたぜ。これらの方々に感謝感謝。

6.AST簡易ダンプ・ツールのソース

main.cpp
#ifdef _MSC_VER     // start of disabling MSVC warnings
  #pragma warning(push)
  #pragma warning(disable:4146 4180 4244 4258 4267 4291 4345 4351 4355 4456 4457 4458 4459 4503 4624 4722 4800 4996)
#endif

#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"

#ifdef _MSC_VER     // end of disabling MSVC warnings
  #pragma warning(pop)
#endif

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)

    // アクセススペックの表示
    void printAccessSpec(AccessSpecifier as) {
        switch (as)
        {
        case AS_public:     errs() << "public ";    break;
        case AS_protected : errs() << "protected "; break;
        case AS_private :   errs() << "private ";   break;
        default:            errs() << "unknown!!";  break;
        }
    }

    // アノテーションの表示
    void printAnnotation(Decl *aDecl, const char *start, const char *end) {
        if (aDecl->hasAttrs()) {
            errs()<< indentation << start;
            AttrVec &Attrs = aDecl->getAttrs();
            for (AttrVec::const_iterator i=Attrs.begin(), e=Attrs.end(); i!=e; ++i) {
                AnnotateAttr *AA = dyn_cast<AnnotateAttr>(*i);
                if (AA) {
                    errs() << AA->getAnnotation() << " ";
                }
            }
            errs() << end;
        }
    }

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;

            // 型の種類
            errs() << indentation << "Kind : ";
            switch (aCXXRecordDecl->TagDecl::getTagKind()) {
            case TTK_Struct:    errs() << "struct\n";       break;
            case TTK_Interface: errs() << "__interface\n";  break;
            case TTK_Union:     errs() << "union\n";        break;
            case TTK_Class:     errs() << "class\n";        break;
            case TTK_Enum:      errs() << "enum\n";         break;
            default:            errs() << "unknown!!\n";    break;
            }

            // アノテーション(in アトリビュート)
            printAnnotation(aCXXRecordDecl,  "  Annotation : ", "\n");

            // 各種特徴フラグ
            if (aCXXRecordDecl->isUsed())           errs() << indentation << "  Used\n";
            if (aCXXRecordDecl->isReferenced())     errs() << indentation << "  Referenced\n";
            if (aCXXRecordDecl->isPolymorphic())    errs() << indentation << "  Polymorphic\n";
            if (aCXXRecordDecl->isAbstract())       errs() << indentation << "  Abstract\n";
            if (aCXXRecordDecl->isEmpty())          errs() << indentation << "  isEmpty\n";
            if (isa<ClassTemplatePartialSpecializationDecl>(aCXXRecordDecl)) {
                errs() << indentation << "  This is a Template-Parcial-Specialization.\n";
            } else if (isa<ClassTemplateSpecializationDecl>(aCXXRecordDecl)) {
                errs() << indentation << "  This is a Template-Specialization.\n";
            }

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

        // アクセススペック(public, protected, private)
        errs() << indentation << "  [AccessSpec : ";
        printAccessSpec(aFieldDecl->getAccess());
        errs() << "]\n";

        // アノテーション
        printAnnotation(aFieldDecl, "  [Annotation : ", "]\n");

        // 名前無しclass/struct/unionの内容表示
        if (R)  VisitCXXRecordDecl(R, true);
        return true;
    }

    // クラス・テンプレート
    virtual bool VisitClassTemplateDecl(ClassTemplateDecl *aClassTemplateDecl) {
        errs() << indentation << "<<<====================================\n";

        // TopLevelの特別処理
        if (indentation.IndentLevel == 0) {
            // clangによる表示
            aClassTemplateDecl->print(errs(), Policy, 0, true);   // 特殊化も表示する
            errs() << "\n";
            errs() << indentation << "--------------------\n";
        }

        errs() << indentation << "ClassTemplateDecl : " << aClassTemplateDecl->getNameAsString() << " {\n";

        {
            INDENTATION;
            Visit(aClassTemplateDecl->getTemplatedDecl());
        }

        errs() << indentation << "}\n";
        errs() << indentation << "====================================>>>\n";
        return true;
    }

    // typedef
    virtual bool VisitTypedefDecl(TypedefDecl *aTypedefDecl) {
        errs() << indentation << "<<<====================================\n";

        // TopLevelの特別処理
        if (indentation.IndentLevel == 0) {
            // clangによる表示
            aTypedefDecl->print(errs(), Policy);
            errs() << "\n";
            errs() << indentation << "--------------------\n";
        }

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

        errs() << indentation << "====================================>>>\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;
    }

    virtual bool VisitUsingDirectiveDecl(UsingDirectiveDecl *aUsingDirectiveDecl) {
        errs() << "UsingDirectiveDecl : " << aUsingDirectiveDecl->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());
}
15
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
17