Фантом, которого не было: Как ненадёжный grep заставил нас чинить призрака
· 3 min read
Один из самых сложных этапов в разработке генератора документации — это сверка (alignment) с легаси-инструментами. Когда вы переходите со старого, десятилетиями работавшего проприетарного генератора на свой собственный, вы обязаны доказать, что не потеряли ни одного класса.

Мы написали автоматизированную систему, которая сравнивала тысячи сущностей, сгенерированных старым генератором, с тем, что нашел наш новый пайплайн на базе Flude. И в один из дней мы наткнулись на пугающую аномалию.
Аномалия в матрице
Скрипт сверки радостно отрапортовал: Flude нашел и сгенерировал документацию для структуры GraphicsAsset и её умного указателя GraphicsAssetPtr.
Проблема заключалась в том, что в старой, эталонной документации этих сущностей не было. Вообще.
По нашей логике, если легаси-генератор не нашел класс, возможно, этого класса просто не существует, а наш парсер где-то ошибся и “склеил” чужие имена. Нужно было проверить исходники.
Нулевой результат

Мы сделали то, что сделал бы любой инженер — открыли терминал и запустили быстрый глобальный текстовый поиск (grep / ripgrep) по всей многогигабайтной кодовой базе нашего энтерпрайзного C++ продукта:
rg "GraphicsAsset" ./sdk_sources/
Результат: 0 совпадений. Абсолютная пустота.
Мы проверили поиск с игнорированием регистра, поискали умный указатель. Ничего.
Паника: Парсер-галлюцинатор
Это был момент настоящего ужаса. Кодовая база не содержит строки GraphicsAsset. Но наш парсер каким-то чудом не просто нашел это имя, а построил для него красивую HTML-страницу со списком методов, свойств и наследованием!
Вывод напрашивался сам собой: наш парсер сошел с ума. Он начал фабриковать C++ классы прямо из воздуха. Возможно, сломалась логика разбора сложных макросов, или кэш Doxygen перекосило так, что он начал склеивать куски разных файлов в одного “Франкенштейна”.
Мы уже приготовились открывать отладчик и переписывать логику связывания токенов в ядре.
Вскрытие кэша
К счастью, прежде чем ломать кодовую базу ядра, мы решили посмотреть на сырые промежуточные данные. Мы пошли в директорию, куда Doxygen складывал свой низкоуровневый XML-дамп, и поискали там.
Внезапно, на диске преспокойно лежал файл struct_graphics_asset.xml. Мы открыли его.
Внутри оказался идеально сформированный XML. Он не был похож на галлюцинацию. Более того, в теге <location> парсер с абсолютной педантичностью указывал нам, откуда он взял этот класс:
Core/Include/Graphics/MaterialConfig.h:3006-3061
Фантом обретает плоть

Мы открыли файл MaterialConfig.h и проскроллили до 3006 строки.
И там, в самом сердце огромного легаси-хедера, пряталась совершенно реальная, живая структура:
struct CORE_ABSTRACT GRAPHICS_EXPORT GraphicsAsset {
// ... десятки строк валидного C++ кода
};
Она всегда была там. Легаси-генератор просто не смог ее распарсить из-за сложных макросов, а наш Flude — смог.
Почему же глобальный rg выдал ноль совпадений? Причин в энтерпрайзных монолитах всегда масса. Возможно, путь Core/Include лежал в хитром .gitignore, и умный ripgrep его проигнорировал. Возможно, файл имел экзотическую Windows-кодировку, которая сломала поиск. Возможно, это был примонтированный сабмодуль.
Итог
Глобальный текстовый поиск выдал отсутствие доказательств за доказательство отсутствия.
Этот случай научил нас важнейшему правилу: текстовый поиск — это не C++ парсер. Пытаться анализировать структуру C++ монолита регулярными выражениями или grep’ом — это путь к катастрофе и ложным выводам.
Если вы сомневаетесь в парсере, никогда не используйте текстовый поиск как арбитра. Доверяйте только сырым выгрузкам самого компилятора или абстрактному синтаксическому дереву (AST). И самое главное: не пытайтесь “исправить” баг в ядре, пока не докажете его существование в самом источнике истины.
Но проблемы парсинга и кэша меркли перед инфраструктурными вызовами. Наш проект с самого первого дня строился как экосистема из нескольких независимых репозиториев. О том, как эта архитектура обернулась ловушками в Git и чуть не сломала нам Continuous Integration — читайте в следующем эпизоде.