Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 667 + PEP 709 segfaults from accessing closure variables bound by any inlined comprehensions #128396

Open
bswck opened this issue Jan 1, 2025 · 1 comment
Assignees
Labels
3.13 bugs and security fixes 3.14 new features, bugs and security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error type-crash A hard crash of the interpreter, possibly with a core dump

Comments

@bswck
Copy link
Contributor

bswck commented Jan 1, 2025

Bug report

Bug description:

The segfault described here is related to PEP-667 and PEP-709.

I think I experienced a couple of similar but not same segfaults, and this here is just one of a few of them. I might be wrong though.
I'd like to start working on fixing this in order to possibly find more unhappy paths.

Reproduction

Here are a bunch of reproductions:

def f():
    lambda: k
    k = [k for k in [0] if locals()]

f()
def f():
    lambda: k
    k = [sys._getframe(0).f_locals["k"] for k in [0]]

f()
def f():
    lambda: k
    k = [eval("") for k in [0]]

f()

@alexmojaki also found

def f():
    lambda: k
    k = 1
    [locals() for k in [0]]

f()
def f():
    lambda: k
    k: int
    [locals() for k in [0]]

f()

This one shows the problem the best:

def fetch_locals_proxy():
    # anything that fetches PEP 667 FrameLocalsProxy contents
    # causes the crash
    sys._getframe(1).f_locals.items()

def f():
    lambda: k
    k = 1
    [fetch_locals_proxy() for k in [0]]

f()

Traceback

Fatal Python error: Segmentation fault

Current thread 0x00007fd059596740 (most recent call first):
  File "/tmp/repro/dump-1.py", line 9 in fetch_locals_proxy
  File "/tmp/repro/dump-1.py", line 14 in f
  File "/tmp/repro/dump-1.py", line 17 in <module>
Segmentation fault (core dumped)

Conclusions

  1. There must be a statement that allows to create a closure for a variable, for instance k, in a scope ("target scope"). It's either k = [...] or k: int in the repros.

  2. There must be an inlined comprehension that binds k as target (e.g. k = 1; [x for x in [0] if locals()] doesn't cause segfault).

  3. After binding the variable during the evaluation of the inlined comprehension, something must fetch the contents of the PEP 667 proxy of the target scope.

  4. The target scope must be any optimized scope (not confirmed for generator expressions), since only those involve PEP 667 FrameLocalsProxy. That's the reason why the crash doesn't happen at a module/class level:

    class Spam:
        lambda: k
        k = [k for k in [0] if locals()]

    or on Python <3.13, before PEP 667 had been introduced.

  5. The iterable traversed in the comprehension isn't special (the crash doesn't happen for certain objects, e.g. [None]1, (...,), but happens for others, e.g. [0], range(1)). Not confirmed for every platform.

Thanks @Eclips4 for hints to finding the best reproduction. Thanks @trag1c for reporting the problem after accidentally running into it in a real-life use case.

I'm delighted to accept any help or guidance (cc @JelleZijlstra @carljm), as I want to author the patch to fix this issue and learn something in the process. Thanks!

CPython versions tested on:

3.13, 3.14, CPython main branch

Operating systems tested on:

Linux

Footnotes

  1. Const lists (from displays) are compiled into tuples under the hood before the loop begins, so [None] here is equivalent to (None,).

@bswck bswck added the type-bug An unexpected behavior, bug, or error label Jan 1, 2025
@Eclips4 Eclips4 added type-crash A hard crash of the interpreter, possibly with a core dump 3.13 bugs and security fixes 3.14 new features, bugs and security fixes labels Jan 1, 2025
@picnixz picnixz added the interpreter-core (Objects, Python, Grammar, and Parser dirs) label Jan 1, 2025
@ZeroIntensity
Copy link
Member

On a debug build, an assertion fails for all three reproducers:

python: Objects/frameobject.c:40: framelocalsproxy_getval: Assertion `PyCell_Check(value)' failed.

@bswck bswck changed the title PEP 667 + PEP 709 segfault of access to local variables in a closure variable with an inlined comprehension shadowing itself PEP 667 + PEP 709 segfaults from accessing closure variables bound by any inlined comprehensions Jan 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.13 bugs and security fixes 3.14 new features, bugs and security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error type-crash A hard crash of the interpreter, possibly with a core dump
Projects
None yet
Development

No branches or pull requests

4 participants