3 февраля 2016 г.

CLang API: обход AST (clang-3.7)

Попробуем теперь реализовать обход AST встроенными методами (по мотивам статьи "How to write RecursiveASTVisitor based ASTFrontendActions"). На мой взгляд, реализация этой задачи стала намного проще по сравнению с версией 2.8.

Предыдущая статья


Минимальный код

Как следует из текста статьи "How to write RecursiveASTVisitor based ASTFrontendActions", минимальный код для обхода AST должен содержать следующее:

  • реализацию своего "посетителя" узлов AST на базе класса RecursiveASTVisitor
  • реализацию интерфейса ASTConsumer
  • реализацию интерфейса ASTFrontendAction
  • вызов процедуры анализа кода и создания AST - runToolOnCode

Для начала создадим простого Посетителя, посещающего всего один тип узла - TranslationUnit

class MyVisitor : public clang::RecursiveASTVisitor<MyVisitor>
{
public:
  bool VisitTranslationUnitDecl (clang::TranslationUnitDecl *D)
  {
    D->dump();
    return true;
  }
};

Здесь мы просто делаем дамп узла TranslationUnit в поток вывода. Посмотреть на дамп мне очень поможет для дальнейшей работы, если я плохо знаком с типами clang и вообще с видом AST. Код для остальных классов тоже несложный:

class MyConsumer : public clang::ASTConsumer
{
public:
  virtual void HandleTranslationUnit (clang::ASTContext &Context) override
  {
    _visitor.TraverseDecl(Context.getTranslationUnitDecl());
  }
private:
   MyVisitor _visitor;
};

class MyAction : public clang::ASTFrontendAction
{
protected:
  typedef std::unique_ptr<clang::ASTConsumer> ASTConsumerPtr;
  virtual ASTConsumerPtr CreateASTConsumer (clang::CompilerInstance &Compiler, llvm::StringRef InFile) override
  {
    return ASTConsumerPtr(new MyConsumer);
  }
};

Вот такой код мы подсунем clang'у для анализа:

struct MyStruct {};
class MyClass {};
namespace Nspace {
  class MyDerived : public MyStruct {};
  class MyDerived2 : public MyDerived, public virtual MyClass {};
}

Теперь нужно собрать запустить анализ и обход дерева средствами clang:

int main ()
{
  const bool ret = clang::tooling::runToolOnCode(new MyAction,
    "struct MyStruct {};"
    "class MyClass {};"
    "namespace Nspace {"
    "class MyDerived : public MyStruct {};"
    "class MyDerived2 : public MyDerived, public virtual MyClass {};"
    "}");
  return ret ? 0 : -1;
}

Что там внутри TranslationUnit?

Компилируем, запускаем и видим, что дамп TranslationUnit выглядит примерно так:

