26 ноября 2010 г.

Clang API: получение AST в виде XML


Задача: с помощью Clang API получить XML-файл, описывающий абстрактное синтаксическое дерево (AST) исходного кода. Это можно сделать и простым запуском команды clang с параметром -ast-print-xml в командной строке, но через API — интереснее :)

Библиотеки clang'а еще не входят в официальные дистрибутивы, поэтому первое, что нужно сделать — выгрузить clang из репозитория и собрать. Clang хорош тем, что собрать его можно и для Unix-подобных систем и для Windows. Я буду использовать clang версии 2.8, так как на данный момент она является последней стабильной версией. К сожалению, clang собирается только как часть процесса сборки LLVM, поэтому, если в системе уже установлен LLVM, то его всё равно придется пересобирать.

Теперь создадим тестовый файл с исходным кодом на C++, для которого будем строить AST, назовем его a.cpp и положим куда-нибудь недалеко. Вот он (файл можно взять здесь):

  1. struct MyClass
  2. {
  3.   unsigned long _a;
  4.   mutable unsigned long _b;
  5.  
  6.   MyClass () : _a(4), _b(44) {}
  7.   unsigned long func ()
  8.   {
  9.     unsigned long r = _a + _b;
  10.     return r;
  11.   }
  12. };

Использование параметров командной строки

Первая сущность, с которой предстоит познакомиться, это CompilerInstance — класс, который является фронтэндом компилятора и выполняет всю "грязную" и "неинтересную" работу. Он находится в пространстве имен clang и создаётся просто:

auto_ptr<CompilerInstance> compiler(new CompilerInstance);

Затем нужно создать экземпляр класса Diagnostic, который будет использоваться компилятором для сбора информации об ошибках и проблемах. Сейчас нас устроит класс, работающий с stdout, поэтому пусть компилятор создаст его сам:

compiler->createDiagnostics(0, NULL); // to stdout
assert(compiler->hasDiagnostics());

Компилятор есть, можно начинать с ним работать — нужно передать в компилятор входные параметры командной строки, необходимые для его инициализации:

const char *args[] =
{
  "-cc1", // вызов LLVM Clang
  "a.cpp" // входной файл
};

clang::CompilerInvocation::CreateFromArgs(
  compiler->getInvocation(),
  args,
  args + 2,
  compiler->getDiagnostics());
assert(0 == compiler->getDiagnostics().getNumErrors());

Теперь нужно создать класс-действие и передать его компилятору на запуск. Так как мы хотим сгенерировать AST в виде XML-файла, то нужно создать экземпляр класса ASTPrintXMLAction, который является наследником класса FrontendAction, и затем выполнить действие с помощью метода CompilerInstance::ExecuteAction():

auto_ptr<FrontendAction> action(new ASTPrintXMLAction);

bool actionSuccessful = compiler->ExecuteAction(*action);
assert(0 == compiler->getDiagnostics().getNumErrors());
assert(actionSuccessful);

Всё, что нужно, уже написано. Теперь программу нужно скомпилировать, слинковать с библиотеками clang и LLVM и запустить.

Если всё прошло успешно, то в директории, где мы запускаем исполняемый файл, появится файл a.xml, содержащий AST файла a.cpp.

Инициализация фронтэнда вручную

Теперь попробуем не использовать метод CompilerInvocation::CreateFromArgs(), а инициализировать фронтэнд компилятора самостоятельно (будем использовать минимальный набор параметров для успешного генерирования AST). Начало такое же, как и в предыдущем примере. Создаём компилятор и диагностику:

auto_ptr<CompilerInstance> compiler(new CompilerInstance);
compiler->createDiagnostics(0, NULL); // to stdout
assert(compiler->hasDiagnostics());

Зададим входной файл, ну и выходной за компанию:

pair<InputKind, string> input(IK_CXX, "a.cpp");
compiler->getFrontendOpts().Inputs.push_back(input);

