From 25732c16fcf99798a1dce23fa0eb3bc63a5f9d48 Mon Sep 17 00:00:00 2001 From: "widehyo@gmail.com" Date: Wed, 15 Jul 2026 01:06:52 +0900 Subject: [PATCH 1/3] 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 --- Lib/test/test_csv.py | 1 - crates/stdlib/src/csv.rs | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 3e86af0f8c5..1108846f544 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -568,7 +568,6 @@ def test_roundtrip_escaped_unquoted_newlines(self): 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 cb7cecff416..9f0ea802d98 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: usize, } #[pyclass(no_attr, module = "_csv", name = "reader", traverse)] @@ -1057,6 +1059,7 @@ mod _csv { impl IterNext for Reader { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let generation = zelf.state.lock().generation; let string = raise_if_stop!(zelf.iter.next(vm)?); let string = string.downcast::().map_err(|obj| { new_csv_error( @@ -1068,10 +1071,17 @@ mod _csv { ) })?; let input = string.as_bytes(); + let mut state = zelf.state.lock(); + if state.generation != generation { + return Err(new_csv_error( + vm, + "iterator has already advanced the reader", + )); + } if input.is_empty() || input.starts_with(b"\n") { + state.generation += 1; return Ok(PyIterReturn::Return(vm.ctx.new_list(vec![]).into())); } - let mut state = zelf.state.lock(); let ReadState { buffer, output_ends, @@ -1079,6 +1089,7 @@ mod _csv { skipinitialspace, delimiter, line_num, + generation, } = &mut *state; let mut input_offset = 0; @@ -1089,6 +1100,7 @@ mod _csv { if zelf.dialect.quoting == QuoteStyle::None && zelf.dialect.escapechar.is_some() { let out = read_quote_none_record(input, zelf.dialect, field_limit, vm)?; *line_num += 1; + *generation += 1; return Ok(PyIterReturn::Return(vm.ctx.new_list(out).into())); } @@ -1176,6 +1188,7 @@ mod _csv { // out.pop(); // } *line_num += 1; + *generation += 1; Ok(PyIterReturn::Return(vm.ctx.new_list(out).into())) } } From 0d0f6fb151d0c54e8a151b1b3129cfb2ef779eea Mon Sep 17 00:00:00 2001 From: "widehyo@gmail.com" Date: Sun, 19 Jul 2026 23:23:08 +0900 Subject: [PATCH 2/3] 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 --- crates/stdlib/src/csv.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 9f0ea802d98..e6853a5bd69 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -1060,8 +1060,17 @@ mod _csv { impl IterNext for Reader { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { let generation = zelf.state.lock().generation; - let string = raise_if_stop!(zelf.iter.next(vm)?); - let string = string.downcast::().map_err(|obj| { + 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,15 +1080,7 @@ mod _csv { ) })?; let input = string.as_bytes(); - let mut state = zelf.state.lock(); - if state.generation != generation { - return Err(new_csv_error( - vm, - "iterator has already advanced the reader", - )); - } if input.is_empty() || input.starts_with(b"\n") { - state.generation += 1; return Ok(PyIterReturn::Return(vm.ctx.new_list(vec![]).into())); } let ReadState { @@ -1089,7 +1090,7 @@ mod _csv { skipinitialspace, delimiter, line_num, - generation, + generation: _, } = &mut *state; let mut input_offset = 0; @@ -1100,7 +1101,6 @@ mod _csv { if zelf.dialect.quoting == QuoteStyle::None && zelf.dialect.escapechar.is_some() { let out = read_quote_none_record(input, zelf.dialect, field_limit, vm)?; *line_num += 1; - *generation += 1; return Ok(PyIterReturn::Return(vm.ctx.new_list(out).into())); } @@ -1188,7 +1188,6 @@ mod _csv { // out.pop(); // } *line_num += 1; - *generation += 1; Ok(PyIterReturn::Return(vm.ctx.new_list(out).into())) } } From 298bb36678fe58a077be25cf3d1eef14873be3ca Mon Sep 17 00:00:00 2001 From: "widehyo@gmail.com" Date: Sun, 19 Jul 2026 23:42:37 +0900 Subject: [PATCH 3/3] 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. --- crates/stdlib/src/csv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index e6853a5bd69..f4ccda55412 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -962,7 +962,7 @@ mod _csv { skipinitialspace: bool, delimiter: u8, line_num: u64, - generation: usize, + generation: u64, } #[pyclass(no_attr, module = "_csv", name = "reader", traverse)]