TranslationUnitDecl 0x255f740 <> 
|-TypedefDecl 0x255fc78 <>  implicit __int128_t '__int128'
|-TypedefDecl 0x255fcd8 <>  implicit __uint128_t 'unsigned __int128'
|-TypedefDecl 0x25600b8 <>  implicit __builtin_va_list '__va_list_tag [1]'
|-CXXRecordDecl 0x2560108  col:8 referenced struct MyStruct definition
| `-CXXRecordDecl 0x2560220  col:8 implicit struct MyStruct
|-CXXRecordDecl 0x25602b0  col:26 referenced class MyClass definition
| `-CXXRecordDecl 0x25603c0  col:26 implicit class MyClass
`-NamespaceDecl 0x25ab8b0  col:47 Nspace
  |-CXXRecordDecl 0x25ab918  col:61 referenced class MyDerived definition
  | |-public 'struct MyStruct'
  | `-CXXRecordDecl 0x25aba70  col:61 implicit class MyDerived
  `-CXXRecordDecl 0x25abb00  col:98 class MyDerived2 definition
    |-public 'class Nspace::MyDerived'
    |-virtual public 'class MyClass'
    |-CXXRecordDecl 0x25abca8  col:98 implicit class MyDerived2
    |-CXXMethodDecl 0x25abd98 > col:98 implicit operator= 'class Nspace::MyDerived2 &(const class Nspace::MyDerived2 &)' inline noexcept-unevaluated 0x25abd98
    | `-ParmVarDecl 0x25abec0  col:98 'const class Nspace::MyDerived2 &'
    `-CXXDestructorDecl 0x25abf48  col:98 implicit ~MyDerived2 'void (void)' inline noexcept-unevaluated 0x25abf48

Что с этим делать?

Узел TranslationUnit содержит весь код, который я скормил функции runToolOnCode для анализа. В дампе прекрасно видно декларации пространства имен, классов и их методов. Теперь, если необходимо посетить тот или иной тип узла AST, нужно реализовать в классе MyVisitor соответствующий метод bool VisitZZZ(ZZZ *decl), где ZZZ - имя типа узла. Например, для посещения узлов NamespaceDecl необходимо создать метод bool VisitNamespaceDecl (NamespaceDecl *decl) и т.д. В принципе, метод VisitTranslationUnitDecl мне уже не нужен, он уже выполнил все свои функции на сегодня.

Для примера давайте реализуем обход деклараций классов и получим имена методов этих классов. Для этого реализуем метод MyVisitor::VisitCXXRecordDecl и в нем используем следующие методы класса CXXRecordDecl для получения нужной нам информации:

  • CXXRecordDecl::getKindName - возвращает имя типа декларации (в нашем случае class/struct)
  • CXXRecordDecl::getIdentifier - возвращает идентификатор декларации, читабельное значение которого можно получить с помощью getName()
  • CXXRecordDecl::getQualifiedNameAsString - возвращает полное имя класса, влючающее имена пространств имен, если таковые есть.
  • CXXRecordDecl::methods - возвращает методы класса в виде списка CXXMethodDecl
bool VisitCXXRecordDecl (CXXRecordDecl *D)
  {
    llvm::outs() << "CXXRecord: " << D->getKindName();
    if (D->getIdentifier())
    {
      llvm::outs() << ", id: " << D->getIdentifier()->getName();
    }
    llvm::outs() << " | " << D->getQualifiedNameAsString() << "\n";

    llvm::outs() << "- Methods\n";
    for (auto i: D->methods())
    {
      llvm::outs() << "- - " << (*i).getQualifiedNameAsString() << "\n";
    }
    llvm::outs() << "------------------------------------------\n";

    return true;
  }

После запуска видим вот такую картину:

CXXRecord: struct, id: MyStruct | MyStruct
- Methods
------------------------------------------
CXXRecord: class, id: MyClass | MyClass
- Methods
------------------------------------------
CXXRecord: class, id: MyDerived | Nspace::MyDerived
- Methods
------------------------------------------
CXXRecord: class, id: MyDerived2 | Nspace::MyDerived2
- Methods
- - Nspace::MyDerived2::operator=
- - Nspace::MyDerived2::~MyDerived2
------------------------------------------

У трех классов методы отсутствуют, а у последнего автоматически сгенерировались аж два метода, - деструктор и оператор присваивания, - что напрямую связано с присутствием виртуального наследования.

Теперь логично было бы визуализировать списки базовых классов. Это можно сделать несколькими способами. Первый — использовать метод CXXRecordDecl::bases(), который возвращает список базовых классов, от которых непосредственно унаследован текущий класс. Второй — использовать метод CXXRecordDecl::forallBases(), которых возвращает список ВСЕХ базовых классов из дерева наследования текущего класса. Попробуем использовать оба метода.

bool VisitCXXRecordDecl (CXXRecordDecl *D)
  {
    llvm::outs() << "CXXRecord: " << D->getKindName();
    if (D->getIdentifier())
    {
      llvm::outs() << ", id: " << D->getIdentifier()->getName();
    }
    llvm::outs() << " | " << D->getQualifiedNameAsString();
    llvm::outs() << "\n";

    // List base classes
    {
      if (D->getNumBases())
      {
        llvm::outs() << "- Bases (" << D->getNumBases() << ")\n";
        for (const auto &base : D->bases())
        {
          llvm::outs() << "- - ";
          const QualType type = base.getType();
          const RecordType *recType = type->getAs<RecordType>();
          const CXXRecordDecl *cxxDecl = cast_or_null<CXXRecordDecl>(recType->getDecl()->getDefinition());
          assert(cxxDecl);

          llvm::outs() << type.getAsString() << " | " << cxxDecl->getQualifiedNameAsString();
          if (base.isVirtual())
          {
            llvm::outs() << " (virtual)";
          }
          llvm::outs() << "\n";
        }

        llvm::outs() << "- All bases\n";
        auto cb = [](const CXXRecordDecl *base, void *param) {
          const CXXRecordDecl *D = reinterpret_cast<CXXRecordDecl*>(param);
          llvm::outs() << "- - ";
          if (base->getIdentifier())
          {
            llvm::outs() << "id: " << base->getIdentifier()->getName();
          }
          llvm::outs() << " | " << base->getQualifiedNameAsString();
          if (D->isVirtuallyDerivedFrom(base))
          {
            llvm::outs() << " (virtual)";
          }
          llvm::outs() << "\n";
          return true;
        };

        D->forallBases(cb, D);
      }

      llvm::outs() << "- Methods\n";
      for (auto i: D->methods())
      {
        llvm::outs() << "- - " << (*i).getQualifiedNameAsString() << "\n";
      }
    }   

    llvm::outs() << "------------------------------------------\n";

    return true;
  }

Результат выполнения будет примерно таким:

CXXRecord: struct, id: MyStruct | MyStruct
- Methods
------------------------------------------
CXXRecord: class, id: MyClass | MyClass
- Methods
------------------------------------------
CXXRecord: class, id: MyDerived | Nspace::MyDerived
- Bases (1)
- - struct MyStruct | MyStruct
- All bases
- - id: MyStruct 0x2560108 | MyStruct
- Methods
------------------------------------------
CXXRecord: class, id: MyDerived2 | Nspace::MyDerived2
- Bases (2)
- - class Nspace::MyDerived | Nspace::MyDerived
- - class MyClass | MyClass (virtual)
- All bases
- - id: MyDerived | Nspace::MyDerived
- - id: MyClass | MyClass (virtual)
- - id: MyStruct | MyStruct
- Methods
- - Nspace::MyDerived2::operator=
- - Nspace::MyDerived2::~MyDerived2
------------------------------------------

Заключение

Так сказать, вуаля. С помощью такого несложного кода можно реализовать выборочный обход интересующих узлов абстрактного синтаксического дерева, построенного clang'ом.

Исходный код

Исходники можно взять на GitHub.

Следующая статья


Комментариев нет:

Отправить комментарий