Извлекаем мета-информацию из Си/C++ кода при помощи (py)gccxml

в 11:36, , рубрики: c++, python, Компиляторы

Извлекаем мета информацию из Си/C++ кода при помощи (py)gccxmlДо появления gccxml, был только один способ извлечь мета-информацию из Си/С++ кода. Для начала, необходимо было написать парсер, способный справиться с грамматикой языка С++. Это не та задача, которую вы обычно решаете дома за выходные.

Теперь, писать парсер больше не нужно. Модифицированный компилятор gcc анализирует ваш код и выдает описание всех пространств имен, типов, классов и функций, встреченных в программе. Данные выдаются в формате XML и в принципе готовы для дальнейшего автоматического анализа и обработки.

Для разбора XML данных, полученных от gccxml, пригодится библиотека pygccxml. Это не просто ридер формата gccxml — библиотека предоставляет интерфейсы для изучения собранных метаданных; в частности есть готовые функции, отвечающие на вопросы вроде «совместимы ли типы T1 и T2?» или «наследует ли класс C1 от C2?». Библиотека написана на языке Python.

Знакомство с gccxml

Gccxml был разработан в Kitware (они же авторы CMake). Это модифицированный C++ парсер из GCC.

Вероятно, у вас еще не установлен gccxml. Лично я ставил gccxml с помощью менеджера пакетов и не вижу необходимости останавливаться на этом шаге подробно. Если для вашей ОСи нет пакетного менеджера, боюсь я ничем не могу помочь.

Начнем с простого определения функции.

namespace test {
  int fn(int a, int b);
}

Компилируем:

gccxml -fxml=test.xml test.cpp

На выходе получаем test.xml такого содержания (фрагмент):

<GCC_XML>
  <Namespace id="_1" name="::" members="… _96 …"
            mangled="_Z2::" demangled="::"/>
  <Namespace id="_96" name="test" context="_1" members="_141 "
            mangled="_Z4test" demangled="test"/>
  <FundamentalType id="_128" name="int" size="32" align="32"/>
  <Function id="_141" name="fn" returns="_128" context="_96" 
            mangled="_ZN4test2fnEii" demangled="test::fn(int, int)"
            location="f1:2" file="f1" line="2" extern="1"
  >
    <Argument name="a" type="_128" location="f1:2" file="f1" line="2"/>
    <Argument name="b" type="_128" location="f1:2" file="f1" line="2"/>
  </Function>
  <File id="f1" name="test.cpp"/>
</GCC_XML>

Здесь все понятно и без документации. Я не буду приводить примеры для других конструкций языка C++ — там все аналогично. Первоочередная цель достигнута — мета-информация извлекается в формате, пригодном для дальнейшей автоматической обработки.

Извлекаем еще больше мета-информации

Иногда в исходных текстах содержится больше информации, чем предусмотрено семантикой языка C++. Пример: SAL-анотации в Windows (__in, __out и тп.)

BOOL WINAPI CreateProcess(
  __in_opt     LPCTSTR lpApplicationName,
  __inout_opt  LPTSTR lpCommandLine,
  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in         BOOL bInheritHandles,
  __in         DWORD dwCreationFlags,
  __in_opt     LPVOID lpEnvironment,
  __in_opt     LPCTSTR lpCurrentDirectory,
  __in         LPSTARTUPINFO lpStartupInfo,
  __out        LPPROCESS_INFORMATION lpProcessInformation
);

Пример: информация о минимальной версии Mac OS X, в которой доступна API функция.

CFErrorRef CFErrorCreate(
  CFAllocatorRef allocator,
  CFStringRef domain,
  CFIndex code,
  CFDictionaryRef userInfo
)
AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;

Даже эту дополнительную мета-инфрмацию можно извлекать при помощи gccxml. Здесь нам поможет специфичное для gcc расширение синтаксиса языка C++ — конструкция attribute. Экспериментировать будем с определением функции fn:

#define __foo __attribute__((gccxml("__foo"))
#define __bar __attribute__((gccxml("__bar"))
namespace test {
  __foo int fn(__bar int a, int b);
}

