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の反復においては反復されない。

0 件のコメント:

コメントを投稿