Files
Kwak Byoung Min 3492c1cef1 Avoid callable iterator deadlock during sentinel comparison (#8040)
Constraint: Python-level sentinel equality can re-enter the same callable iterator while internal iterator state is protected by a non-reentrant lock.

Rejected: Holding the callable iterator status lock across sentinel equality | user-defined __eq__ can call next() on the same iterator and deadlock.

Confidence: high

Scope-risk: moderate

Tested: pre-commit run --all-files; cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher; cargo build --release --features sqlite; pytest -v in extra_tests; cargo run --quiet -- extra_tests/snippets/protocol_iternext.py; cargo check -p rustpython-vm; cargo clippy -p rustpython-vm --all-targets -- -D warnings; git diff --check

Not-tested: push/PR creation

Assisted-by: Codex:gpt-5.5
2026-06-11 13:38:07 +09:00

46 lines
1005 B
Python

ls = [1, 2, 3]
i = iter(ls)
assert i.__next__() == 1
assert i.__next__() == 2
assert next(i) == 3
assert next(i, "w00t") == "w00t"
s = "你好"
i = iter(s)
i.__setstate__(1)
assert i.__next__() == "好"
assert i.__reduce__()[2] == 2
# Regression test for re-entrant callable iterator sentinel equality.
# Equality can execute Python code that calls back into the same iterator.
class ReenterEq:
entered = False
it = None
def __eq__(self, other):
if not self.entered:
self.entered = True
try:
next(self.it)
except StopIteration:
pass
else:
raise AssertionError("inner next should stop at the sentinel")
return True
value = ReenterEq()
sentinel = object()
callable_it = iter(lambda: value, sentinel)
value.it = callable_it
try:
next(callable_it)
except StopIteration:
pass
else:
raise AssertionError("outer next should stop at the sentinel")
assert value.entered