From 570258f6cbf43d542e374efa1162182a93e6337f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B4=91=ED=9A=A8?= <87641474+widehyo1@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:05:51 +0900 Subject: [PATCH] csv: reject reentrant reader advancement (#8324) * csv: reject reentrant reader advancement Snapshot the reader generation before calling the input iterator. If a nested call completes a record and advances the same reader, reject the outer call with csv.Error instead of continuing with stale parser state. Increment the generation on each successful record path, matching CPython's handling of the existing reentrant-reader regression. Enable that test by removing its expectedFailure marker. Assisted-by: Codex:gpt-5.6-sol * csv: invalidate outer reads after reentry Advance the reader generation immediately after the reentrancy check, before validating or parsing the returned input item. This invalidates the outer call even when a nested read later fails validation or parsing. Remove the per-return generation updates now that the common path advances it exactly once. Assisted-by: Codex:gpt-5.6-sol * csv: generation type conversion(usize to u64) On 32-bit targets (like WebAssembly, which RustPython supports), usize is 32-bit. Csv with over 32-bit rows can raise panic on these platform. type conversion from usize to u64. --- Lib/test/test_csv.py | 1 - crates/stdlib/src/csv.rs | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 3e86af0f8..1108846f5 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -568,7 +568,6 @@ class Test_Csv(unittest.TestCase): self.assertEqual(row, rows[i]) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: Error not raised def test_reader_reentrant_iterator(self): # gh-145105: re-entering the reader from the iterator must not crash. class ReentrantIter: diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index cb7cecff4..f4ccda554 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -411,6 +411,7 @@ mod _csv { skipinitialspace: options.get_skipinitialspace(), delimiter: options.get_delimiter(), line_num: 0, + generation: 0, }), dialect: options.result(vm)?, }) @@ -961,6 +962,7 @@ mod _csv { skipinitialspace: bool, delimiter: u8, line_num: u64, + generation: u64, } #[pyclass(no_attr, module = "_csv", name = "reader", traverse)] @@ -1057,8 +1059,18 @@ mod _csv { impl IterNext for Reader { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { - let string = raise_if_stop!(zelf.iter.next(vm)?); - let string = string.downcast::().map_err(|obj| { + let generation = zelf.state.lock().generation; + let string_obj = raise_if_stop!(zelf.iter.next(vm)?); + let mut state = zelf.state.lock(); + if state.generation != generation { + return Err(new_csv_error( + vm, + "iterator has already advanced the reader", + )); + } + state.generation += 1; + + let string = string_obj.downcast::().map_err(|obj| { new_csv_error( vm, format!( @@ -1071,7 +1083,6 @@ mod _csv { if input.is_empty() || input.starts_with(b"\n") { return Ok(PyIterReturn::Return(vm.ctx.new_list(vec![]).into())); } - let mut state = zelf.state.lock(); let ReadState { buffer, output_ends, @@ -1079,6 +1090,7 @@ mod _csv { skipinitialspace, delimiter, line_num, + generation: _, } = &mut *state; let mut input_offset = 0;