Атрибуты «применяются» к ближайшей семантической единицей в исходном тексте. Так первый атрибут относится к функции fn, а второй — к параметру a. Gcc понимает различные атрибуты, но в данном случае нас интересует только атрибут gccxml.

Gccxml выдает следующую информацию по функции fn. Как мы видим, все аннотации сохранены и доступны для дальней обработки.

<Function id="_141" name="fn" returns="_128" context="_96" 
            mangled="_ZN4test2fnEii" demangled="test::fn(int, int)" 
            location="f1:7" file="f1" line="7" extern="1" 
            attributes="gccxml(__foo)">
  <Argument name="a" type="_128" location="f1:7" file="f1" line="7" 
            attributes="gccxml(__bar)"/>
  <Argument name="b" type="_128" location="f1:7" file="f1" line="7"/>
</Function>

Знакомство с pygccxml

Pygccxml разрабатывается Roman Yakovenko и co. Цель проекта — автоматическое изготовление C++/Python биндингов при помощи boost::python. Интересно, чем их не устроил SWIG?

Pygccxml можно поставить либо через пакетный менеджер, либо вручную (качать здесь, инструкции по установке в README.txt).

Документация на pygccxml оставляет желать лучшего. Для начала работы ее достаточно, но если потребуется что-то, выходящее за рамки базовых возможностей, придется заглядывать в исходный код библиотеки. Это странно, но документация не доступна для онлайн-просмотра, ее можно только скачать.

Ниже приведен пример простешего анализатора C++ кода с использованием библиотеки pygccxml.
Скрипт распечатывает все функции, объявленные в прострастве имен test.

import pygccxml
db = pygccxml.parser.parse(['test.cpp'])
global_ns = pygccxml.declarations.get_global_namespace(db)
for test_ns in global_ns.namespaces('test'):
    for function in test_ns.calldefs():
        pygccxml.declarations.print_declarations(function)

Вот результат работы скрипта:

free_function_t: 'fn'
    location: [./test.cpp]:4
    artificial: 'False' 
    attributes: gccxml(__foo)
    demangled: test::fn(int, int)
    mangled: _ZN4test2fnEii
    return type: int
    arguments type: int a, int b

Идеи для вашего кода

Зачем мне программно анализировать C++ код, если я не интересуюсь языками и компиляторами? — спросит прагматичный читатель. Сейчас я расскажу про несколько вполне реальных задач, которые потребовали автоматического анализа кода.

Вот что пишет Алексей Пахунов aka notakernelguy:

Я тут совсем недавно удивлялся почему нет библиотек, эмулирующих поддержку UTF-8 на уровне Win32 API. Т.е. такая библиотека реализует, скажем, CreateFileUtf8 в дополнение к предлагаемым системой CreateFileA и CreateFileW, а макрос CreateFile будет выбирать нужную реализацию уже из трех вариантов.

В 2007 году Алексей решает создать такую библиотеку для прозрачного внедрения поддержки Unicode в программу Notepad2. Предполагалось автоматически обработать заголовочные файлы Windows и генерировать искомую библиотеку программно. Алексей не использует gccxml и в 2012 году его библиотека все еще не готова.

Следующие два примера из моей практики.

С помощью gccxml я делал C++ обертку для CoreFoundation — базового объектно-ориентированного Си API в Mac OS X. Цель данного проекта — реализовать автоматическое управление временем жизни CF объектов. Да, я в курсе про ARC.

А вот второй пример. У меня есть система для обработки данных, написанная на C++. Система изначально однопоточная, для увеличения быстродействия в планах разнести часть взаимодействующих объектов по разным потокам. Для этого предполагается создать ряд proxy-классов, которые будут конвертировать вызов метода в отправку сообщения другому потоку, где после раcпаковки сообщения будет вызван метод объекта, скрытого за proxy. Изменений существующего кода не требуется, так как доступ к любому объекту по-прежнему осуществляется из единственного потока. Требуется написать много однотипного кода, и эту задачу лучше всего доверить автоматическому генератору.

Ограничения gccxml

К сожалению, gccxml имеет некоторые недостатки. Из кода извлекаются только декларации, а тела функций не доступны. Декларации шаблонов также не доступны. Gccxml основан на достаточно старой версии gcc и разработка идет не слишком активно.

Автор: mejedi

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js