2011年11月30日水曜日

Clangでマクロの展開箇所を調べる

C++ソースコード上にマクロで表現されたDSLをclangで調べるために、マクロ展開箇所を調べたくなった。

マクロはclang::Preprocessorの範疇であるが、Preprocessorはマクロの展開情報を直接扱わない。clangにはPreprocessingRecordおよびPreprocessedEntryというクラスがあり、これらを利用する。

まず、Preprocessorが処理においてPreprocessingEntryを保存してくれるよう、PreprocessingRecordをセットアップする。
clang::InitializePreprocessor(pp, ppOpts, headerSearchOpts, frontendOpts);
pp.createPreprocessingRecord(true);

そうすると、プリプロセッシングの後でPreprocessorに設定されたPreprocessingRecordが、PreprocessedEntryを反復してくれる。PreprocessedEntryの派生クラスとしてMacroExpansionがあるので、キャストして情報を得ればよい。
clang::PreprocessingRecord* ppRecord = pp.getPreprocessingRecord();
clang::PreprocessingRecord::iterator ppr = ppRecord->begin();
clang::PreprocessingRecord::iterator eoppr = ppRecord->end();
for(; ppr != eoppr; ++ppr)
{
    if((*ppr)->getKind() == clang::PreprocessedEntity::MacroExpansionKind)
    {
        clang::MacroExpansion* expansion
            = llvm::dyn_cast<clang::MacroExpansion>(*ppr);
        if(expansion->getName()->getName() == "MY_MACRO")
        {
            clang::SourceRange range = expansion->getSourceRange();
            range.getBegin().dump(srcMgr);
        }
    }
}

2011年11月20日日曜日

CLangでクラスの中身を調べる:所属パッケージを調べる

前回までで、複数のcppをまとめて処理することが(たぶん)できないので、列挙されたクラスをあとで重複排除する必要があることがわかった。

clangにはリンク等の時点で判別する方法があると思うが、その機能を調べるのが非常にめんどくさい。そこで、各クラスの所属パッケージを調べておいて、その名前を文字列比較することで判定しようと思う。

自分の名前(クラス名)はCXXRecordDecl::getName()で取得できる。なので所属するパッケージ(名前空間)を取得できればよいはずである。

名前空間はclang::Type::dump()にてダンプされているので、そこで利用されているルーチンclang::TypePrinter::AppendScope()を調べてみた。以下のような実装である:

void TypePrinter::AppendScope(DeclContext *DC, std::string &Buffer) {
  if (DC->isTranslationUnit()) return;
  AppendScope(DC->getParent(), Buffer);

  unsigned OldSize = Buffer.size();

  if (NamespaceDecl *NS = dyn_cast<NamespaceDecl>(DC)) {
    if (NS->getIdentifier())
      Buffer += NS->getNameAsString();
    else
      Buffer += "<anonymous>";
  } else if (ClassTemplateSpecializationDecl *Spec
               = dyn_cast<ClassTemplateSpecializationDecl>(DC)) {
    IncludeStrongLifetimeRAII Strong(Policy);
    const TemplateArgumentList &TemplateArgs = Spec->getTemplateArgs();
    std::string TemplateArgsStr
      = TemplateSpecializationType::PrintTemplateArgumentList(
                                            TemplateArgs.data(),
                                            TemplateArgs.size(),
                                            Policy);
    Buffer += Spec->getIdentifier()->getName();
    Buffer += TemplateArgsStr;
  } else if (TagDecl *Tag = dyn_cast<TagDecl>(DC)) {
    if (TypedefNameDecl *Typedef = Tag->getTypedefNameForAnonDecl())
      Buffer += Typedef->getIdentifier()->getName();
    else if (Tag->getIdentifier())
      Buffer += Tag->getIdentifier()->getName();
  }

  if (Buffer.size() != OldSize)
    Buffer += "::";
}

本来の目的を考えると、上のルーチンをそのまま使えばいいのだが、それはできない。このTypePrinterは匿名の名前空間(namespace {} と宣言されているやつ)内で定義されており、かつAST/TypePrinter.cppにクラス定義があるのである。
なのでこのルーチンを解析して移植する。
std::string getPackagePath(clang::DeclContext* context)
{
    if(context == nullptr)
    {
        return "";
    }

    if(context->isTranslationUnit())
    {
        return "";
    }

    std::string path = getPackagePath(context->getParent());
    std::string::size_type oldLen = path.length();

    clang::NamespaceDecl* nsDecl = llvm::dyn_cast<clang::NamespaceDecl>(context);
    if(nsDecl == nullptr)
    {
        clang::TagDecl* tag = llvm::dyn_cast<clang::TagDecl>(context);
        if(tag != nullptr)
        {
            clang::TypedefNameDecl* tdef = tag->getTypedefNameForAnonDecl();
            if(tdef == nullptr)
            {
                path += tag->getIdentifier()->getName();
            }
            else
            {
                path += tdef->getIdentifier()->getName();
            }
        }
    }
    else
    {
        if(nsDecl->getIdentifier() == nullptr)
        {
            path += "<anonymous>";
        }
        else
        {
            path += nsDecl->getName();
        }
    }

    if(path.length() != oldLen)
    {
        path += "::";
    }

    return path;
}

これで通常の名前空間やインナークラスは処理できるようになった。TypePrinter側の実装でテンプレート用?ルーチンがあるのが気になる。

追記:
クラステンプレートの定義はASTContextによるclang::Typeの反復においては反復されない。

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の時点で再利用できないのね。

CLangでクラスの中身を調べる:クラスの重複反復を抑制する

ちょっと時間が空いた。llvmとclangを3.0 RC4にして継続。

clang::ASTContext::types_begin()だと、クラスが重複して反復されてしまう。これをなんとかしたい。でもclang::Typeやclang::CXXRecordDeclにはoperator==()がないので、反復済みかどうかの判定では対処できない。

clang::Typeのメンバ関数に、getTypeClass()というものがあった。これを調べてみると重複されて反復されるclang::Typeは片方がRecordでもう片方がElaboratedであるようだ。
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::Elaborated)
    {
        continue;
    }

これでOKになった。clang::Type::Recordのみ通すようにすれば確実性は上がるだろう。
※問題は、これが何を意味するのかさっぱり理解していないところだ・・・。

