До появления 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