compiler->getFrontendOpts().OutputFile = "ast.xml";

Теперь нужно указать платформу, а то без нее никуда, и указать язык исходного кода, так как указания типа входного файла IK_CXX недостаточно (в данном случае там вообще можно указать IK_None):

compiler->getTargetOpts().Triple = "x86_64"; // x86, alpha, ppc, ppc64, ...
compiler->getLangOpts().CPlusPlus = 1;

Теперь создаем экземпляр класса-действия и выполняем его:

auto_ptr<FrontendAction> action(new ASTPrintXMLAction);

bool actionSuccessful = compiler->ExecuteAction(*action);
assert(0 == compiler->getDiagnostics().getNumErrors());
assert(actionSuccessful);

После сборки и запуска программы появится файл ast.xml, который должен совпадать по содержимому с файлом a.xml, который у нас получился в прошлом примере.


UPD: Получившийся XML-файл можно взять здесь.
Исходный код для тестов - здесь.

UPD2: После того, как вы соберете llvm и clang (и выполните make install), у вас появится исполняемый файл llvm-config, который вам очень поможет при компиляции и линковке ваших собственных проектов.

  • Директории с заголовочными файлами: llvm-config --includedir
  • С/C++ флаги: llvm-config --cppflags | --cflags | --cxxflags
  • LD флаги: llvm-config --ldflags
  • Либы: llvm-config --libs (только llvm-либы, либы clang вам придется прописывать руками)
  • И многое другое: llvm-config --help

13 комментариев:

  1. А можно посмотреть, что в итоге получилось, сам xml файл?

    ОтветитьУдалить
  2. Можно.
    http://code.google.com/p/white-knight-is-alive/source/browse/trunk/examples/clang/ast.xml

    Заодно добавил ссылку в конце поста.

    ОтветитьУдалить
  3. Спасибо! А то хотелось посмотреть как же этот AST в xml выглядит.

    ОтветитьУдалить
  4. Можно получить исходники?

    ОтветитьУдалить
    Ответы
    1. http://code.google.com/p/white-knight-is-alive/source/browse/trunk/examples/clang/

      Удалить
    2. Пытаюсь скомпилировать с clang3.0 не выходит. Какую версию библиотеки использовали?

      Удалить
    3. В тексте же написано :)
      "Я буду использовать clang версии 2.8"

      Удалить
    4. Простите не заметил. Большое спасибо за статью :)

      Удалить
    5. Еще вопрос
      для функций тоже будет вывод в формате XML как для методов или в таком же формате как выдает -ast-print-xml например:
      (CompoundStmt 0x26c7d350
      (ReturnStmt 0x26c7d340
      (CallExpr 0x26c7d2f8 'long long'
      (ImplicitCastExpr 0x26c7d2e8 'long long (*)(int, long long, int) __attribute__((cdecl))'
      (DeclRefExpr 0x26c7d214 'long long (int, long long, int) __attribute__((cdecl))' Function 0x26c7fc50 '_lseeki64' 'long long (int, long long, int) __attribute__((cdecl))'))
      (ImplicitCastExpr 0x26c7d320 'int'
      (DeclRefExpr 0x26c7d22c 'int' lvalue ParmVar 0x26c80050 'fd' 'int'))
      (CStyleCastExpr 0x26c7d280 'long long'
      (ImplicitCastExpr 0x26c7d270 'off64_t':'long long'
      (DeclRefExpr 0x26c7d244 'off64_t':'long long' lvalue ParmVar 0x26c80090 'offset' 'off64_t':'long long')))
      (ImplicitCastExpr 0x26c7d330 'int'
      (DeclRefExpr 0x26c7d298 'int' lvalue ParmVar 0x26c7d0e0 'whence' 'int')))))

      Удалить
    6. Я, к сожалению, не знаю.

      Удалить
    7. Есть скомпилированная версия? Не получилось скомпилировать(

      Удалить