Code Property Graph — не «карта файлов» и не результат одного парсера. Это единая модель программы, где синтаксическая структура связана с потоком управления, вызовами, типами, зависимостями данных и исходными координатами. CodeGraph использует эти связи для поиска дефектов, архитектурных нарушений и метрик аудита.
Эта статья отвечает на два вопроса:
- как текущий GoCPG строит и обогащает граф;
- что можно честно сравнивать с Joern без неподтверждённых заявлений о скорости или точности.
Формулы, которые превращают замечания и метрики графа в Q1–Q12 и Health Score, описаны в методологии Dashboard.
Коротко: где проходит граница GoCPG
GoCPG отвечает за построение и хранение CPG проекта. Языковой адаптер
(frontend) создаёт базовые узлы и связи, затем упорядоченные проходы анализа
(passes) добавляют семантические слои.
Результат сохраняется в DuckDB; CodeGraph получает типизированные результаты
через сервисные контракты. Прикладной слой не строит второй «канонический» граф
поверх первого.
Состав конвейера определяется текущим кодом и профилем. Поэтому документация не фиксирует маркетинговое число проходов: стандартный набор может расширяться доменной разметкой, VCS-enrichment и профильными анализаторами.
1. От исходников до базового графа
Выбор языкового адаптера
GoCPG регистрирует отдельные языковые адаптеры для C, C++, Go, Python, JavaScript,
TypeScript, Java, Kotlin, C#, PHP и 1C. Большинство используют tree-sitter. Go
использует go/parser и go/ast, а 1C — собственный lexer/parser. Для
части языков доступен упрощённый fallback без полной семантической точности.
Frontend отвечает за то, что можно извлечь непосредственно из текста:
- файлы, namespaces/modules, типы, методы, параметры и локальные переменные;
- вызовы, литералы, идентификаторы, блоки и управляющие конструкции;
- импорты, аннотации, комментарии и координаты в исходном коде;
- связи AST, аргументов, получателей вызова и исходных файлов.
Успешный разбор ещё не означает полный анализ. Например, языковой адаптер видит синтаксическую форму динамического вызова, но его целевой метод может остаться неразрешённым до восстановления типов и разрешения импортов.
Материализация
Полная сборка проходит так:
- GoCPG находит файлы, применяет include/exclude rules и расширения выбранного языкового адаптера.
- Frontend разбирает файлы параллельно и формирует
DiffGraph. Ошибки сохраняются по файлам, а не исчезают из результата. - Идентификаторы адаптера синхронизируются с глобальным генератором, чтобы добавленные проходами узлы не столкнулись с исходными.
- Базовый diff записывается в DuckDB и одновременно становится in-memory
CPGGraphдля следующих стадий. - Семантические проходы читают уже построенные слои, создают новые узлы/рёбра и сохраняют изменения.
Неполный разбор, пропущенный файл или недоступный адаптер ограничивает основу, а не «чистый» результат.
2. Семантический конвейер
Порядок важен: call graph нельзя надёжно строить до imports и type recovery, а PDG — до control/data-flow.
| Стадия | Что добавляется | Для чего используется |
|---|---|---|
| Базовая топология | metadata, files, namespaces, type nodes/declarations, inheritance, parameter links | Единая идентичность сущностей и типов. |
| Восстановление типов | типы идентификаторов и вызовов, origin/confidence для call nodes | Более точные FQN и цели вызовов в динамических языках. |
| Поток управления | CFG, dominators, post-dominators, CDG | Достижимость, ветвления и зависимости управления. |
| Imports и вызовы | import maps, Call FQN propagation, call resolution, dynamic dispatch, FFI edges | Межфайловые и межъязыковые связи вызовов. |
| Поток данных | references, alias analysis, reaching definitions, interprocedural propagation | Источник значения, побочные эффекты и пути между методами. |
| Анализ рисков | неинициализированные переменные, диапазоны значений, утечки ресурсов, разыменование null, конкурентность, taint, переполнение целых и буферов | Замечания с привязкой к графовому пути и исходному коду. |
| Зависимости программы | EVAL_TYPE, return type propagation, PDG и DDG | Совместный анализ control- и data-dependencies. |
| Объектная модель | bindings, vtables, dynamic call linking | Интерфейсные и виртуальные вызовы, constructor-to-initializer links. |
| Обогащение | метрики методов, комментарии, вложенность, замечания, структурный поиск шаблонов | Метрики аудита, поиск шаблонов и объяснимые результаты. |
| Опциональные слои | domain annotations, use-case propagation, VCS tags | Доменный контекст, точки входа, авторство и частота изменений. |
Каждый проход объявляет зависимости и поддержку инкрементального выполнения. Параллельно могут выполняться только независимые части DAG; «параллельный конвейер» не означает нарушение семантического порядка.
3. Восстановление типов в Python и JavaScript
Для компилируемого проекта тип часто доступен из объявления или toolchain. В Python и JavaScript один и тот же идентификатор может получить тип только во время выполнения. GoCPG не притворяется, что эта неопределённость исчезает: он собирает статические свидетельства, применяет их итеративно и сохраняет происхождение и уверенность там, где это предусмотрено схемой.
Источники типовых фактов
Первая, внутрипроцедурная стадия использует:
- аннотации и уже извлечённые type declarations;
- присваивания вида
x = ClassName(...); - constructor calls;
- значения локальных переменных и одноимённые identifiers;
self/this-поля и member assignments;- Python
isinstanceguards; - return types встроенных функций и методов из language-specific Type Knowledge Base.
Для Python и JavaScript есть отдельные наборы встроенных функций и типовых
заглушек. Это не полное окружение выполнения: пользовательская метапрограммная
логика, monkey
patching, eval, динамические импорты и произвольное изменение object shape
могут остаться неразрешёнными.
Итерации и сходимость
На последующих итерациях GoCPG распространяет:
- возвращаемый тип от разрешённого метода к месту вызова;
- тип результата вызова к переменной в присваивании;
- тип из return statements и properties;
- факты динамической диспетчеризации из Type Knowledge Base;
- временный
<returnValue>type для вызова, цель которого известна, а точный return type — нет.
Обновления сначала собираются параллельно, затем применяются детерминированно. По умолчанию конфигурация сохраняет совместимость с тремя базовыми итерациями, но adaptive loop может продолжаться до десяти. После третьей итерации он останавливается, если отношение новых обновлений к предыдущей итерации ниже 1%, или раньше, если обновлений нет.
Для call nodes сохраняются type_origin и type_confidence. Примеры
источников: builtin, tkb, constructor, propagated, inferred.
Факты конструктора и встроенных функций имеют более высокую уверенность, временный
<returnValue> — более низкую. Это позволяет downstream-анализу отличить
явный факт от приближения.
Как типы улучшают граф вызовов
После type recovery отдельный pass переписывает method_full_name вызова,
используя восстановленный тип receiver и import map. Затем call resolution
связывает call node с наиболее подходящим method node, а dynamic linker
добавляет virtual dispatch и constructor-to-__init__ связи.
Если данных недостаточно, вызов остаётся неразрешённым или приблизительным. Наличие ребра вызова не доказывает достижимость во время выполнения, а его отсутствие при динамической магии не доказывает отсутствие вызова. Security и architecture rules должны учитывать confidence и completeness basis.
4. Потоки управления и данных
GoCPG строит несколько связанных представлений:
- CFG показывает допустимый порядок выполнения внутри метода;
- dominator и post-dominator trees нужны для анализа обязательных путей;
- CDG связывает узел с условием, от которого зависит его выполнение;
- REF и reaching-definition edges связывают использование со значением;
- alias analysis уточняет возможные ссылки на один объект;
- межпроцедурные reaching definitions переносят факты через места вызова;
- DDG материализует зависимости данных;
- PDG объединяет зависимости данных и управления.
Taint, resource-leak, null-dereference и другие анализаторы работают поверх этих слоёв. Поэтому замечание должно сохранять сообщение вместе с координатой в исходном коде, связанный узел и, где применимо, путь доказательства.
Разные CPG-реализации могут материализовать разные вспомогательные узлы и рёбра. Сырое количество узлов, CALL или CDG само по себе не показывает точность без размеченных ожидаемых фактов в размеченном наборе.
5. Структурный анализ Architecture v1
Новый структурный анализ не сводится к поиску импортов. Он классифицирует сущности CPG по компонентам и проверяет разрешённые отношения между ними.
Модель и правила
- Каждая сущность, участвующая в классификации, получает стабильный UID компонента. Отображаемый
idможно менять;uidвходит в identity и fingerprint. - Parent components группируют листья, но сами не классифицируют сущности.
- Область, слой, тип, платформа и среда выполнения — независимые измерения. Для обязательной проверки классификация должна быть полной и однозначной.
- Architecture rules используют
spec_version: "2.0",kind: architecture, стабильный rule ID и selectorsall,any,not. - Preset и rule references всегда version-pinned:
id@version.latestи неявное наследование статуса соседнего языка отклоняются.
Architecture v1 содержит три общих template и девять language presets: C, C++, C#, Go, JavaScript, Kotlin, PHP, Python и TypeScript. JavaScript и TypeScript имеют отдельные манифесты возможностей и результаты приёмки.
Полнота, baseline и waiver
Capability registry объявляет, какие семейства фактов даёт каждый language
adapter. Недостающая обязательная capability не усредняется с успешными:
обязательная проверка получает BLOCKED_INCOMPLETE.
Incremental run считается авторитетным только как incremental_complete, если
его fingerprints, baseline/waiver states и semantic digest совпадают с полным
запуском. Изменение публичного API, модели, правил, baseline, waiver, рёбер или SCC
расширяет invalidation. Частичный результат вызывает один synchronous full
retry; только свежий полный результат может заменить его.
Baseline связывает принятый долг с точным rule, fingerprint version, finding fingerprint и semantic hash. Файл в анализируемом репозитории сам по себе ничего не подавляет. Waiver ограничен по времени, требует разных requester/approver и подписывает проект, репозиторий, commit, ветку, хеши и ревизию. Ключи доверия и revocation state приходят из control plane или CI trust config, а не из проверяемого кода.
Результаты и release gate
Канонический JSON содержит запуск с привязкой к ревизии, доказательство полноты, результат каждого активного правила, замечания, покрытие и метрики. SARIF, Mermaid и PlantUML — детерминированные проекции того же результата; они не меняют fingerprint. Read-only gRPC QueryService позволяет list/export/validate/describe/explain сохранённый run.
Профили discovery и advisory не разрешают выпуск. enforce_new,
enforce_all и strict могут вернуть PASS или FAIL_VIOLATIONS, но
только при свежих и полных фактах и достаточном покрытии возможностей.
Отсутствующее, устаревшее, частичное, ошибочное доказательство или нулевой
знаменатель даёт
BLOCKED_INCOMPLETE.
Эти результаты питают прежде всего Q2, Q4, Q6 и Q11 аудита, но не заменяют другие сценарии.
6. Карта решения: GoCPG и Joern
Joern также строит CPG через языковые адаптеры, добавляет семантические слои проходами и предоставляет язык запросов CPGQL на Scala. Официальная документация описывает интерактивную консоль, сценарии, серверный режим, расширяемые проходы и plugins. Frontend и набор overlays зависят от release, поэтому их нужно фиксировать в каждом сравнении.
| Аспект | GoCPG в CodeGraph | Joern |
|---|---|---|
| Основной процесс | Импорт проекта, автоматические анализаторы, Dashboard, CLI/MCP/REST и управляемые доказательства. | Интерактивное исследование уязвимостей через запросы, сценарии, сканирование и серверный режим. |
| Хранение | DuckDB в области проекта, атомарная запись и типизированные сервисные контракты. | Собственная графовая база в памяти и модель проектов Joern. |
| Запросы | Сервисы Go, gRPC и контролируемые интерфейсы проекта; SQL остаётся внутри границы хранения. | CPGQL на Scala, REPL, сценарии и HTTP-сервер. |
| Расширение | Языковые адаптеры, Go-проходы с зависимостями, правила шаблонов и архитектуры, доменные и VCS-слои. | Языковые адаптеры, CPG-проходы, расширения запросов и плагины JVM. |
| Динамические типы | Общий итеративный проход, отдельные типовые заглушки, происхождение/уверенность и распространение FQN. | Реализация зависит от адаптера и слоёв зафиксированной версии. |
| Структурный контроль | Версионированная модель и presets, реестр возможностей, подписанное управление, эквивалентность полного/инкрементального анализа и запрет при неполных данных. | Архитектурные запросы и расширения строятся средствами CPGQL, проходов и плагинов; правила принятия решения нужно оценивать отдельно. |
Joern подходит командам, которым нужен интерактивный анализ через CPGQL. GoCPG встроен в автоматизированный жизненный цикл CodeGraph, где важны хранение в области проекта, воспроизводимые замечания, свежесть, управление и доказательства готовности к выпуску.
Официальные источники Joern для сверки:
7. Почему здесь нет старых цифр производительности
Историческая версия этой статьи содержала конкретные времена, числа узлов/рёбер и выводы о Python call graph. Эти измерения относились к старым версиям GoCPG и Joern, двум конкретным checkout и известной проблеме нормализации путей Python. Без исходного манифеста, журналов, digest корпуса и повторного запуска такие значения нельзя выдавать за состояние текущего продукта.
Техническая структура исходной статьи восстановлена, а численные выводы намеренно не перенесены. Это не отказ от сравнения, а граница проверяемости.
8. Воспроизводимый benchmark
Для численного вывода нужен неизменяемый манифест измерения
(immutable benchmark manifest, далее benchmark manifest):
- commit GoCPG/CodeGraph, точный release Joern и artifact digests;
- commit корпуса, языки, правила включения/исключения и список ожидаемых фактов;
- оборудование, ОС, ограничения выполнения, состояние кеша и параллельная нагрузка;
- точные команды import, query, export и cleanup;
- cold/warm policy, число повторов, timeout, failures и outlier treatment;
- wall time, CPU, peak memory, storage и полнота результата;
- raw logs, machine-readable output, digest и независимый reviewer.
Сопоставляйте import time и raw graph size с полным путём до полезного ответа:
- parser/import completeness;
- correctness и completeness на размеченных фактах;
- query latency после import;
- end-to-end время;
- unresolved calls и unsupported constructs;
- память, хранение и incremental update;
- operator effort, deployment, security и upgrade/rollback.
Каждый инструмент запускается в чистом подходящем ему workspace. Failed import, частичный граф и ручная настройка остаются частью результата. Если манифест отсутствует, допустимы только проверенные выводы об архитектуре и процессе.
Источники истины GoCPG
gocpg/pkg/frontendиgocpg/docs/frontend-guide.md— языковые адаптеры и извлекаемые факты.gocpg/pkg/cpg/schema— узлы, рёбра и свойства.gocpg/pkg/passes/pipeline.go— текущий порядок семантических проходов.gocpg/pkg/passes/types/type_recovery*.goиgocpg/pkg/typestubs— восстановление типов и Type Knowledge Base.gocpg/pkg/passes/controlflow,dataflow,callgraph— CFG/CDG, data-flow и call graph.gocpg/pkg/architectureиgocpg/docs/architecture/v1/— структурный анализ, governance и форматы результатов.gocpg/pkg/storage/duckdbиgocpg/api/proto/gocpg/v1— хранение и типизированная интеграция.