Ад системных заголовков: Как мы учили libclang понимать GCC и выживать с MSVC
· 3 min read
После того как мы перевели парсер C++ на libclang, мы ожидали увидеть идеально точное абстрактное синтаксическое дерево (AST). Но когда мы натравили его на первый же реальный проект, вместо красивой иерархии классов мы получили дерево, состоящее из сплошных CXCursor_UnexposedDecl и фатальных синтаксических ошибок.

Парсер спотыкался на первой же строчке: #include <vector>.
Глухой компилятор
Оказалось, что libclang (будучи просто подключаемой библиотекой, а не полноценным CLI-компилятором) по умолчанию понятия не имеет, где в операционной системе лежат стандартные заголовочные файлы.
Если парсер не видит системных заголовков, он не понимает, что такое std::string или std::vector. А поскольку в современном C++ всё завязано на шаблонах стандартной библиотеки, парсинг ломается каскадом.
В Enterprise-мире разработчики обычно сами передают парсерам гигантские простыни из флагов -I/usr/include/.... Но мы строили SaaS и удобную CLI-утилиту. Мы не могли заставлять пользователей вручную искать пути к компиляторам на своих машинах. Парсер должен был стать умным.
Квест по поиску GCC

На Linux-системах нам пришлось написать собственный механизм автообнаружения (Auto-Discovery).
Мы реализовали эвристику, которая в фоне вызывала системный компилятор с хитрыми флагами: gcc -print-file-name=include (с фоллбэком на cc). Этот трюк заставляет GCC (или системный Clang) выплюнуть в консоль список всех директорий, которые он использует для поиска системных заголовков по умолчанию.
Наш Python-код перехватывал этот вывод, парсил его, фильтровал несуществующие директории и динамически конвертировал в массив флагов -I, которые затем скармливались в libclang. Парсер “прозрел” — он начал идеально понимать Linux-проекты.
Кошмар MSVC-STL на Windows
Но если на Linux всё решилось элегантным хаком, то на Windows нас ждал настоящий ад.
Разработчики под Windows используют компилятор Microsoft Visual C++ (MSVC) и его проприетарную реализацию стандартной библиотеки — MSVC-STL. Интеграция Clang с хитросплетениями виндового кода (включая монструозный windows.h) — это боль.
MSVC исторически прощает многие вольности синтаксиса и использует специфичные макросы, прагмы и расширения. Строгий libclang смотрел на код из MSVC-STL и просто отказывался его парсить, генерируя сотни ошибок.
Чтобы помирить их, нам пришлось глубоко закопаться в документацию LLVM. Мы подобрали специфичный набор флагов совместимости (-fms-compatibility, -fms-extensions, а также подменять макросы -U__clang_major__ -D__clang_major__=19 для обхода проверок версии в MSVC STL), которые заставлялиlibclang` притворяться компилятором от Microsoft. Только после тонкой настройки виртуального окружения парсер перестал паниковать при виде виндовых исходников.
Итог
Мы потратили кучу времени не на сам парсинг клиентского кода, а на настройку окружения, в котором этот код живет. Но результат того стоил: наш парсер научился “из коробки” читать реальный код разработчиков на любой операционной системе, не требуя от них сложных конфигураций.
Но “видеть” код — не значит понимать его структуру. Когда парсер наконец-то прочитал все заголовки, мы обнаружили, что он теряет половину методов, если разработчик осмелился определить их за пределами класса (Out-Of-Line). О том, как мы вручную пришивали оторванные методы к шаблонам — в следующем эпизоде.