GoCPG vs Joern: Сравнительный анализ¶
Дата: 2026-02-25
Joern: 4.0.426 (flatgraph)
GoCPG: 0.1.0 (DuckDB)
Кодовые базы: GoCPG (Go, 215 файлов), CodeGraph (Python, 1183 файла)
Резюме¶
GoCPG в 3.9x быстрее Joern при генерации CPG и создаёт более богатые графы с дополнительными типами рёбер (DDG, PDG, eval_type, binds_to). Joern генерирует в 2.6x больше CDG-рёбер из-за моделирования вызовов функций как точек ветвления (пути исключений). Оба инструмента используют идентичные базовые алгоритмы (доминаторы Cooper-Harvey-Kennedy, CDG по Ferrante-Ottenstein-Warren). Ключевое архитектурное различие: GoCPG — нативный Go-бинарник с записью напрямую в DuckDB, Joern — JVM/Scala-система с in-memory flatgraph.
1. Производительность¶
Парсинг + полный конвейер анализа¶
| Кодовая база |
GoCPG |
Joern |
Ускорение |
| GoCPG (Go, 215 файлов) |
13.1с |
51.8с |
3.9x |
| CodeGraph (Python, 1183 файла) |
85.5с |
97.7с |
1.1x |
Время по пассам (Go)¶
| Фаза |
GoCPG (параллельный DAG) |
Joern (последовательный) |
| CFG |
в рамках 4.57с |
638мс |
| Доминаторы |
в рамках 4.57с |
615мс |
| CDG |
в рамках 4.57с |
129мс |
| Reaching Defs |
в рамках 4.57с |
2,827мс |
| Граф вызовов |
в рамках 4.57с |
74мс |
| Все пассы |
4.57с (30 пассов, параллельно) |
~6.9с (последовательно) |
Преимущество GoCPG: конвейер на основе DAG выполняет все 30 пассов параллельно с разрешением зависимостей. Joern выполняет пассы последовательно.
Память и хранение¶
| Метрика |
GoCPG |
Joern |
| Формат вывода |
DuckDB (SQL-запросы) |
flatgraph (проприетарный бинарный формат) |
| Среда выполнения |
Нативный бинарник (без JVM) |
JVM + Scala |
| Инкрементальные обновления |
Git diff, отслеживание веток |
Не поддерживается |
2. Сравнение узлов¶
Go кодовая база (исходники GoCPG, 215 файлов)¶
| Тип узла |
GoCPG |
Joern |
Разница |
Пояснение |
| method |
10,549 |
5,762 |
+83% |
GoCPG создаёт внешние заглушки методов |
| call |
83,908 |
95,061 |
-12% |
Joern инлайнит операторы как вызовы |
| file |
430 |
375 |
+15% |
GoCPG включает testdata |
| type_decl |
2,060 |
879 |
+134% |
GoCPG резолвит интерфейсные типы |
| identifier |
79,217 |
87,603 |
-10% |
|
| literal |
24,537 |
31,874 |
-23% |
Joern разбивает составные литералы |
| local |
11,200 |
12,897 |
-13% |
|
| param |
14,730 |
10,333 |
+43% |
GoCPG включает method_parameter_out |
| return |
5,262 |
4,620 |
+14% |
|
| block |
37,449 |
28,449 |
+32% |
|
| control_structure |
11,368 |
13,332 |
-15% |
|
| member |
3,169 |
3,108 |
+2% |
|
| comment |
12,752 |
0 |
— |
Joern отбрасывает все комментарии |
| method_return |
9,303 |
5,762 |
+61% |
|
| type |
2,732 |
0 |
— |
Joern не сохраняет TYPE-узлы для Go |
| namespace |
216 |
49 |
+341% |
GoCPG: один на пакетный путь |
| method_ref |
191 |
180 |
+6% |
|
| annotation |
237 |
0 |
— |
Joern игнорирует Go build tags |
| ИТОГО |
349,710 |
332,652 |
+5% |
|
Уникальные узлы GoCPG (отсутствуют в Joern):
- finding (1,199) — результаты анализа шаблонов
- binding (6,253) — привязки тип-метод
- field_identifier (19,876) — доступ к полям структур
- modifier (670) — модификаторы видимости
- import (1,097) — объявления импортов
- method_parameter_out (8,900) — выходные параметры
- type_ref (2,137) — ссылки на типы
Python кодовая база (CodeGraph, 1183 файла)¶
| Тип узла |
GoCPG |
Joern |
Разница |
Пояснение |
| method |
200,790 |
61,700 |
+225% |
GoCPG создаёт заглушки для всех внешних методов |
| call |
1,431,271 |
661,422 |
+116% |
GoCPG моделирует декораторы/comprehensions как вызовы |
| file |
4,515 |
1,540 |
+193% |
GoCPG включает init.py, конфиги, тесты |
| type_decl |
92,998 |
56,569 |
+64% |
|
| identifier |
1,286,815 |
612,328 |
+110% |
|
| literal |
615,354 |
255,001 |
+141% |
GoCPG извлекает части f-строк |
| local |
428,845 |
218,693 |
+96% |
|
| param |
292,805 |
133,839 |
+119% |
|
| block |
390,614 |
143,495 |
+172% |
|
| comment |
105,730 |
0 |
— |
Joern отбрасывает все комментарии |
| ИТОГО |
6,474,877 |
2,894,558 |
+124% |
|
3. Сравнение рёбер¶
Go кодовая база¶
| Тип ребра |
GoCPG |
Joern |
Разница |
Пояснение |
| cfg |
270,587 |
268,470 |
+0.8% |
Почти идентично |
| cdg |
59,123 |
153,152 |
-61% |
См. раздел 4 |
| dominate |
192,710 |
253,562 |
-24% |
|
| post_dominate |
259,891 |
251,657 |
+3% |
|
| reaching_def |
821,197 |
671,111 |
+22% |
GoCPG: межпроцедурный анализ |
| call |
90,844 |
95,426 |
-5% |
|
| contains |
305,724 |
285,865 |
+7% |
|
| argument |
170,722 |
188,583 |
-9% |
|
| ast |
195,808 |
332,152 |
-41% |
Joern материализует полное AST |
| ref |
92,320 |
66,165 |
+40% |
GoCPG резолвит больше ссылок |
| condition |
11,039 |
10,345 |
+7% |
|
| receiver |
13,857 |
16,705 |
-17% |
|
| source_file |
335,713 |
5,429 |
+6,084% |
GoCPG связывает все узлы с исходным файлом |
| parameter_link |
8,731 |
10,333 |
-16% |
|
| ddg |
243,723 |
— |
— |
Только GoCPG: граф зависимостей данных |
| pdg |
722,704 |
— |
— |
Только GoCPG: граф программных зависимостей |
| eval_type |
277,592 |
— |
— |
Только GoCPG: типизация выражений |
| binds_to |
50,739 |
— |
— |
Только GoCPG: привязки параметров типов |
| inherits_from |
2,156 |
— |
— |
Только GoCPG для Go |
| capture |
383 |
— |
— |
Только GoCPG: захват замыканий |
| ИТОГО |
4,132,053 |
2,608,955 |
+58% |
|
Python кодовая база¶
| Тип ребра |
GoCPG |
Joern |
Разница |
| cfg |
4,170,935 |
1,889,935 |
+121% |
| cdg |
1,786,888 |
668,084 |
+167% |
| reaching_def |
8,200,984 |
4,205,949 |
+95% |
| call |
1,733,711 |
15,906,040 |
-89% |
| ast |
5,851,652 |
2,678,327 |
+118% |
| ddg |
1,927,830 |
— |
Только GoCPG |
| pdg |
7,716,042 |
— |
Только GoCPG |
| eval_type |
4,191,411 |
2,160,473 |
+94% |
| ИТОГО |
65,874,890 |
35,928,055 |
+83% |
Примечание: 15.9M call-рёбер Joern для Python — результат NaiveCallLinker, который агрессивно связывает все вызовы по имени (~10x ложных срабатываний).
4. Анализ расхождения CDG: 59K vs 153K¶
Ключевая находка¶
При почти идентичном числе CFG-рёбер (270K vs 268K), Joern создаёт в 14.2x больше ветвящихся узлов:
| Метрика CFG |
GoCPG |
Joern |
Соотношение |
| Линейные узлы (1 преемник) |
268,401 |
239,514 |
|
| Ветвящиеся узлы (2+ преемников) |
970 |
13,734 |
14.2x |
| — из них CALL |
~841 |
13,031 |
15.5x |
| — из них IDENTIFIER |
~129 |
703 |
5.4x |
Причина¶
Joern моделирует каждый вызов функции как точку ветвления — вызов может вернуть результат нормально или выбросить исключение (два пути). В Go, где ошибки обрабатываются явными возвратами (if err != nil), а не исключениями, это создаёт 13,031 ложное ветвление.
Цепочка влияния¶
Больше ветвящихся узлов → Другие деревья постдоминаторов →
Большие постдоминаторные фронтиры → Больше CDG-рёбер
GoCPG: 970 ветвящихся → 59K CDG
Joern: 13,734 ветвящихся → 153K CDG
Корректность для Go¶
Для Go CDG GoCPG точнее: Go не использует try/catch исключения. Моделирование путей исключений Joern создаёт ложные зависимости управления, которых нет в реальном потоке.
Алгоритмы идентичны¶
| Шаг |
Joern |
GoCPG |
| Доминаторы |
Cooper-Harvey-Kennedy |
Cooper-Harvey-Kennedy |
| Постдоминаторы |
На обратном CFG |
На обратном CFG |
| CDG |
Постдоминаторный фронтир |
IPDOM chain walk |
5. Архитектурное сравнение¶
Проектирование конвейера¶
| Аспект |
GoCPG |
Joern |
| Язык |
Go (нативный бинарник) |
Scala (JVM) |
| Планирование пассов |
DAG с топосортировкой, параллельно |
Последовательный список |
| Хранилище |
DuckDB (SQL-запросы) |
flatgraph (проприетарный формат) |
| Инкрементальные обновления |
Git diff + отслеживание веток |
Не поддерживается |
Конвейер анализа¶
| Фаза |
GoCPG (30 пассов) |
Joern (~25 пассов) |
| Парсинг |
Frontend → DiffGraph → CPGGraph |
Frontend → flatgraph |
| Типы |
TypeNode, TypeDecl, Inheritance, TypeRecovery |
TypeDeclStub, MethodStub, TypeEval |
| Поток управления |
CFG, Dominator, PostDominator, CDG |
CfgCreation, CfgDominator, CdgPass |
| Граф вызовов |
CallGraph, CallResolution, ImportResolver |
StaticCallLinker, DynamicCallLinker |
| Поток данных |
Ref, AliasAnalysis, ReachingDef, InterproceduralReachingDef |
ReachingDefPass |
| Комбинированные |
PDG (CDG + данные), DDG |
(не вычисляется) |
| Обогащение |
Metrics, Comments, Findings, PatternMatch |
ContainsEdge |
| Домен |
Annotation, Tags, UseCasePropagation, VCS |
(нет) |
Уникальные возможности GoCPG¶
- DDG/PDG — рёбра зависимостей данных и программных зависимостей
- AliasAnalysisPass — внутрипроцедурный анализ указателей (
p = &x → pts[p]={x}, копирование указателей, разрешение p->field → obj.field)
- Межпроцедурный Reaching Def — анализ потока данных между функциями + распространение побочных эффектов глобальных переменных
- Тернарные косвенные вызовы —
(cond ? f1 : f2)(args) создаёт отдельные CallNode для каждой ветви (C/C++)
- PatternMatchPass — структурный поиск шаблонов в конвейере
- FindingGenerationPass — находки качества/безопасности как узлы CPG
- DomainAnnotationPass — доменная разметка (PostgreSQL, Django и др.)
- VCSTagPass — теги автора, частоты изменений из git
- Watch mode — наблюдение за файловой системой с live-обновлением CPG
- Инкрементальные обновления — на основе git diff с отслеживанием веток и поддержкой слияний
- DuckDB — SQL-запросы без JVM
Уникальные возможности Joern¶
- Интерактивный REPL — Scala-консоль для ad-hoc запросов
- 13 модулей анализа (vs 11 у GoCPG) — Ruby, Swift
- GHIDRA — анализ бинарников
- NaiveCallLinker — агрессивное связывание вызовов по имени
- PythonTypeRecovery — итеративный вывод типов (2 прохода)
6. Сравнение потоков данных¶
Reaching Definitions¶
| Аспект |
GoCPG |
Joern |
| Гранулярность |
Внутрипроцедурный + межпроцедурный |
Только внутрипроцедурный |
| Анализ указателей |
Анализ points-to для цепочек разыменования |
Отсутствует (Joern #5580, #5668) |
| Глобальные побочные эффекты |
Межпроцедурное распространение записей в глобальные переменные |
Отсутствует (Joern #5581) |
| Семантика стандартной библиотеки |
YAML-конфигурации для 11 языков |
Жёстко закодировано в Scala |
| Тайм-аут |
15с на метод |
Настраиваемый max-num-def |
| Рёбра (Go) |
821,197 |
671,111 (+22%) |
| Рёбра (Python) |
8,200,984 |
4,205,949 (+95%) |
Граф вызовов¶
| Аспект |
GoCPG |
Joern |
| Стратегия |
FQN с 4-уровневым откатом |
Static + Dynamic + Naive |
| Go (рёбра) |
90,844 |
95,426 |
| Python (рёбра) |
1,733,711 |
15,906,040 (~10x ложных срабатываний) |
7. Паритет по Python (с коррекцией дупликации)¶
Ошибка: дупликация путей к файлам (~2x раздувание)¶
GoCPG имеет ошибку нормализации путей: каждый исходный файл сохраняется с абсолютным (D:\work\codegraph\src\...) и относительным (..\src\...) путём, создавая ~2x FILE-узлов и дублируя все содержащиеся узлы/рёбра.
| Метрика |
Значение |
| Файлов с абсолютным путём |
2,345 |
| Файлов с относительным путём |
2,170 |
| Пересекающихся (один файл, два пути) |
2,336 |
| Уникальных файлов |
2,345 |
| Файлов в Joern |
1,540 |
Причина: orchestrator.go:280 использует абсолютные пути из filepath.WalkDir(). Резолвинг импортов/типов в Python-модуль анализае создаёт записи с относительными путями. CPGGraph.filesByName использует путь как ключ — оба варианта сосуществуют. full_name метода включает путь к файлу (filepath:ClassName.method), поэтому дедупликация по full_name не работает.
Сравнение узлов (дедупликация)¶
Используя только абсолютные пути как истинные дедуплицированные значения:
| Тип узла |
GoCPG (дедупл.) |
Joern |
Разница |
Пояснение |
| Внутренние методы |
42,923 |
47,749 |
-10% |
Близкий паритет |
| Внешние заглушки |
117,118 |
13,951 |
+740% |
GoCPG агрессивнее создаёт заглушки |
| call |
740,272 |
661,422 |
+12% |
GoCPG: декораторы, comprehensions как вызовы |
| identifier |
664,721 |
612,328 |
+9% |
Близкий паритет |
| literal |
317,917 |
255,001 |
+25% |
GoCPG: части f-строк |
| type_decl |
47,789 |
56,569 |
-16% |
Joern: PythonTypeRecovery добавляет больше |
| comment |
~52,865 |
0 |
— |
Joern отбрасывает комментарии |
Сравнение рёбер (оценка /2)¶
| Тип ребра |
GoCPG (сырое) |
GoCPG /2 (оценка) |
Joern |
Разница |
| cfg |
4,170,935 |
~2,085K |
1,890K |
+10% |
| cdg |
1,786,888 |
~893K |
668K |
+34% |
| reaching_def |
8,200,984 |
~4,100K |
4,206K |
-3% |
| call |
1,733,711 |
~867K |
15,906K |
-95% |
| ast |
5,851,652 |
~2,926K |
2,678K |
+9% |
| ddg |
1,927,830 |
~964K |
— |
Только GoCPG |
| pdg |
7,716,042 |
~3,858K |
— |
Только GoCPG |
Примечание: 15.9M call-рёбер Joern — результат NaiveCallLinker (~18x ложных срабатываний). FQN-резолвинг GoCPG значительно точнее.
Вердикт по Python¶
Паритет достигнут на ключевых метриках после коррекции дупликации:
- Внутренние методы: -10% — незначительное расхождение в генерации синтетических методов
- CFG: +10%, Reaching defs: -3%, AST: +9% — всё близко к паритету
- CDG: +34% — ожидаемо: GoCPG правильно моделирует ветвление исключений Python
- Граф вызовов: GoCPG в 18x точнее NaiveCallLinker Joern
- Эксклюзивно: DDG, PDG, eval_type, binds_to — только GoCPG
8. Покрытие языков¶
| Язык |
GoCPG |
Joern |
| C/C++ |
Да |
Да |
| Go |
Да (нативный go/parser) |
Да |
| Python |
Да |
Да |
| JavaScript/TypeScript |
Да |
Да |
| Java |
Да |
Да |
| Kotlin |
Да |
Да |
| C# |
Да |
Да |
| PHP |
Да |
Да |
| 1C:Предприятие |
Да |
Нет |
| Ruby |
Нет |
Да |
| Swift |
Нет |
Да |
| Бинарники (GHIDRA) |
Нет |
Да |
9. Итоговая таблица¶
| Измерение |
GoCPG |
Joern |
Лидер |
| Скорость парсинга |
13с |
52с |
GoCPG (3.9x) |
| Без JVM |
Да |
Нет |
GoCPG |
| SQL-запросы к результату |
Да |
Нет |
GoCPG |
| Типы узлов |
22 |
16 |
GoCPG |
| Типы рёбер |
22 (DDG, PDG) |
14 |
GoCPG |
| CDG для Go |
59K (точный) |
153K (пути исключений) |
GoCPG |
| Паритет Python |
-10% методов, +10% CFG |
Базовый |
Ничья (паритет) |
| Граф вызовов Python |
867K (точный) |
15.9M (~18x FP) |
GoCPG |
| Глубина анализа данных |
Межпроцедурный |
Внутрипроцедурный |
GoCPG |
| Инкрементальные обновления |
Git-based |
Нет |
GoCPG |
| Поиск шаблонов |
YAML + tree-sitter |
Scala DSL |
Ничья |
| Интерактивный REPL |
Нет |
Да |
Joern |
| Число языков |
11 |
13 + GHIDRA |
Joern |
| Экосистема |
Проприетарный |
Open source |
Joern |
Создано: 2026-02-25 | GoCPG v0.1.0 vs Joern v4.0.426