ところで、今年12/3のboost勉強会(#7)ではclangやるセッションがいくつかあるのね・・・。カミさんと日程調整していたら参加が締め切られてorz。

2011年10月27日木曜日

メモ:頭にCXXがついているClangのクラス

clang/lib/AST/CXXABI.h(26):class CXXABI {
clang/include/clang/AST/CXXInheritance.h(69):class CXXBasePath : public SmallVector<CXXBasePathElement, 4> {
clang/include/clang/AST/CXXInheritance.h(117):class CXXBasePaths {
clang/include/clang/AST/CXXInheritance.h(361):class CXXFinalOverriderMap 
clang/include/clang/AST/CXXInheritance.h(365):class CXXIndirectPrimaryBaseSet
clang/include/clang/AST/DeclCXX.h(164):class CXXBaseSpecifier {
clang/include/clang/AST/DeclCXX.h(265):class CXXRecordDecl : public RecordDecl {
clang/include/clang/AST/DeclCXX.h(1315):class CXXMethodDecl : public FunctionDecl {
clang/include/clang/AST/DeclCXX.h(1447):class CXXCtorInitializer {
clang/include/clang/AST/DeclCXX.h(1693):class CXXConstructorDecl : public CXXMethodDecl {
clang/include/clang/AST/DeclCXX.h(1924):class CXXDestructorDecl : public CXXMethodDecl {
clang/include/clang/AST/DeclCXX.h(1993):class CXXConversionDecl : public CXXMethodDecl {
clang/include/clang/AST/DeclFriend.h(130):class CXXRecordDecl::friend_iterator {
clang/include/clang/AST/ExprCXX.h(48):class CXXOperatorCallExpr : public CallExpr {
clang/include/clang/AST/ExprCXX.h(91):class CXXMemberCallExpr : public CallExpr {
clang/include/clang/AST/ExprCXX.h(157):class CXXNamedCastExpr : public ExplicitCastExpr {
clang/include/clang/AST/ExprCXX.h(206):class CXXStaticCastExpr : public CXXNamedCastExpr {
clang/include/clang/AST/ExprCXX.h(237):class CXXDynamicCastExpr : public CXXNamedCastExpr {
clang/include/clang/AST/ExprCXX.h(271):class CXXReinterpretCastExpr : public CXXNamedCastExpr {
clang/include/clang/AST/ExprCXX.h(302):class CXXConstCastExpr : public CXXNamedCastExpr {
clang/include/clang/AST/ExprCXX.h(327):class CXXBoolLiteralExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(357):class CXXNullPtrLiteralExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(386):class CXXTypeidExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(467):class CXXUuidofExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(549):class CXXThisExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(586):class CXXThrowExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(639):class CXXDefaultArgExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(725):class CXXTemporary {
clang/include/clang/AST/ExprCXX.h(753):class CXXBindTemporaryExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(796):class CXXConstructExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(928):class CXXFunctionalCastExpr : public ExplicitCastExpr {
clang/include/clang/AST/ExprCXX.h(984):class CXXTemporaryObjectExpr : public CXXConstructExpr {
clang/include/clang/AST/ExprCXX.h(1013):class CXXScalarValueInitExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(1051):class CXXNewExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(1258):class CXXDeleteExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(1380):class CXXPseudoDestructorExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(2252):class CXXUnresolvedConstructExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(2356):class CXXDependentScopeMemberExpr : public Expr {
clang/include/clang/AST/ExprCXX.h(2762):class CXXNoexceptExpr : public Expr {
clang/include/clang/AST/StmtCXX.h(25):class CXXCatchStmt : public Stmt {
clang/include/clang/AST/StmtCXX.h(61):class CXXTryStmt : public Stmt {
clang/include/clang/AST/StmtCXX.h(128):class CXXForRangeStmt : public Stmt {
clang/lib/AST/DeclarationName.cpp(31):class CXXSpecialName
clang/lib/AST/DeclarationName.cpp(49):class CXXOperatorIdName : public DeclarationNameExtra {
clang/lib/AST/DeclarationName.cpp(62):class CXXLiteralOperatorIdName
clang/lib/AST/ItaniumMangle.cpp(137):class CXXNameMangler {
clang/lib/CodeGen/CGBlocks.cpp(1363):class CXXByrefHelpers : public CodeGenModule::ByrefHelpers {
clang/include/clang/Sema/CXXFieldCollector.h(25):class CXXFieldCollector {
clang/include/clang/Sema/DeclSpec.h(60):class CXXScopeSpec {
clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h(703):class CXXThisRegion : public TypedValueRegion {
clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h(847):class CXXTempObjectRegion : public TypedValueRegion {
clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h(876):class CXXBaseObjectRegion : public TypedValueRegion {

CLangでクラスの中身を調べる: クラスとそのメンバ関数の列挙

クラスのメンバ関数はclang::CXXRecordDecl::method_begin(), method_end()で反復できる。この反復でclang::CXXMethodDeclが得られる。
CXXMethodDeclはヘッダを見ると、各関数の属性、例えば可視性であったり、戻り値の型であったりを取得できるようだ。

とりあえず、自分で明示的に定義したメンバ関数をリストアップしてみよう。

コード:
clang::ASTContext::const_type_iterator t = astContext.types_begin();
clang::ASTContext::const_type_iterator eot = astContext.types_end();
for(; t != eot; ++t)
{
    clang::CXXRecordDecl* pDecl = (*t)->getAsCXXRecordDecl();
    if( (pDecl != nullptr) && pDecl->hasDefinition() && pDecl->isClass() )
    {
        pDecl->getDeclName().dump();
        //pDecl->getDeclName().printName(llvm::outs());

        // 基本クラスを確認する。
        std::printf(">>>> base class >>>>\n");
        clang::CXXRecordDecl::base_class_iterator base = pDecl->bases_begin();
        clang::CXXRecordDecl::base_class_iterator eobase = pDecl->bases_end();
        for(; base != eobase; ++base)
        {
            // CXXRecordDecl::base_class_iterator は CXXBaseSpecifier* 。
            base->getType().dump();
        }
        std::printf("<<<<\n");

        // public なメンバ関数を列挙する。
        std::printf(">>>> method >>>>\n");
        clang::CXXRecordDecl::method_iterator method = pDecl->method_begin();
        clang::CXXRecordDecl::method_iterator eomethod = pDecl->method_end();
        for(; method != eomethod; ++method)
        {
            if( !method->isImplicit() && (method->getAccess() == clang::AS_public) )
            {
                method->dump();
            }
        }
        std::printf("<<<<\n");

        std::printf("\n---------------------------------------------------------\n");
    }
}

調べるcppはこんな感じ。
#include <cstdio>

namespace SuperNs
{
    class Super
    {
    public:
        Super()
        {
        }

        void super_public_method()
        {
        }
        
        virtual void super_public_virtual_method()
        {
        }

        virtual void super_public_virtual_method_nooverride()
        {
        }

    private:
        void super_private_method()
        {
        }
    };
}

namespace SubNs
{
    class Sub
        : public SuperNs::Super
    {
    public:
        Sub()
            : Super()
        {
        }

        void sub_public_method()
        {
        }
        
        virtual void super_public_virtual_method()
        {
        }

    private:
        void sub_private_method()
        {
        }
    };
}

int main(int, char**)
{
    std::printf("Hello,world\n");
    std::getchar();
    return 0;
}
結果:
_Lockit
>>>> base class >>>>
<<<<
>>>> method >>>>
_Lockit()_Lockit(int)void ~_Lockit() noexceptstatic void _Lockit_ctor(int)static void _Lockit_dtor(int)<<<<

---------------------------------------------------------
_Mutex
>>>> base class >>>>
<<<<
>>>> method >>>>
_Mutex()void ~_Mutex() noexceptvoid _Lock()void _Unlock()<<<<

---------------------------------------------------------
_Init_locks
>>>> base class >>>>
<<<<
>>>> method >>>>
_Init_locks()void ~_Init_locks() noexcept<<<<

---------------------------------------------------------
Super
>>>> base class >>>>
<<<<
>>>> method >>>>
Super() {
}

void super_public_method() {
}

virtual void super_public_virtual_method() {
}

virtual void super_public_virtual_method_nooverride() {
}

<<<<

---------------------------------------------------------
Sub
>>>> base class >>>>
: SuperNs::Super identifier
<<<<
>>>> method >>>>
Sub() : SuperNs::Super() {
}

void sub_public_method() {
}

virtual void super_public_virtual_method() {
}

<<<<

---------------------------------------------------------
Super
>>>> base class >>>>
<<<<
>>>> method >>>>
Super() {
}

void super_public_method() {
}

virtual void super_public_virtual_method() {
}

virtual void super_public_virtual_method_nooverride() {
}

<<<<

---------------------------------------------------------

なんでか、Superが2回反復されている(コピペミスではない)。その他は一応意図通り動いているようだ。ただ「親クラスのメンバ関数は子クラスで反復されない」のはちょっと困った。

C++コード表示の調整テスト中・・・

C++コードが見にくいので調整中。 以前からPrettyPrintを導入しようとしていたが、うまくいかずにモヤモヤしていた。どうもBloggerのテンプレートとして動的ビューを選択しているとうまくいかないようなので、シンプルに戻してみた。
#include <iostream>

#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Host.h"

#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"

#include "clang/Basic/LangOptions.h"
#include "clang/Basic/FileSystemOptions.h"

#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Basic/FileManager.h"

#include "clang/Frontend/HeaderSearchOptions.h"
#include "clang/Frontend/Utils.h"

#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"

#include "clang/Lex/Preprocessor.h"
#include "clang/Frontend/PreprocessorOptions.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/Frontend/CompilerInstance.h"

#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/Builtins.h"

#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Sema/Sema.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Type.h"
#include "clang/AST/Decl.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Ownership.h"
#include "clang/AST/DeclGroup.h"
#include "clang/AST/CXXInheritance.h"

#include "clang/Parse/Parser.h"

#include "clang/Parse/ParseAST.h"

int main()
{
    // 言語オプションをセットアップする。
    clang::LangOptions langOpts;
    langOpts.MicrosoftExt            = true;
    langOpts.MicrosoftMode            = true;
    langOpts.CPlusPlus                = true;
    langOpts.CPlusPlus0x            = true;
    langOpts.Bool                    = true;
    langOpts.GNUMode                = false;
    langOpts.GNUKeywords            = false;
    langOpts.LaxVectorConversions    = false;
    langOpts.Exceptions                = true;
    langOpts.CXXExceptions            = true;
    langOpts.MSBitfields            = true;
    langOpts.NeXTRuntime            = false;
    langOpts.MSCVersion                = _MSC_VER;

    // Diagnostic をセットアップする。
    clang::DiagnosticOptions diagOpts;
    clang::TextDiagnosticPrinter diagPrinter(llvm::outs(), diagOpts);
    diagPrinter.BeginSourceFile(langOpts, nullptr);

    llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>
        diagIds(new clang::DiagnosticIDs());
    clang::DiagnosticsEngine diagEngine(diagIds, &diagPrinter, false);

    // 動作環境オプションをセットアップする。
    llvm::Triple triple;
    triple.setArch(llvm::Triple::x86);
    triple.setVendor(llvm::Triple::PC);
    triple.setOS(llvm::Triple::Win32);
    clang::TargetOptions targetOpts;
    targetOpts.Triple = triple.getTriple();
    clang::TargetInfo* targetInfo
        = clang::TargetInfo::CreateTargetInfo(diagEngine, targetOpts);

    // ソースファイル管理をセットアップする。
    clang::FileSystemOptions fileSysOpts;
    clang::FileManager fileMgr(fileSysOpts);
    clang::SourceManager srcMgr(diagEngine, fileMgr);

    // ヘッダ検索をセットアップする。
    clang::HeaderSearch headerSearch(fileMgr);
    clang::HeaderSearchOptions headerSearchOpts;
    headerSearchOpts.AddPath("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\include",
                                clang::frontend::Angled, false, false, false);
    headerSearchOpts.AddPath("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include",
                                clang::frontend::Angled, false, false, false);
    clang::ApplyHeaderSearchOptions(headerSearch, headerSearchOpts, langOpts,
                                    triple);

    // プリプロセッサをセットアップする。
    clang::CompilerInstance compiler;
    clang::Preprocessor pp(diagEngine, langOpts, targetInfo, srcMgr, headerSearch, compiler);
    clang::PreprocessorOptions ppOpts;
    clang::FrontendOptions frontendOpts;
    clang::InitializePreprocessor(pp, ppOpts, headerSearchOpts, frontendOpts);


    const clang::FileEntry *pFile = fileMgr.getFile("D:\\develop\\Clang\\temp.cpp");
    srcMgr.createMainFileID(pFile);

    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();

    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)
    {
        clang::CXXRecordDecl* pDecl = (*t)->getAsCXXRecordDecl();
        if( (pDecl != nullptr) && pDecl->hasDefinition() && pDecl->isClass() )
        {
            //pDecl->getDeclName().printName(llvm::outs());

            // 基本クラスを確認する。
            std::printf(">>>> base class >>>>\n");
            clang::CXXRecordDecl::base_class_iterator base = pDecl->bases_begin();
            clang::CXXRecordDecl::base_class_iterator eobase = pDecl->bases_end();
            for(; base != eobase; ++base)
            {
                // CXXRecordDecl::base_class_iterator は CXXBaseSpecifier* 。
                base->getType().dump();
            }
            std::printf("<<<<\n");

            // public なメンバ関数を列挙する。
            std::printf(">>>> method >>>>\n");
            clang::CXXRecordDecl::method_iterator method = pDecl->method_begin();
            clang::CXXRecordDecl::method_iterator eomethod = pDecl->method_end();
            for(; method != eomethod; ++method)
            {
                method->dump();
                std::printf("\n-#-#-#-#\n");
            }
            std::printf("<<<<\n");

            std::printf("\n---------------------------------------------------------\n");
        }
    }

    std::getchar();
    return 0;
}

2011年10月26日水曜日

まとめ:CLangをビルド(Release3.0 RC1)

先の文章だとわかりづらいので、新しくわかった内容も含めて2.9→3.0の変更点をまとめておく。


clang::LangOptionsは実装や利用可能オプションが結構変わっているので、clang/Basic/LangOptions.defファイルを見て内容を確認したほうがよい。
Windows/VisualC++関連として、MicrosoftオプションがMicrosoftExtとMicrosoftModeの2つに分割されている。


旧clang::Diagnosticのほとんどの機能はclang::DiagnosticsEngineに分離された。clang::Diagnosticクラスもまだ残っているが、構文解析においては単純に置換(Diagnostic→DiagnosticsEngine)するだけでいい様子。


clang::Preprocessorがclang::ModuleLoaderを要求するようになった。clang::ModuleLoaderは抽象クラスであり、例えばclang::CompilerInstanceが利用できる。


clang::Builtin::Contextがclang::TargetInfoを要求しなくなった。clang::Builtin::Context::InitializeTarget()というAPIがあるが、コールしなくても構文解析は動くようだ。


他、細かなインターフェースの変更や、リンクするlibの増加などがあるが、エラーに対して普通に対処すれば問題ない。

CLangをビルド(Release3.0 RC1)


リポジトリを覗いたら、LLVM共々3.0 RC1が10/18に出ていた。リリースノートを見ると色々改善されている様子なので、環境を入れ替えてみる。

llvmとclangは普通にビルドが通ったが、自分のサンプルはエラーが多発した。
それぞれの内容と対処は以下の通り。

■dyn_castがビルドエラー
→llvm名前空間に入った。

→llvm::dyn_castに変更。

■clang::LangOptions.Microsoftがない
→おそらくMicrosoftExtとMicrosoftModeの2つに分割されたのだと思われる。

→両方trueに設定。

■clang::Diagnosticのコンストラクタの引数が変わった
→DiagnosticsEngineなるクラスができたようだ。
DiagnosticsEngineのコンストラクタは
・llvm::IntrusiveRefCntPtr
・DiagnosticConsumer
・bool ShouldOwnClient
の3引数を受け取る。第1引数は今までと同じだが、第2引数のDiagnosticConsumerはまた知らないクラスだ。ただしこっちはコンストラクタに引数がない。またDiagnosticsEngineはConsumerを受け取らなくてもいいっぽい。

→DiagnosticsEngineはConsumerなしで生成。
→後述の理由(実行時アサーション)により、やっぱり受け取らないと駄目なようだ。
よくよく確認すると、TextDiagnosticPrinterがDiagnosticConsumerの派生クラスになっている!
これを指定するよう修正。

■clang::TargetInfo::CreateTargetInfo()の引数が変わった。
→第1引数がclang::Diagnosticからclang::DiagnosticsEngineに変更されている。

→上記で作ったEngineをそのまま指定。

■clang::SourceManagerのコンストラクタの引数が変わった。
→上記と同様clang::Diagnosticからclang::DiagnosticsEngineに変更されている。

→上記で作ったEngineをそのまま指定。

■clang::Preprocessorのコンストラクタの引数が変わった。
→上記と同様clang::Diagnosticからclang::DiagnosticsEngineに変更されている。
・・・clang::Diagnosticの一部がDiagnosticsEngineに分割されたのかな?
第3引数のTargetInfoが参照からポインタに。
さらに以下の引数が追加された:
・ModuleLoader
・IdentifierInfoLookup
clang::ModuleLoaderはインターフェースであり、それを実装しているのはclang::ASTUnitとclang::CompilerInstanceである。

→上記で作ったEngineをそのまま指定。
ModuleLoaderにはCompilerInstanceを指定。
IdentifierInfoLookupはオプションっぽいのでnullptr指定。

■clang::Builtin::Contextのコンストラクタの引数が変わった。
→引数なしに。ただしInitializeTarget()なるメンバ関数がある。

→InitializeTarget()をコールするよう変更。

■clang::ASTContextのコンストラクタの引数が変わった。
→第3引数のTargetInfoが参照からポインタに。


▲clang::ASTReader::ReadAST()がリンクエラー。
→clangSerialization.libをリンク。

▲clang::driver::Arg::getAsString()がリンクエラー。
→clangDriver.libをリンク。


●実行時、DiagnosticIDs::ProcessDiag()でアサーション。
→DiagnosticsEngineにDiagnosticConsumerがセットされてないと判定NG。

→TextDiagnosticPrinterをDiagnosticsEngineのコンストラクタでConsumerとして設定。


以上の対応で動作した。今のコードはこんな感じ:

#include <iostream>

#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Host.h"

#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"

#include "clang/Basic/LangOptions.h"
#include "clang/Basic/FileSystemOptions.h"

#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Basic/FileManager.h"

#include "clang/Frontend/HeaderSearchOptions.h"
#include "clang/Frontend/Utils.h"

#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"

#include "clang/Lex/Preprocessor.h"
#include "clang/Frontend/PreprocessorOptions.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/Frontend/CompilerInstance.h"

#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/Builtins.h"

#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Sema/Sema.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Type.h"
#include "clang/AST/Decl.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Ownership.h"
#include "clang/AST/DeclGroup.h"
#include "clang/AST/CXXInheritance.h"

#include "clang/Parse/Parser.h"

#include "clang/Parse/ParseAST.h"

class MyASTConsumer : public clang::ASTConsumer
{
public:
 MyASTConsumer() : clang::ASTConsumer() { }
 virtual ~MyASTConsumer() { }

 virtual void HandleTopLevelDecl( clang::DeclGroupRef d)
 {
  static int count = 0;
  clang::DeclGroupRef::iterator it;
  for( it = d.begin(); it != d.end(); it++)
  {
   count++;
   clang::VarDecl *vd = llvm::dyn_cast<clang::VarDecl>(*it);
   if(!vd)
   {
    continue;
   }
   std::cout << vd << std::endl;
   if( vd->isFileVarDecl() && vd->hasExternalStorage() )
   {
    std::cerr << "Read top-level variable decl: '";
    std::cerr << vd->getDeclName().getAsString() ;
    std::cerr << std::endl;
   }
  }
 }
};

int main()
{
 // 言語オプションをセットアップする。
 clang::LangOptions langOpts;
 langOpts.BCPLComment = true;
 langOpts.Bool   = true;
 langOpts.MicrosoftExt = true;
 langOpts.MicrosoftMode = true;
 langOpts.CPlusPlus  = true;
 langOpts.CPlusPlus0x = true;
 langOpts.Exceptions  = true;
 langOpts.CXXExceptions = true;
 langOpts.MSBitfields = true;
 langOpts.NeXTRuntime = false;
 langOpts.NoBuiltin  = true;
 langOpts.MSCVersion  = _MSC_VER;

 // Diagnostic をセットアップする。
 clang::DiagnosticOptions diagOpts;
 clang::TextDiagnosticPrinter diagPrinter(llvm::outs(), diagOpts);
 diagPrinter.BeginSourceFile(langOpts, NULL);

 llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>
  diagIds(new clang::DiagnosticIDs());
 clang::DiagnosticsEngine diagEngine(diagIds, &diagPrinter, false);
 clang::Diagnostic diag(&diagEngine);

 // 動作環境オプションをセットアップする。
 llvm::Triple triple;
 triple.setArch(llvm::Triple::x86);
 triple.setVendor(llvm::Triple::PC);
 triple.setOS(llvm::Triple::Win32);
 clang::TargetOptions targetOpts;
 targetOpts.Triple = triple.getTriple();
 clang::TargetInfo* targetInfo
  = clang::TargetInfo::CreateTargetInfo(diagEngine, targetOpts);

 // ソースファイル管理をセットアップする。
 clang::FileSystemOptions fileSysOpts;
 clang::FileManager fileMgr(fileSysOpts);
 clang::SourceManager srcMgr(diagEngine, fileMgr);

 // ヘッダ検索をセットアップする。
 clang::HeaderSearch headerSearch(fileMgr);
 clang::HeaderSearchOptions headerSearchOpts;
 headerSearchOpts.AddPath("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\include",
        clang::frontend::Angled, false, false, false);
 headerSearchOpts.AddPath("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include",
        clang::frontend::Angled, false, false, false);
 clang::ApplyHeaderSearchOptions(headerSearch, headerSearchOpts, langOpts,
         triple);

 // プリプロセッサをセットアップする。
 clang::CompilerInstance compiler;
 clang::Preprocessor pp(diagEngine, langOpts, targetInfo, srcMgr, headerSearch, compiler);
 clang::PreprocessorOptions ppOpts;
 clang::FrontendOptions frontendOpts;
 clang::InitializePreprocessor(pp, ppOpts, headerSearchOpts, frontendOpts);



 const clang::FileEntry *pFile = fileMgr.getFile("D:\\develop\\Clang\\temp.cpp");
 srcMgr.createMainFileID(pFile);
 //pp.EnterMainSourceFile();

 clang::IdentifierTable identifierTable(langOpts);
 clang::SelectorTable selectorTable;

 clang::Builtin::Context builtinContext;
 builtinContext.InitializeTarget(*targetInfo);
 clang::ASTContext astContext(
  langOpts,
  srcMgr,
  targetInfo,
  identifierTable,
  selectorTable,
  builtinContext,
  0 /* size_reserve*/);
   // clang::ASTConsumer astConsumer;
   MyASTConsumer astConsumer;

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

   //MySemanticAnalisys mySema( pp, astContext, astConsumer);

 //clang::Parser parser( pp, sema);
 //parser.ParseTranslationUnit();
 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)
 {
  clang::CXXRecordDecl* pDecl = (*t)->getAsCXXRecordDecl();
  if( (pDecl != nullptr) && pDecl->isClass() )
  {
   if(pDecl->hasDefinition())
   {
    clang::CXXRecordDecl::base_class_iterator base = pDecl->bases_begin();
    clang::CXXRecordDecl::base_class_iterator eobase = pDecl->bases_end();
    for(; base != eobase; ++base)
    {
     base->getType().dump();
    }
   }
  }
 }

 getchar();
 return 0;
}

2011年10月25日火曜日

CLangでクラスの中身を調べる:Rebuild(5)

clang::RecordTypeにはgetAsCXXRecordDecl()というAPIもあり、clang::CXXRecordDeclが取得できる。このCXXRecordDeclのヘッダを眺めると、なかなか魅力的なAPIがある。

base_class_iterator bases_begin() { return data().getBases(); }

名前から、当該型の基本クラスを反復できることは容易に想像できる。

ところがこのbases_begin()をコールするとアサーションされてしまう。どうもメンバ変数が設定されてない様子。
CXXRecordDecl::hasDecinition()にて、このメンバ変数が設定されているかどうかを判定できるようだ。

こんなコードで

clang::ASTContext::const_type_iterator t = astContext.types_begin();
clang::ASTContext::const_type_iterator eot = astContext.types_end();
for(; t != eot; ++t)
{
 clang::CXXRecordDecl* pDecl = (*t)->getAsCXXRecordDecl();
 if( (pDecl != nullptr) && pDecl->isClass() )
 {
  if(pDecl->hasDefinition())
  {
   clang::CXXRecordDecl::base_class_iterator base = pDecl->bases_begin();
   clang::CXXRecordDecl::base_class_iterator eobase = pDecl->bases_end();
   for(; base != eobase; ++base)
   {
    base->getType().dump();
   }
  }
 }
}

こんな結果

: class Super identifier

正しく判定できている様子。
このhasDefinition()が何を意味するのか・・・。

2011年10月24日月曜日

CLangでクラスの中身を調べる:Rebuild(4)

Type.hを眺めていたら、isClassType()なるAPIを発見。さっそくgetClassType()の代わりに利用。きちんと動作した。

このclang::Type::isClassType()の実装や、すぐ下のisStructureType()の実装は興味深い。

bool Type::isClassType() const {
  if (const RecordType *RT = getAs<recordtype>())
    return RT->getDecl()->isClass();
  return false;
}
bool Type::isStructureType() const {
  if (const RecordType *RT = getAs<recordtype>())
    return RT->getDecl()->isStruct();
  return false;
}

まずTypeの派生クラスとしてRecordTypeがあり、反復中の型がクラスや構造体であるのなら、その実体はRecordTypeとして生成される。
かつRecordTypeにはgetDecl()なるAPIがあり、RecordDeclオブジェクトを返却してくれる。

ということで、Intellisenceにまかせてテキトーにコードを書いて実行してみた。

for(; t != eot; ++t)
{
 const clang::RecordType* pType = (*t)->getAs<clang::RecordType>();
 if(pType != nullptr)
 {
  const clang::RecordDecl* pDecl = pType->getDecl();
  if( (pDecl != nullptr) && pDecl->isClass() )
  {
   pDecl->getDeclName().dump();
  }
 }
}

結果。

type_info
_Lockit
_Mutex
_Init_locks

クラス名が取得できるようになった。

CLangでクラスの中身を調べる:Rebuild(3)

環境をVS2010に移行したら、リンクエラーが大量に・・・プロジェクトの依存関係を構築しただけではlibをリンクしてくれないらしい。以下のようにリンク設定を記述した。

..\build\lib\debug\LLVMSupport.lib
..\build\lib\debug\LLVMCodeGen.lib
..\build\lib\debug\LLVMMC.lib
..\build\lib\debug\clangBasic.lib
..\build\lib\debug\clangFrontend.lib
..\build\lib\debug\clangAnalysis.lib
..\build\lib\debug\clangLex.lib
..\build\lib\debug\clangSema.lib
..\build\lib\debug\clangAST.lib
..\build\lib\debug\clangParse.lib


前回のコードで、clang::Type::Recordに該当するもののみをダンプするよう修正してみた。こんなコードで

clang::ASTContext::const_type_iterator t = astContext.types_begin();
clang::ASTContext::const_type_iterator eot = astContext.types_end();
for(; t != eot; ++t)
{
 if(clang::Type::Record == (*t)->getTypeClass())
 {
  (*t)->dump();
 }
}

こんなソースコードを食わせると

#include <cstdio>

int main(int, char**)
{
 std::printf("Hello,world\n");
 std::getchar();
 return 0;
}

こんな結果。

: class type_info identifier
: struct threadlocaleinfostruct identifier
: struct threadmbcinfostruct identifier
: struct __lc_time_data identifier
: struct localeinfo_struct identifier
: struct tagLC_ID identifier
: struct threadlocaleinfostruct::<anonymous (x86)\microsoft="" 8\vc\include="" at="" c:\program="" crtdefs.h:2063:9="" files="" studio="" visual=""> identifier
: struct lconv identifier
: class std::_Lockit identifier
: class std::_Mutex identifier
: class std::_Init_locks identifier
: struct _iobuf identifier

2011年10月23日日曜日

CLangでクラスの中身を調べる:Rebuild(2)

ちょっと進展があったのでメモ。

clangにはTypeというクラスとその派生クラスがたくさんある。
構文解析結果としてそれを得られれば、その内容も調べられるかもしれない。

構文解析はclang::ParseAST()という関数にて実施する。このとき引数にclang::ASTContextのインスタンスを指定するのだが、このASTContextにTypeを反復するtypes_begin(), types_end()メンバ関数がある。

さて、Typeにはdump()という関数があるので、反復した要素に対してdump()してみると・・・

clang::ASTContext::const_type_iterator t = astContext.types_begin();
clang::ASTContext::const_type_iterator eot = astContext.types_end();
for(; t != eot; ++t)
{
 (*t)->dump();
}

int等の組み込み型やincludeしたC/C++の標準構造体などがすべて出力される。まあ当たり前か。

他にTypeで何かいいものがないかなぁと調べると、getTypeClass()というのがある。これをダンプ結果と組み合わせると、自作クラスでは値として22が返却される。getTypeClassName()を使うと、これはRecordという種別であるようだ。

こんなプログラムで

clang::ASTContext::const_type_iterator t = astContext.types_begin();
clang::ASTContext::const_type_iterator eot = astContext.types_end();
for(; t != eot; ++t)
{
 (*t)->dump();
 std::printf("%u %s\n", (*t)->getTypeClass(), (*t)->getTypeClassName());
 std::printf("-----------------------------------------------------------------------------------\n");
}

こんなのをパースして

#include 

class Temp
{
};
class Hoge;
int main(int, char**)
{
 std::printf("Hello,world\n");
 std::getchar();
 return 0;
}

こんな結果が出る(末尾付近を一部抜粋)

: class Temp identifier
ほげ    22      Record
-----------------------------------------------------------------------------------
: class Hoge identifier
ほげ    22      Record
-----------------------------------------------------------------------------------
: char **identifier
ほげ    2       Pointer
-----------------------------------------------------------------------------------
: int (identifier)(int, char **)
ほげ    14      FunctionProto
-----------------------------------------------------------------------------------

2011年10月2日日曜日

CLangでクラスの中身を調べる:Rebuild(1)

こんなの見つけた。

今までの苦労は・・・と一瞬思ったが、バージョン違いのせいかそのままではビルドが通らなかったり、変なビルドエラーが出たりする。
こいつのTutorial6を元にしてビルドが通るよう修正しながら動作を見ていくことにする。

まず気づいた点:
  • LangOptionsをきちんと設定しなければならない。たとえばMicrosoft拡張仕様をtrueにしないと__int64が正しく認識されなかったりする。
  • 色々とグローバル関数があって、それを利用して各インスタンスを初期化していく。ヘッダ検索はApplyHeaderSearchOptions()、プリプロセッサはInitializePreprocessor()など。
  • TextDiagnosticPrinter::BeginSourceFile()は、やっぱり自分でコールしなければならない。

CLangでクラスの中身を調べる(5)

さて、ようやくプリプロセッサを実行するところまで行った。と、プリプロセッサがエラーを出している。
In file included from D:\develop\Clang\temp.cpp:1:
In file included from C:\Program Files (x86)\Microsoft Visual Studio 8\VC\INCLUDE/cstdio:5:
In file included from C:\Program Files (x86)\Microsoft Visual Studio 8\VC\INCLUDE/yvals.h:6:
C:\Program Files (x86)\Microsoft Visual Studio 8\VC\INCLUDE/crtdefs.h:40:2: error: #error ERROR: Only Win32 target supported!
#error ERROR: Only Win32 target supported!

どうも_WIN32をdefineせずにcstdioをインクルードしてはいけないらしい。ということで、predefineを追加してみる。
色々調べていくと、clang::PreprocessorにsetPredefines()というAPIがあり、またその設定値をclang::MacroBuilderで生成できることがわかった。
	std::string predefinesBuf;
	llvm::raw_string_ostream predefines(predefinesBuf);
	clang::MacroBuilder macroBuilder(predefines);
	macroBuilder.defineMacro("_WIN32");
	pp.setPredefines(predefines.str());

これでビルドして実行するとプリプロセッサがエラーを出さなくなった。ブレークポイントでPreprocessor::Lex()の結果を確認すると、何かしらデータは入っているようだ。ようやく先に進めそうだ。

2011年10月1日土曜日

CLangでクラスの中身を調べる(4)

前回の続き。アサーションではなくエラーメッセージにする方法の検討。

どうもclang::DiagnosticのメンバにClientというのがあり、それがnullptrであるときにアサーションされるようだ。

ってことで、改めてマジメにclang::Diagnosticsについて調べてみる。.hのドキュメントを見ると、clang::DiagnosticClientを出力先に指定しなければならないようだ。もし自分が診断結果の出力先のクラスを作るなら、きっと出力先を切り替えられるよう抽象化するだろうと思ってpublic DiagnosticClientで検索すると、以下のようなものがヒットした:
  • ChainedDiagnosticClient
  • TextDiagnosticBuffer
  • TextDiagnosticPrinter
  • VerifyDiagnosticsClient
  • StoredDiagnosticClient
  • FixItRewriter
  • IgnoringDiagClient
  • PathDiagnosticClient
最もそれっぽいclang::TextDiagnosticPrinterを使ってみる。

clanng::TextDiagnosticPrinterのコンストラクタはllvm::raw_ostreamとclang::DiagnosticOptionsを要求する。
前者は出力先ストリームであるからさらに抽象化されているのではないかと思って検索すると、やはり派生クラスがいくつか出てくる。ひとまずstd::ostreamに出力すると書いてあるllvm::raw_os_ostreamを使ってみる。
後者は色々設定できるようだが、まずはデフォルト設定で使ってみる。
インクルードに
#include <iostream>
#include "llvm/Support/raw_os_ostream.h"
#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
を追加して、以下のコードを記述。
	llvm::raw_os_ostream diagOStream(std::cout);
	clang::DiagnosticOptions diagOpts;
	clang::TextDiagnosticPrinter diagPrinter(diagOStream, diagOpts);

	llvm::IntrusiveRefCntPtr<clang::diagnosticids>
		diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds, &diagPrinter);

ビルドするとclang::TextDiagnosticPrinterがリンクエラーになるので、clangFrontendプロジェクトを依存関係に追加。

で、実行すると・・・今度はTextDiagnosticPrinterでアサーションされるorz
メンバにLangOptionsがいて、そいつがnullptrであるとのこと。そのメンバ変数を設定するAPIはTextDiagnosticPrinter::BeginSourceFile()のみであるようだ。親クラスであるclang::DiagnosticClientの.hに記載のドキュメントによると、ソースファイルの処理が開始されたこと通知するためのコールバックであると書かれている。

そこでBeginSourceFile()の呼び出しルートを調べる。

■clang::DiagnosticClient::BeginSourceFile()の呼び出し元
  • clang::ASTMergeAction::ExecuteAction()
    ※ASTMergeActionはclang::FrontendActionの派生クラス。
  • clang::FrontendAction::BeginSourceFile()
■clang::FrontendAction::ExecuteAction()の呼び出し元
  • clang::FrontendAction::Execute()
■clang::FrontendAction::BeginSourceFile()の呼び出し元
  • clang::ASTUnit::Parse()
  • clang::ASTUnit::getMainBufferWithPrecompiledPreamble()
  • clang::ASTUnit::CodeComplete()
  • clang::CompilerInstance::ExecuteAction()
■clang::FrontendAction::Execute()の呼び出し元
  • clang::ASTUnit::Parse()
  • clang::ASTUnit::getMainBufferWithPrecompiledPreamble()
  • clang::ASTUnit::CodeComplete()
  • clang::CompilerInstance::ExecuteAction()
■clang::CompilerInstance::ExecuteAction()の呼び出し元
  • clang::ExecuteCompilerInvocation()

うーん、CompilerInstanceやASTUnitはプリプロセッサより後段であると思う。では自前でコールするのがいいのか?
正しいやり方かどうか怪しくなってきたが、試してみると・・・アサーションされなくなり、プリプロセッサのエラーが確認できるようになった。
#include <iostream>
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/Host.h"
#include "clang/Frontend/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"

int main(int, char*[])
{
	// Preprocessor をセットアップする。

	llvm::raw_os_ostream diagOStream(std::cout);
	clang::DiagnosticOptions diagOpts;
	clang::TextDiagnosticPrinter diagPrinter(diagOStream, diagOpts);

	llvm::IntrusiveRefCntPtr<clang::diagnosticids>
		diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds, &diagPrinter, false);

	clang::LangOptions langOpts;
	diagPrinter.BeginSourceFile(langOpts, NULL);

	clang::TargetOptions targetOpts;
	targetOpts.Triple = llvm::sys::getHostTriple();

	clang::TargetInfo* targetInfo
		= clang::TargetInfo::CreateTargetInfo(diag, targetOpts);

	clang::FileSystemOptions fileSysOpts;
	clang::FileManager fileMgr(fileSysOpts);
	clang::SourceManager srcMgr(diag, fileMgr);

	clang::HeaderSearch headerSearch(fileMgr);

	clang::Preprocessor pp(diag, langOpts, *targetInfo, srcMgr, headerSearch);


	// 標準インクルードパスを設定する。

	llvm::StringRef stdHeaderPath1("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\INCLUDE");
	llvm::StringRef stdHeaderPath2("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include");
	std::vector<clang::directorylookup> stdHeaderPaths;
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath1);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, false);
		stdHeaderPaths.push_back(d);
	}
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath2);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, false);
		stdHeaderPaths.push_back(d);
	}
	headerSearch.SetSearchPaths(stdHeaderPaths, 0, false);


	// コンパイル対象のファイルを指定する。

	llvm::StringRef cppPath("D:\\develop\\Clang\\temp.cpp");
	const clang::FileEntry* cppEntry = fileMgr.getFile(cppPath);
	clang::FileID fileId = srcMgr.createMainFileID(cppEntry);
	pp.EnterMainSourceFile();

	clang::Token token;
	pp.Lex(token);

	diagPrinter.EndSourceFile();

	getchar();
	return 0;
}

しばらくこれで進めてみよう・・・。

2011年9月29日木曜日

CLangでクラスの中身を調べる(3)

前回の続き。標準インクルードパスを設定する方法を探さねばならない。


クラス名的に最もクサイのはclang::HeaderSearchである。ということでHeaderSearchの.hから見てみると、SetSearchPaths()というAPIがある。
このAPIはclang::DirectoryLookupというクラスの配列を受け取る。これは単純にHeaderSearchのメンバ変数を書き換えるようだ。そちらのメンバ変数の説明によると

  • 最初にカレントフォルダを検索する。
  • 次に(当該メンバ変数)を順に検索する。
  • オプションで(当該メンバ変数)の検索開始インデックスを指定したり、カレントフォルダを検索しないようにできる。

という感じらしい。

DirectoryLookupはえーと・・・検索エントリで、単なるフォルダではなくフレームワーク?やヘッダマップ?も指定できるオブジェクトと説明があるがさっぱりわからない。DirectoryLookupは2種類のコンストラクタがあるが、そのうち片方ではclang::DirectoryEntryとclang::SrcMgr::CharacteristicKindを受け取る。DirectoryEntryは単にフォルダ名を格納するだけであるが、フォルダ名のセットAPIはない。代わりにFileManagerがfriend指定されているので、FileManagerから受け取るのだろう。CharacteristicKindは列挙でC_User, C_System, C_ExternCSystemが選べる。

FileManagerからDirectoryEntryを受け取る方法だが、単発のファイルパスの解決のときと同様、getDirectory()APIにファイルパス文字列を渡した結果として受け取れるようだ。

ってことで、以下のように実装してみる。
	llvm::StringRef stdHeaderPath1("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\INCLUDE");
	llvm::StringRef stdHeaderPath2("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include");
	std::vector<clang::directorylookup> stdHeaderPaths;
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath1);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, true);
		stdHeaderPaths.push_back(d);
	}
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath2);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, true);
		stdHeaderPaths.push_back(d);
	}
	headerSearch.SetSearchPaths(stdHeaderPaths, 0, false);

実行すると・・・まだダメ。headerSearchに追加したDirectoryLookupのLookupFile()は呼び出されているが、「フレームワーク」として登録されている場合、'/'文字が検索するファイルのパスに入ってないとダメらしい。フレームワークってboostとかclangとか、そういうのを指しているのか?

ためしにフーレムワーク指定をfalseにすると・・・今度は別の箇所でアサーションした。なるほど、標準ライブラリはフレームワークではないらしい。

ところで、ヘッダファイルが見つからないごときでアサーションが発生するのは勘弁してほしい気がする。次は先に進む前に、アサーションではなくエラーメッセージを表示させるようなことを考える。

CLangでクラスの中身を調べる(2)

CLangのプリプロセッサ実行の続き・・・の前に。CLangが提供する各クラスのドキュメントについて。
ググって出てくるドキュメントには要注意。バージョンアップでI/Fが変更されている箇所が非常に多いのだ。だから正確な情報を得るには、CLangのソースコードの.hを見たり、.hや.cppに記述されているdoxygenスタイルコメントを読む必要がある。


では続き。
ドキュメントによるとプリプロセッサの実行はclang::Preprocessor::Lex関数によるようだ。そしてLex関数の引数はclang::Tokenで戻り値はvoidなので、引数がoutなんだろうと推測される。

しかし、今まで対象のファイルやフォルダを指定していない。何も指定せずにLex関数をコールするとどうなるだろうか?
	clang::Token token;
	pp.Lex(token);
実行すると見事になにもエラーが発生しない。CLangは例外をthrowしないのか?

ということで対象ファイルを指定する方法を探す。前回ちょっと触ったクラスのうち、たぶんFileManagerあたりでファイルやフォルダを指定し、SourceManagerで読み込む、という感じではないかと推測して各クラスを調べる。
そしてFileManagerのヘッダを見ると、FileEntryとかDirectoryEntiryというのを返却するAPIがある。そしてSourceManagerのドキュメントを見ると、FileEntryを受け取るAPIとしてcreateFileID()やcreateMainFileID()がある。
createFileID()などはclang::FileIDを返す。そこでPreprocessorのヘッダを見ると、FileIDを引数に取るEnterSourceFile()や、引数はないけどFileIDとmain source fileに言及しているEnterMainSourceFile()がある。だいぶ見えてきた。

ということで、main source fileとmainじゃないsource fileの違いがよくわからないが、とりあえずmain source fileを処理しそうなコードを書いて実行してみる。
	llvm::StringRef cppPath("D:\\develop\\Clang\\temp.cpp");
	const clang::FileEntry* cppEntry = fileMgr.getFile(cppPath);
	clang::FileID fileId = srcMgr.createMainFileID(cppEntry);
	pp.EnterMainSourceFile();

	clang::Token token;
	pp.Lex(token);
ちなみにtemp.cppの中身はこんな感じ:
#include <cstdio>
int main(int, char**) { std::printf("Hello,world\n"); std::getchar(); return 0; }

そうすると、clang::Preprocessor::Lex()の実行でアサーションされてしまった。コールスタックを見るとclang::Preprocessor::HandleIncludeDirective()という関数にて「Search include directories.」とコメントされている箇所で、処理結果の診断がNGになっているようだ。なるほど。

CLangでクラスの中身を調べる(1)

C++において、ある特定の型で宣言されたメンバ変数を持つクラスとその変数名を列挙したい。
CLangを使ってそれを実現する(か挫折する)まで頑張る。

現在の環境:
・Visual Studio 2005 with SP1
・llvm 2.9
・CLang 2.9

まずCLangのドキュメントを読んでみたがよくわからない。さかんにASTという単語が出てくるが、これはたぶんAbstract Syntax Treeの略(=構文解析結果)だと思うので、そこまでたどり着ければ何か見えてくるだろう。

まずはプリプロセッサ。ドキュメントには「The Lexer and Preprocessor Library」という節があるので、ここを見ればいいはず。ちなみにLexer = Lexical Analyzerであるらしい。これによるとプリプロセッサのクラスはclang::Preprocessorというのがある。ドキュメントを読み進める前にこいつのオブジェクトを作ってみよう。
clang::Preprocessorはコンストラクタで多数のインスタンスが要求される。
  • clang::Diagnostic
  • clang::LangOptions
  • clang::TargetInfo
  • clang::SourceManager
  • clang::HeaderSearch
以下、1つずつ潰す。

■clang::Diagnostic
コンパイルエラーなどを通知する?
こいつはllvm::IntrusiveRefCntPtrを要求する。clang::DiagnosticIDsはデフォルト生成可能。


■clang::LangOptions
言語オプション?RTTIを有効にしたりする様子。
コンストラクタにて全オプションがデフォルト指定されているので、まずはデフォルトのまま利用する。

■clang::TargetInfo
ターゲットとは、たぶんバイナリの動作環境の事だろう。
こいつはコンストラクタは利用できず、staticなファクトリ関数を使う。clang::Diagnosticとclang::TargetOptionを要求する。
clang::TargetOptionは単なる構造体である。そのメンバにTripleというのがあって最初躓いたが、どうも以下の4つをまとめたものであるらしい:
  • CPUアーキテクチャ(alpha, ppc, x86, x86_64等)
  • ベンダ(UnknownとAppleとPCしかねぇー)
  • OS(Cygwin, Linux, Win32等)
  • 環境(オプション? GNU, MachO等)
そしてllvm::sysにgetHostTriple()というAPIが用意されている。今回は別にバイナリを作るわけではないで、ホスト環境をそのまま使うことにする。それ以外のオプションは設定しなくても大丈夫っぽい。Featuresがちょっと気になるが・・・。

■clang::SourceManager
ソースコードをファイルから読み込んでメモリ上に保持管理する役割を持つようだ。
こいつはclang::Diagnosticとclang::FileManagerを要求する。
clang::FileManagerはファイルやフォルダを検索する機能、またファイルをキャッシングする機能を提供する。clang::FileSystemOptionsを要求する。
clang::FileSystemOptionsは単なる構造体で、作業フォルダの指定しか属性がない。しかもデフォルトでは指定する必要がないようだ。

■clang::HeaderSearch
その名の通りヘッダ検索をサポートするようだ。
こいつもclang::FileManagerを要求する。

■clang::Preprocessor
こんな感じになった。
#include "llvm/Support/Host.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"

int main(int, char*[])
{
	llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds);

	clang::LangOptions langOpts;

	clang::TargetOptions targetOpts;
	targetOpts.Triple = llvm::sys::getHostTriple();

	clang::TargetInfo* targetInfo
		= clang::TargetInfo::CreateTargetInfo(diag, targetOpts);

	clang::FileSystemOptions fileSysOpts;
	clang::FileManager fileMgr(fileSysOpts);
	clang::SourceManager srcMgr(diag, fileMgr);

	clang::HeaderSearch headerSearch(fileMgr);

	clang::Preprocessor pp(diag, langOpts, *targetInfo, srcMgr, headerSearch);

	getchar();
	return 0;
}

ビルドすると当然リンクエラーが出る。今はclang/Basicとclang/Lexに依存しているので、それぞれclangBasicプロジェクトとclangLexプロジェクトに依存するよう、プロジェクト依存関係を追加すればOK。
※LLVM.slnに自分のテストプロジェクトを追加している。
実行してエラーも何もでないことを確認した。

2011年9月27日火曜日

CLangをビルド

CLangをビルドしたので、ちょっとメモ。

基本は
に従って進める。

trunkだとビルドが通らなかったので、LLVMとCLang共にRELEASE 2.9を取得。
チェックアウト時にCLangのチェックアウトフォルダ名をうっかりcfeにしたらビルド対象に含まれなかった。clangに修正してやり直したらOK。

Cygwinでビルドしたときは、改行にCRが入っているとCygwinのシェルに怒られた。Unix系使ったことないから何故怒られるのかわからないので全部LFのみに置換。
llvm/config
llvm/autoconf/config.guess
llvm/autoconf/config.sub
llvm/autoconf/mkinstalldirs
llvm/autoconf/install-sh
llvm/projects/sample/configure

VS2010でビルドしたバイナリで.cppをコンパイルしたら
「clang: error: unable to execute command: program not executable」
「clang: error: linker command failed due to signal 1 (use -v to see invocation)」
と出力され、コンパイルできなかった。何故だろう・・・。