Skip to content

fix(csv-core): NFA read_record mishandles records split across calls - #438

Open
prostomarkeloff wants to merge 1 commit into
BurntSushi:masterfrom
prostomarkeloff:fix-nfa-read-record-output-pos
Open

prostomarkeloff wants to merge 1 commit into
BurntSushi:masterfrom
prostomarkeloff:fix-nfa-read-record-output-pos

Conversation

@prostomarkeloff

Copy link
Copy Markdown

Found with find-dup-defs using its converge pass, which reports behavioural divergences between near-duplicate functions — here read_record_dfa and read_record_nfa, which seed nout differently (0 vs self.output_pos).

read_record_nfa starts nout at self.output_pos and returns the running total, while read_record_dfa starts nout at 0 (bytes written this call) and records ends[i] = self.output_pos + nout. When a record is delivered across several read_record calls — the normal streaming case where the caller passes out[outpos..] / ends[endpos..] — the NFA writes at the wrong offset and reports wrong end positions.

Single-call inputs (the existing tests) hide this because output_pos stays 0 throughout. ReaderBuilder::nfa is a #[doc(hidden)] debug option, but it's the reference the DFA is diffed against, so it should agree with the DFA.

Example, feeding ab,c then d,ef\n:

dfa = ["ab", "cd", "ef"]
nfa = ["ab", "c\0", "\0\0"]   // before this change

Fix

Bring the NFA to the DFA's convention: nout counts bytes written this call, ends[nend] = self.output_pos + nout, and output_pos accumulates (+= nout) rather than being used as the write offset.

Tests

Added a test that feeds one record in two chunks and asserts NFA output equals DFA output. cargo test -p csv-core is green.

Disclosure: found and prepared with the help of an AI-assisted tool; I have reviewed the change and the test.

read_record_nfa seeded nout with self.output_pos and returned the
accumulated total, writing into an already-offset output slice. When a
record spans multiple read_record calls the fields come out shifted
(e.g. ["ab","c\0","\0\0"] instead of ["ab","cd","ef"]). Follow the
DFA convention: nout counts bytes written this call, ends are
self.output_pos + nout, and output_pos accumulates.
@prostomarkeloff
prostomarkeloff force-pushed the fix-nfa-read-record-output-pos branch from ac7b6d6 to d5fd5cd Compare October 23, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant