From 67c5ceed2686f6a5ad4423a1df3036e51b76de53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 15:28:16 +0000 Subject: [PATCH 1/4] Initial plan From 59536007d792c98e50eb17c1002875e4511ef8af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 15:46:22 +0000 Subject: [PATCH 2/4] fix: prevent iterator length_hint deadlock Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/vm/src/builtins/iter.rs | 37 +++++++++++++++++-------- extra_tests/snippets/builtin_iter.py | 41 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 extra_tests/snippets/builtin_iter.py diff --git a/crates/vm/src/builtins/iter.rs b/crates/vm/src/builtins/iter.rs index 736303e95ee..c2748ab6f96 100644 --- a/crates/vm/src/builtins/iter.rs +++ b/crates/vm/src/builtins/iter.rs @@ -4,7 +4,7 @@ use super::{PyInt, PyTupleRef, PyType}; use crate::{ - Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, class::PyClassImpl, function::ArgCallable, object::{Traverse, TraverseFn}, @@ -197,16 +197,31 @@ impl PySequenceIterator { } #[pymethod] - fn __length_hint__(&self, vm: &VirtualMachine) -> PyObjectRef { - let internal = self.internal.lock(); - if let IterStatus::Active(obj) = &internal.status { - let seq = obj.sequence_unchecked(); - seq.length(vm) - .map(|x| PyInt::from(x).into_pyobject(vm)) - .unwrap_or_else(|_| vm.ctx.not_implemented()) - } else { - PyInt::from(0).into_pyobject(vm) - } + fn __length_hint__(&self, vm: &VirtualMachine) -> PyResult { + vm.with_recursion("in __length_hint__", || { + let obj = { + let internal = self.internal.lock(); + match &internal.status { + IterStatus::Active(obj) => Some(obj.clone()), + IterStatus::Exhausted => None, + } + }; + if let Some(obj) = obj { + let seq = obj.sequence_unchecked(); + match seq.length(vm) { + Ok(x) => Ok(PyInt::from(x).into_pyobject(vm)), + Err(err) => { + if err.fast_isinstance(vm.ctx.exceptions.recursion_error) { + Err(err) + } else { + Ok(vm.ctx.not_implemented()) + } + } + } + } else { + Ok(PyInt::from(0).into_pyobject(vm)) + } + }) } #[pymethod] diff --git a/extra_tests/snippets/builtin_iter.py b/extra_tests/snippets/builtin_iter.py new file mode 100644 index 00000000000..169ba9915e6 --- /dev/null +++ b/extra_tests/snippets/builtin_iter.py @@ -0,0 +1,41 @@ +import queue +import threading + + +def make_iterator(): + holder = {} + + class Evil: + def __getitem__(self, index): + if index == 0: + return 0 + raise IndexError + + def __len__(self): + return holder["it"].__length_hint__() + + obj = Evil() + holder["it"] = iter(obj) + return holder["it"] + + +it = make_iterator() +q = queue.Queue() + + +def run(): + try: + it.__length_hint__() + except Exception as exc: # noqa: BLE001 + q.put(exc) + else: + q.put(None) + + +t = threading.Thread(target=run, daemon=True) +t.start() +t.join(1) + +assert not t.is_alive(), "iterator.__length_hint__ deadlocked" +err = q.get_nowait() +assert isinstance(err, RecursionError) From f1be6e9f080d53662de7787fc6ea41fbb33009db Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 3 Jan 2026 13:21:18 +0900 Subject: [PATCH 3/4] fix --- crates/vm/src/builtins/iter.rs | 22 +++++++++----------- extra_tests/snippets/builtin_iter.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/builtins/iter.rs b/crates/vm/src/builtins/iter.rs index c2748ab6f96..668a2a5081e 100644 --- a/crates/vm/src/builtins/iter.rs +++ b/crates/vm/src/builtins/iter.rs @@ -4,7 +4,7 @@ use super::{PyInt, PyTupleRef, PyType}; use crate::{ - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, class::PyClassImpl, function::ArgCallable, object::{Traverse, TraverseFn}, @@ -199,24 +199,20 @@ impl PySequenceIterator { #[pymethod] fn __length_hint__(&self, vm: &VirtualMachine) -> PyResult { vm.with_recursion("in __length_hint__", || { - let obj = { + let (obj, position) = { let internal = self.internal.lock(); match &internal.status { - IterStatus::Active(obj) => Some(obj.clone()), - IterStatus::Exhausted => None, + IterStatus::Active(obj) => (Some(obj.clone()), internal.position), + IterStatus::Exhausted => (None, 0), } }; if let Some(obj) = obj { let seq = obj.sequence_unchecked(); - match seq.length(vm) { - Ok(x) => Ok(PyInt::from(x).into_pyobject(vm)), - Err(err) => { - if err.fast_isinstance(vm.ctx.exceptions.recursion_error) { - Err(err) - } else { - Ok(vm.ctx.not_implemented()) - } - } + match seq.length_opt(vm) { + Some(len) => len.map(|len| { + PyInt::from(len.saturating_sub(position)).into_pyobject(vm) + }), + None => Ok(vm.ctx.not_implemented()), } } else { Ok(PyInt::from(0).into_pyobject(vm)) diff --git a/extra_tests/snippets/builtin_iter.py b/extra_tests/snippets/builtin_iter.py index 169ba9915e6..02d469a47ee 100644 --- a/extra_tests/snippets/builtin_iter.py +++ b/extra_tests/snippets/builtin_iter.py @@ -39,3 +39,33 @@ def run(): assert not t.is_alive(), "iterator.__length_hint__ deadlocked" err = q.get_nowait() assert isinstance(err, RecursionError) + + +class NoLen: + def __getitem__(self, index): + if index < 3: + return index + raise IndexError + + +no_len_it = iter(NoLen()) +assert no_len_it.__length_hint__() is NotImplemented +next(no_len_it) +assert no_len_it.__length_hint__() is NotImplemented + + +class Seq: + def __init__(self): + self.items = [1, 2, 3] + + def __getitem__(self, index): + return self.items[index] + + def __len__(self): + return len(self.items) + + +seq_it = iter(Seq()) +assert seq_it.__length_hint__() == 3 +next(seq_it) +assert seq_it.__length_hint__() == 2 From f4b68ba5dc97ea610c304669a3da2b5a11ed9c62 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Jan 2026 23:48:46 +0000 Subject: [PATCH 4/4] Auto-format: cargo fmt --all --- crates/vm/src/builtins/iter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/builtins/iter.rs b/crates/vm/src/builtins/iter.rs index 668a2a5081e..e82fb6dde40 100644 --- a/crates/vm/src/builtins/iter.rs +++ b/crates/vm/src/builtins/iter.rs @@ -209,9 +209,9 @@ impl PySequenceIterator { if let Some(obj) = obj { let seq = obj.sequence_unchecked(); match seq.length_opt(vm) { - Some(len) => len.map(|len| { - PyInt::from(len.saturating_sub(position)).into_pyobject(vm) - }), + Some(len) => { + len.map(|len| PyInt::from(len.saturating_sub(position)).into_pyobject(vm)) + } None => Ok(vm.ctx.not_implemented()), } } else {