A Code Property Graph (CPG) is a unified program model that connects syntax to control flow, calls, types, data dependencies, and source locations. CodeGraph uses these relationships for defect detection, architecture enforcement, and audit metrics.
This article answers two questions:
- how the current GoCPG pipeline builds and enriches the graph;
- how GoCPG and Joern differ in graph storage, queries, and the use of analysis results.
The formulas that turn graph findings and metrics into Q1–Q12 and the Health Score are documented in the Dashboard methodology.
The role of GoCPG in CodeGraph
GoCPG builds and stores the project CPG. A language frontend creates the base nodes and relationships; ordered passes add semantic layers. The result is persisted in DuckDB, and CodeGraph consumes typed results through service contracts. Analyzers use a shared project model, so a recovered call, an architecture dependency, and an audit metric refer to the same source entities.
The selected profile determines pipeline composition. Domain annotations, version-control history (VCS enrichment), and profile-specific analyses can extend the standard passes.
1. From source code to a base graph
Frontend selection
GoCPG builds code graphs for C, C++, Go, Python, JavaScript, TypeScript, Java, Kotlin, C#, PHP, and 1C. A dedicated frontend for each language extracts the program structure and the relationships between its elements.
A frontend extracts facts that are directly visible in source:
- files, namespaces/modules, types, methods, parameters, and locals;
- calls, literals, identifiers, blocks, and control structures;
- imports, annotations, comments, and source locations;
- AST, argument, receiver, and source-file relationships.
A frontend extracts the syntax of a dynamic call. Type recovery and import resolution then refine its target method.
Materialization
A full build follows this path:
- GoCPG selects files using the language and include/exclude rules.
- The frontend parses source code and extracts declarations, calls, and control structures.
- Subsequent stages add types, call targets, and data-flow relationships.
- GoCPG saves the graph in DuckDB for queries and further analysis.
GoCPG records parse failures with their file paths. The team uses these details to identify source files that need another import.
Frontends accumulate changes in DiffGraph; CPGGraph represents the graph
for subsequent stages. The pipeline (gocpg/pkg/passes/pipeline.go)
defines the pass composition and order. Architecture analysis contracts live in
gocpg/pkg/architecture.
2. The semantic pipeline
Ordering matters: a call graph cannot reliably precede import and type recovery, and a PDG cannot precede control- and data-flow layers.
| Stage | Added facts | Primary use |
|---|---|---|
| Base topology | metadata, files, namespaces, type nodes/declarations, inheritance, parameter links | Stable entity and type identity. |
| Type recovery | identifier and call types, plus origin/confidence for call nodes | Better FQNs and call targets in dynamic languages. |
| Control flow | CFG, dominators, post-dominators, CDG | Reachability, branching, and control dependence. |
| Imports and calls | import maps, Call FQN propagation, call resolution, dynamic dispatch, FFI edges | Cross-file and cross-language call relationships. |
| Data flow | references, alias analysis, reaching definitions, interprocedural propagation | Value origin, side effects, and paths across methods. |
| Risk analyses | uninitialized variables, ranges, resource leaks, null dereference, concurrency, taint, integer/buffer overflow | Findings tied to graph paths and source. |
| Program dependencies | EVAL_TYPE, return type propagation, PDG, and DDG | Combined control- and data-dependency analysis. |
| Object model | bindings, vtables, dynamic call linking | Interface, virtual-dispatch, and constructor-to-initializer relationships. |
| Enrichment | method metrics, comments, containment, findings, structural pattern matching | Audit metrics, pattern search, and explainable results. |
| Optional layers | domain annotations, use-case propagation, VCS tags | Domain context, entry points, authorship, and change frequency. |
Every pass declares dependencies. Passes that implement IncrementalPass also
report incremental support and process selected files. The scheduler runs
independent branches of the dependency graph (DAG) concurrently and preserves
the order of dependent stages.
3. Type recovery for Python and JavaScript
In a compiled project, a declaration or toolchain often supplies a type. In Python and JavaScript, an identifier’s type may exist only at runtime. GoCPG collects static evidence, applies it iteratively, and records origin and confidence where the schema supports them.
Sources of type facts
The first, intraprocedural stage uses:
- annotations and already extracted type declarations;
- assignments such as
x = ClassName(...); - constructor calls;
- locals and same-name identifiers;
self/thisfields and member assignments;- Python
isinstanceguards; - builtin and method return types from a language-specific Type Knowledge Base.
Python and JavaScript have separate builtin/type-stub sets. These sets supply
return types for known functions and methods. For metaprogramming, monkey
patching, eval, dynamic imports, and object-shape changes, the pass retains
available static facts and records unresolved types in the analysis result.
Iteration and convergence
Later iterations propagate:
- a resolved method’s return type to its call site;
- a call result type into assignment targets;
- types from return statements and properties;
- dynamic-dispatch facts from the Type Knowledge Base;
- a temporary
<returnValue>type when the call target is known but its exact return type is not.
Updates are collected concurrently and then applied deterministically. The default configuration preserves three base iterations, while the adaptive loop may continue for up to ten. After the third iteration it stops when the ratio of newly applied updates to the previous iteration is below 1%, or earlier when there are no updates.
Call nodes persist type_origin and type_confidence. Example origins
include builtin, tkb, constructor, propagated, and inferred.
Constructor and builtin facts carry higher confidence; a temporary
<returnValue> carries less. Downstream analysis can distinguish a direct
fact from an approximation.
How recovered types improve the call graph
After type recovery, a dedicated pass rewrites a call’s
method_full_name from the recovered receiver type and import map. Call
resolution then links the call node to the best available method node, and the
dynamic linker adds virtual-dispatch and constructor-to-__init__
relationships.
The graph shows relationships recovered from source code. For each call, analysis records a resolved target or marks the target as requiring refinement. Origin and confidence help explain the result; completeness determines whether the available facts support the selected check.
Example: object type and call target
This Python code creates the greeter variable without a type annotation:
class Greeter:
def greet(self) -> str:
return "Hello"
def say_hello():
greeter = Greeter()
return greeter.greet()
With the standard analysis profile, GoCPG uses the constructor call to identify
the object type and the -> str annotation to identify the method result type.
| Expression | Recovered type | Graph relationship |
|---|---|---|
Greeter() |
Greeter |
Creates an object of class Greeter. |
greeter.greet() |
str |
The call is linked to Greeter.greet in example.py. |
CodeGraph can follow this relationship to the target method and continue analysis across the call. When investigating the impact of a method change, the graph provides the code locations that call it.
4. Control and data flow
GoCPG materializes several connected representations:
- CFG captures possible execution order within a method;
- dominator and post-dominator trees support mandatory-path analysis;
- CDG connects a node to the condition that controls its execution;
- REF and reaching-definition edges connect a use to its value;
- alias analysis refines references that may denote the same object;
- interprocedural reaching definitions propagate facts through call sites;
- DDG materializes data dependencies;
- PDG combines data and control dependencies.
Taint, resource-leak, null-dereference, and related analyzers operate on these layers. A finding should therefore retain a message, source location, related node, and an evidence path where applicable.
Different CPG implementations can materialize different helper nodes and edges. Check accuracy against labeled expected facts in the source corpus. Node, CALL-edge, and CDG-edge counts describe the size of the resulting graph.
5. Architecture v1 structural analysis
Structural analysis classifies CPG entities into components and checks which relationships are allowed between them.
Model and rules
- Every eligible entity receives a stable component UID. Display
idis mutable;uidparticipates in identity and finding fingerprints. - Parent components group leaves but do not classify entities themselves.
- Scope, layer, type, platform, and runtime are independent dimensions. Enforcement requires exhaustive, unambiguous classification.
- Architecture rules use
spec_version: "2.0",kind: architecture, a stable rule ID, andall,any, andnotselectors. - Preset and rule references are version-pinned as
id@version.latestand implicit inheritance from a neighboring language are rejected.
Architecture v1 contains three generic templates and eleven language presets: 1C, C, C++, C#, Go, Java, JavaScript, Kotlin, PHP, Python, and TypeScript. Each language has its own capability manifest and fixture qualification. During a run, the analyzer compares the selected rules’ requirements with the project facts and produces a coverage result.
Completeness, baselines, and waivers
The capability registry declares the fact families supplied by each language
adapter. If a required capability is missing, the check returns
BLOCKED_INCOMPLETE.
An incremental run is authoritative only as incremental_complete when its
fingerprints, baseline/waiver states, and semantic digest equal the
corresponding full run. Public API, model, rule, baseline, waiver, edge, or SCC
changes expand invalidation. A partial result triggers one synchronous full
retry; only a fresh full result may replace it.
A baseline binds accepted debt to an exact rule, fingerprint version, finding fingerprint, and semantic hash. Finding suppression uses an accepted baseline record. A waiver is time-bounded, requires distinct requester and approver identities, and signs the project, repository, commit, branch, hashes, and revision. Trust keys and revocation state come from the control plane or CI trust configuration, not the analyzed repository.
Results and release gates
Canonical JSON contains the revision-bound run, completeness evidence, an outcome for every active rule, findings, coverage, and metrics. SARIF, Mermaid, and PlantUML are deterministic projections of that result and do not change fingerprints. The gRPC QueryService reads a saved run, exports and validates results, returns a compact run description, and explains a finding.
discovery and advisory profiles do not authorize a release.
enforce_new, enforce_all, and strict may return PASS or
FAIL_VIOLATIONS, but only with fresh, complete facts and sufficient
capability coverage. Missing, stale, partial, failed, or zero-denominator
evidence yields BLOCKED_INCOMPLETE.
These results primarily inform audit sections Q2, Q4, Q6, and Q11; they do not replace other audit scenarios.
6. GoCPG and Joern: architecture comparison
Joern also builds CPGs through language frontends, adds semantic layers through passes, and exposes a Scala-based CPG Query Language. Its official documentation describes an interactive shell, scripts, server mode, extensible passes, and plugins. Frontends and overlays vary by release and must be pinned for an evaluation.
| Dimension | GoCPG in CodeGraph | Joern |
|---|---|---|
| Primary workflow | Project import, automated analyzers, Dashboard, CLI/MCP/REST, and governed evidence. | Interactive and query-driven vulnerability research, scripts, scan, and server workflows. |
| Storage | Project-scoped DuckDB, atomic writes, and typed service contracts. | A custom in-memory graph database and Joern project model. |
| Query surface | Go services, gRPC, and controlled project interfaces; SQL remains inside the storage boundary. | Scala-based CPGQL, REPL, scripts, and an HTTP server. |
| Extension model | Language frontends, dependency-aware Go passes, pattern/architecture rules, domain and VCS layers. | Frontends, CPG passes, query extensions, and JVM plugins. |
| Dynamic types | Iterative shared pass, language-specific type stubs, origin/confidence, and FQN propagation. | Behavior depends on the pinned frontend and overlays. |
| Structural enforcement | Versioned model/presets, capability registry, signed governance, full/incremental equivalence, and a fail-closed gate. | Architecture queries and extensions can be built with CPGQL, passes, and plugins; the policy workflow must be evaluated separately. |
Joern fits teams that perform interactive analysis and have invested in CPGQL. GoCPG is integrated into CodeGraph’s automated lifecycle, where project-scoped persistence, reproducible findings, freshness, governance, and release evidence matter.
Official Joern sources:
Joern server mode executes queries in an interpreter. Configure authentication and separate process and network isolation when deploying it. The server guide assigns isolation of executed queries to the deployment environment.
7. Choosing pilot criteria
Start with the team’s task. For vulnerability research, compare query authoring and data-flow inspection. For recurring project checks, compare import, result persistence, repeated runs, and architecture rules in the release workflow.
| Team task | Pilot check | Useful result |
|---|---|---|
| Investigate a specific vulnerability | Find the data source, sensitive operation, and path between them. | A source-code path that the researcher can inspect. |
| Assess the impact of a method change | Recover the call target and find its callers. | Locations to check after the change. |
| Control dependencies before release | Define components and a forbidden relationship, then run the rule. | A finding with a source location and a check outcome for that revision. |
| Run recurring audits | Repeat import and analysis after changing selected files. | Comparable results from two runs and a clear cost for the repeat check. |
Select a few expected facts: a call target, a data-flow path from an input to a sensitive operation, and a forbidden dependency between components. Use these facts to inspect both tools on the same source corpus. Measure time and resource use with the protocol below.
8. Reproducible benchmark protocol
Every numerical conclusion needs an immutable benchmark manifest:
- GoCPG/CodeGraph commit, exact Joern release, and artifact digests;
- corpus commit, languages, include/exclude rules, and labeled expected facts;
- hardware, OS, runtime limits, cache state, and concurrent load;
- exact import, query, export, and cleanup commands;
- cold/warm policy, repetitions, timeouts, failures, and outlier treatment;
- wall time, CPU, peak memory, storage, and result completeness;
- raw logs, machine-readable output, digests, and an independent reviewer.
Compare more than import time and raw graph size:
- parser/import completeness;
- correctness and completeness against labeled facts;
- post-import query latency;
- end-to-end time to an actionable result;
- unresolved calls and unsupported constructs;
- memory, storage, and incremental-update behavior;
- operator effort, deployment, security, and upgrade/rollback fit.
Run each tool in a clean workspace suited to that tool. Failed imports, partial graphs, and manual tuning remain part of the result. The manifest lets the team repeat measurements and check numerical conclusions. Support architecture and workflow conclusions with the corresponding source code and documentation.
Apply GoCPG to your project
Start with architecture control setup, which takes you from project preparation to dependency checks.
- Model and rules — define components and the relationships allowed between them.
- Execution and governance — choose a profile and run the complete teaching example.
- Results and interfaces — explain a dependency finding and export the results.
- Dashboard methodology — follow how findings and metrics contribute to the project assessment.