dot (upstream C): sametail/samehead on a record node can put the merged port outside every field, and C then routes from uninitialised path-end boxes
With sametail (or samehead) on a record node, sameport rounds the merged port to whole points. That can put the port just outside the record's outermost field. C's record_path then finds no field and still returns the side mask without filling in any box, so beginpath/endpath keep whatever endp->boxes/boxn happened to be on the stack. C's edges then start up to 100pt away from the node (seed 1278). In seed 1153 (rankdir=RL) C's Pshortestpath fails, the edge name 86 -> name 87 is lost, and C exits 1. The port uses the node box instead and draws a normal edge from the node boundary.
Reproducer
digraph "fuzz 1278" {
"n3" [shape="record", label="{{{\N}|<p0> long field text|<p1> long field text}}"];
subgraph "cluster_0" {
"n2" [];
"n6" [];
"n4" [];
}
"n0" -> "n3" [penwidth="2.25", weight="1"];
"n2" -> "n4" [label="中文标签\rfoo_bar\rthen", dir="none"];
"n3" -> "n2":"w" [weight="1", sametail="st1"];
"n3" -> "n6" [headport="e", weight="2", sametail="st1"];
{"n1" "n6" "n0"} -> "n0":"nw";
"n4" -> "n6" [headclip="false", lhead="cluster_0"];
}
GVBINDIR=<pango-free plugins> dot -Kdot -Tsvg repro.gv # C Graphviz 16.1.0
graphviz-pure -Kdot -Tsvg repro.gv # port
C output vs port
--- c.svg
+++ rust.svg
-<path fill="none" stroke="black" d="M126,-52C126,-52 134.47,-148.23 88,-194.4 65.44,-216.81 38.89,-187.79 18.75,-212.4 17.16,-214.34 15.69,-216.82 14.65,-219.32"/>
+<path fill="none" stroke="black" d="M126,-119.5C126,-119.5 117.08,-170.99 88,-194.4 63.23,-214.34 38.89,-187.79 18.75,-212.4 17.16,-214.34 15.69,-216.82 14.65,-219.32"/>
-<path fill="none" stroke="black" d="M126,-212.4C126,-212.4 157.86,-51 90.44,-35.23"/>
-<polygon fill="black" stroke="black" points="91.08,-31.78 80.76,-34.17 90.31,-38.74 91.08,-31.78"/>
+<path fill="none" stroke="black" d="M126,-117.5C126,-117.5 122.54,-48.92 90.23,-36.06"/>
+<polygon fill="black" stroke="black" points="91.21,-32.69 80.74,-34.28 89.92,-39.57 91.21,-32.69"/>
Root cause
sameport sets prt.p = {round(x1), round(y1)} (lib/dotgen/sameport.c:149). For the record in the reproducer the boundary point is x = -109.54 relative to the centre, so the rounded port x = -110 is outside field 0 (LL.x = -109.54). record_path (lib/common/shapes.c:3793-3829) loops over the fields, matches none (BETWEEN(ls, p.x, rs) fails) and still return side. beginpath (lib/common/splines.c:542-543, and endpath at :740-741) treats the non-zero return as success and skips the endp->boxes[0] = endp->nb; endp->boxn = 1 fallback. But tend/hend in make_regular_edge are uninitialised locals (pathend_t tend, hend;, lib/dotgen/dotsplines.c:1713), so the box count and boxes are undefined behaviour. C's output is stable from run to run on one build but depends on stack contents.
The port's pboxfn (crates/graphviz-pure-layout/src/dotgen/splinegraph.rs:166-195) deliberately returns None in this case, which falls back to the node box (see the comment there). Confirmed by changing the record to shape=box: both programs then agree on both cases.
Why the port should not match: what C does depends on uninitialised stack memory, and reproducing it would mean modelling a particular C build's stack. Suggest closing as wontfix (upstream bug: record_path should return 0 when no field contains the port, or tend/hend should be zero-initialised).
Cases
2 fuzz case(s): seeds 1278, 1153. Seed 1153 (rankdir=RL, record name 86 with tailport=p0 and sametail) is the exit-status case: C prints triangulation failed, source point not in any triangle, in routesplines, Pshortestpath failed and lost name 86 name 87 edge, then exits 1. The port exits 0 and draws the edge.
Regenerate a case with python3 -I scripts/fuzz-gen.py --seed N (checked byte for byte for seed 1278).
Files
tests/fuzz-open/027-dot-sameport-record-uninit-pathend
C and the port
C onlyport onlyboth: in register
Rasterising…
C ink onlyport ink onlysame pixel, faded
Reproduce
digraph "fuzz 1278" {"n3" [shape="record", label="{{{\N}|<p0> long field text|<p1> long field text}}"];subgraph "cluster_0" {"n2" [];"n6" [];"n4" [];}"n0" -> "n3" [penwidth="2.25", weight="1"];"n2" -> "n4" [label="中文标签\rfoo_bar\rthen", dir="none"];"n3" -> "n2":"w" [weight="1", sametail="st1"];"n3" -> "n6" [headport="e", weight="2", sametail="st1"];{"n1" "n6" "n0"} -> "n0":"nw";"n4" -> "n6" [headclip="false", lhead="cluster_0"];}-Kdot -Tsvgr1/diff/s1278.gv seed=1278r1/diff/s1153.gv seed=1153Files: c.svg, cases.txt, cmd.txt, repro.gv, rust.svg. Source: tests/fuzz-open/027-dot-sameport-record-uninit-pathend.