cpg_diff returns a deterministic structural edit script between two Code
Property Graphs (CPGs). The API is feature-free and leaves both graphs
unchanged.
use libcpg::{
cpg_diff, AnalysisError, CodePropertyGraph, CpgDiff, CpgDiffOptions,
};
fn compare(
old: &CodePropertyGraph,
new: &CodePropertyGraph,
) -> Result<CpgDiff, AnalysisError> {
cpg_diff(old, new, &CpgDiffOptions::default())
}The result contains:
alignment: structurally corresponding old/new node ids;alignment_cost: the concrete cost of the retained bounded GED script;addedandremoved: complete node or edge snapshots;changed: aligned node/edge substitutions with exact changed fields; andclassification: a dependence-sensitive review label.
Node and edge ids belong to their snapshot's input graph. Use alignment to
translate node identity across versions; do not compare numeric ids directly.
The label is meaningful only for represented Program Dependence Graph (PDG) evidence. Build comparable PDGs on both graphs first:
use libcpg::{
cpg_diff, AnalysisError, ChangeKind, CodePropertyGraph, CpgDiffOptions,
NodeId, PdgBuilder,
};
fn compare_function_dependence(
old: &mut CodePropertyGraph,
new: &mut CodePropertyGraph,
old_function: NodeId,
new_function: NodeId,
) -> Result<ChangeKind, AnalysisError> {
PdgBuilder::new().build(old, old_function);
PdgBuilder::new().build(new, new_function);
Ok(cpg_diff(old, new, &CpgDiffOptions::default())?.classification)
}BehaviorChanging means the script changed at least one represented control-
or data-dependence edge. Refactoring means it did not. Neither label proves
language-level behavioral equivalence or difference. Without PDGs, even an
added guard can classify as Refactoring because only AST/CFG/node evidence is
present.
use libcpg::{CpgChange, CpgDiff, CpgElement};
fn summarize(diff: &CpgDiff) -> (usize, usize) {
let changed_nodes = diff
.changed
.iter()
.filter(|edit| matches!(edit, CpgChange::Node { .. }))
.count();
let added_edges = diff
.added
.iter()
.filter(|edit| matches!(edit, CpgElement::Edge(_)))
.count();
(changed_nodes, added_edges)
}Node substitutions list any changed Kind, Range, Text, MessageKind,
LockKind, AllocationType, Properties, or AstLinks. Edge substitutions
list changed Kind and/or Label. Complete old/new snapshots make each record
auditable without looking up a graph that may later be dropped.
Parallel typed edges remain separate records. Edge ids are evidence coordinates, not equality keys, so pure edge renumbering is unchanged.
use libcpg::{cpg_diff, AnalysisError, CodePropertyGraph, CpgDiff, CpgDiffOptions};
fn bounded(
old: &CodePropertyGraph,
new: &CodePropertyGraph,
) -> Result<CpgDiff, AnalysisError> {
cpg_diff(
old,
new,
&CpgDiffOptions {
max_nodes: 256,
max_edges: 50_000,
max_edits: 25_000,
},
)
}The effective limits are the caller values clamped to hard ceilings of 512
nodes, 2,000,000 edges per input, and 2,000,000 aggregate edits. A graph or
script that exceeds a limit returns AnalysisError::BudgetExceeded. No partial
script or prefix-based classification escapes.
Alignment is cubic in the admitted node count, so select max_nodes from your
latency policy rather than automatically using the hard maximum. Apply a
separate ingress policy to total node-payload size; property/text snapshots are
complete.
With features = ["serde"], the options and complete output derive serde:
use libcpg::{cpg_diff, CodePropertyGraph, CpgDiffOptions};
fn json(old: &CodePropertyGraph, new: &CodePropertyGraph) -> serde_json::Result<Vec<u8>> {
let diff = cpg_diff(old, new, &CpgDiffOptions::default())
.expect("admitted CPG diff");
serde_json::to_vec(&diff)
}Ordering is canonical for the same logical graph pair, independent of node and edge insertion order. Persist the libcpg version with long-lived payloads; public CPG node/edge schemas can evolve between releases.
cpg_diff compares represented nodes and edges. It does not compare language,
source path, retained whole-source text, the root field, or recorded CFG
entry/exit side tables. Compare those container fields separately if they are
part of your versioning contract.
See the component contract, Theory 45, ADR-0055, and the security boundary.