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.
This commit is contained in:
이광효 authored and GitHub committed 2026-07-20 17:05:51 +09:00
1 parent c7d265e8dd
commit 570258f6cb
2 files changed
+15 -4

No files matched your search

-1
View File
@@ -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:
+15 -3
View File
@@ -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<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
let string = raise_if_stop!(zelf.iter.next(vm)?);
let string = string.downcast::<PyStr>().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::<PyStr>().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;