2011年11月20日日曜日

CLangでクラスの中身を調べる:複数のソースコードをまとめてパース・・・はできない

前回で単cppの重複定義を取り除くことができた。が、複数h/cppをパースさせた場合にどうなるのか心配になってきた。
なので今回は複数のh/cppを入力してみる。

cppをテキトーに追加してみる

ソースコードをメモリに展開するのはclang::SourceManagerである。しかしSourceManagerに(mainではない)ソースファイルを追加するであろうcreateFileID()は、インクルードパスのSourceLocationを要求する。createMainFileID()は要求しないのに。

この違いは何かと思って、createMainFileID()を使わずに以下のようにファイルを追加してみる。

srcMgr.createFileID(fileMgr.getFile("D:\\develop\\Clang\\SampleProj\\Package1\\Super.cpp"),
                    clang::SourceLocation(), clang::SrcMgr::C_User);

すると以下のようなエラーメッセージが出てしまう。

>fatal error: error opening file '<invalid loc>':

うーん、そうですか。ダメですか。でもcreateMainFileID()を複数回呼ぶのもダメですよね。

じゃあってことで、1つめのコードはcreateMainFileID()を、2つめ以降はcreateFileID()を使ってみる。

srcMgr.createMainFileID(fileMgr.getFile("D:\\develop\\Clang\\SampleProj\\Package1\\Super.cpp"));
srcMgr.createFileID(fileMgr.getFile("D:\\develop\\Clang\\SampleProj\\Package2\\Sub.cpp"),
                    clang::SourceLocation(), clang::SrcMgr::C_User);

実行すると、Sub.cppは解析結果に含まれない。

SourceLocationの生成を調べる

SourceLocationがデフォルト生成のままだとダメなのか、と思ってSourceLocationを調べると、
  • メンバ変数はunsigned ID1つだけ。
  • コンストラクタは引数なし。
  • IDを初期化できそうなメンバ関数はない。
となっている。つまりSourceLocation自身は初期化機能を提供せず、他のクラスが初期化するのだ。

これまで使ってきたクラスの中から生成しそうなやつをピックアップすると
  • FileManager
  • HeaderSearch
  • SourceManager
くらいなのだが、このうちFileManagerとHeaderSearchはSourceLocationを全く参照していない。なのでSourceManager.hをあさってみる。
  • clang::SrcMgrのインナークラスであるFileInfoにgetIncludeLoc()というAPIがある。
  • SourceManager自身にgetIncludeLoc()というAPIがある。
とりあえず後者を採用して実行・・・

clang::FileID mainId = srcMgr.createMainFileID(fileMgr.getFile("D:\\develop\\Clang\\SampleProj\\Package1\\Super.cpp"));
srcMgr.createFileID(fileMgr.getFile("D:\\develop\\Clang\\SampleProj\\Package2\\Sub.cpp"),
                    srcMgr.getIncludeLoc(mainId), clang::SrcMgr::C_User);

結果、やっぱりダメ。SourceLocationの問題ではないらしい。

複数cppをまとめてコンパイルできると誰が言った?

ここであることが頭をよぎる。gcc等では1回の起動で1つの.cppしか処理しない。clangのクラス群もそのように設計されているのではないか?
SourceManagerのMainという概念は、.cpp1ファイルのことを指すのではないか?

もしそうだとすると、非常に面倒なことである。ASTContextにおけるclang::Typeの反復の重複除去も意味がない。
そこで、1つのASTContextで複数の.cppを処理できるのか、試してみることにする。

まずSourceManagerのリセットである。調べたところ、MainFileIDはclearIDTable()メンバ関数でのみリセットされるようである。

ということで、以下のように実装してみる。

clang::IdentifierTable identifierTable(langOpts);
clang::SelectorTable selectorTable;
clang::Builtin::Context builtinContext;
clang::ASTContext astContext(langOpts, srcMgr, targetInfo, identifierTable,
                                selectorTable, builtinContext, 0);

clang::ASTConsumer astConsumer;
clang::Sema sema(pp, astContext, astConsumer);
sema.Initialize();

const char* targets[2] =
{
    "D:\\develop\\Clang\\SampleProj\\Package1\\Super.cpp",
    "D:\\develop\\Clang\\SampleProj\\Package2\\Sub.cpp"
};
for(const char** target = targets; target != (targets + 2); ++target)
{
    srcMgr.clearIDTables();
    srcMgr.createMainFileID(fileMgr.getFile(*target));
    clang::ParseAST(pp, &astConsumer, astContext);
}

// 構文解析結果を確認する。
clang::ASTContext::const_type_iterator t = astContext.types_begin();
clang::ASTContext::const_type_iterator eot = astContext.types_end();
for(; t != eot; ++t)
{
    if((*t)->getTypeClass() != clang::Type::Record)
    {
        continue;
    }

    clang::CXXRecordDecl* pDecl = (*t)->getAsCXXRecordDecl();
    if( (pDecl != nullptr) && pDecl->hasDefinition() && pDecl->isClass() )
    {
        std::printf("\n---------------------------------------------------------\n");

        (*t)->dump();
    }
}

実行した結果、次のようなエラーメッセージが。

Assertion failed: NumEnteredSourceFiles == 0 && "Cannot reenter the main file!", file D:\develop\Clang\llvm\tools\clang\lib\Lex\Preprocessor.cpp, line 388

うーむ、Preprocessorの時点で再利用できないのね。

0 件のコメント:

コメントを投稿