neato mode=KK model=circuit: C indexes the conductance matrix by AGSEQ (1-based) and crashes
With mode=KK and model=circuit, C Graphviz 16.1.0 crashes even on a connected triangle. circuit_model fills the nG x nG conductance matrix with Gm[i][j] where i, j are AGSEQ of the edge's endpoints. Node sequence numbers start at 1 (and on a packed component they are root-graph numbers), so the last node writes through Gm[nG], a pointer read past the row-pointer array. C dies with SIGSEGV/SIGBUS, or, when the stray pointer happens to be writable (as in fuzz case 33861, which crashes only in some runs), lays out from a corrupted matrix. The port indexes by the node's layout id (ND_id), as every other neato distance model does, and produces a drawing.
Reproducer
graph G {
layout=neato;
mode="KK";
model="circuit";
a -- b;
b -- c;
c -- a;
}
GVBINDIR=<pango-free plugins> dot -Kneato -Tsvg repro.gv # C Graphviz 16.1.0
graphviz-pure -Kneato -Tsvg repro.gv # port
C output vs port
C: exit status 139 (SIGSEGV), no output (every run on the 3-node reproducer;
2-4 of 10 runs on the fuzz case s33861, the other runs print an SVG)
port: exit status 0, SVG of the triangle
Root cause
C: lib/neatogen/circuit.c:44-50 uses AGSEQ(agtail(e))/AGSEQ(aghead(e)) as matrix indices; AGSEQ comes from agnextseq (lib/cgraph/graph.c:156-159, pre-increment, so the first node is 1). new_array (lib/neatogen/stuff.c:39-51) allocates exactly nG row pointers. Called from kkNeato (lib/neatogen/neatoinit.c:1245-1256). Crash reports for the fuzz case show EXC_BAD_ACCESS in circuit_model < neatoLayout < neato_layout. (The stress-majorization path, model=circuit without mode=KK, uses lib/neatogen/stress.c's own circuit_model on the vertex array and does not crash.)
Port: crates/graphviz-pure-layout/src/neatogen/stuff.rs:445-458 (circuit_model_kk) indexes by g.nodes[..].id, the 0-based node index within the component.
The port should not reproduce a wild write. Recommend wontfix (upstream bug: use ND_id as shortest_path and subset_model do).
Cases
1 fuzz case, seeds 33861. Each regenerates byte for byte with python3 -I scripts/fuzz-gen.py --seed N (checked for all of them).
Files
tests/fuzz-open/023-neato-circuit-kk-agseq-index-crash
C and the port
No pair of C and port outputs in this reproducer.
[no output; dot was killed by signal 11 (SIGSEGV) in circuit_model]Reproduce
graph G {layout=neato;mode="KK";model="circuit";a -- b;b -- c;c -- a;}C: GVBINDIR=<pango-free plugins> dot -Kneato -Tsvg repro.gvport: graphviz-pure -Kneato -Tsvg repro.gvr4/diff/s33861.gv seed=33861Files: c.stderr, cases.txt, cmd.txt, repro.gv, rust.svg. Source: tests/fuzz-open/023-neato-circuit-kk-agseq-index-crash.