Skip to content

_Py_Dealloc is an escaping call #124483

Closed
Closed

Description

@brandtbucher

Crash report

We like to pretend that _Py_Dealloc (and, by extension, Py_DECREF, Py_XDECREF, Py_CLEAR, PyStackRef_CLOSE, PyStackRef_CLEAR, etc...) isn't an escaping call in tier two. However, it's perfectly capable of invalidating the world. For example, the following code crashes when run under the JIT:

import sys

class Crashy:
    def __del__(self):
        ns = sys._getframe(1).f_locals
        if ns["i"] == 999:
            ns["i"] = None

def crashy():
    for i in range(1000):
        n = Crashy()
        i + i  # Remove guards for i.
        n = None  # Change i.
        i + i  # Crash!

crashy()

Longer term, we're hoping that removing refcounting operations in tier two will help us out here. Fortunately, the _SET_IP and _CHECK_VALIDITY instructions necessary to fix this issue aren't expensive enough to show up on benchmarks when added back (for now).

Linked PRs

Activity

  1. added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    type-crashA hard crash of the interpreter, possibly with a core dump
    3.14bugs and security fixes
    on Sep 25, 2024
  2. self-assigned this
    on Sep 25, 2024
  3. brandtbucher commented on Nov 9, 2024

    @brandtbucher
    MemberAuthor

    Doing this correctly is actually pretty tricky:

    • PyStackRef_CLOSE should be an escaping call, but that means that refs need to be closed in the order they occur on the stack, and all input refs must be closed before output refs are created. That's actually quite awkward in practice, since it's common to create outputs before inputs are destroyed.
    • Things that consume references, like _PyTuple_FromStackRefSteal, become escaping only on error. That makes it basically impossible to adjust the stack pointer correctly.

    After talking it through with @markshannon, one possible approach to making this work could be:

    • Rather than adjusting the stack level when references are consumed, overwrite their stack slot with PyStackRef_NULL. That way it doesn't matter what order things are consumed in, and the stack level doesn't need to be adjusted more than once per instruction.
    • Although we discourage it in public APIs, _PyTuple_FromStackRefSteal (and functions like it) should only steal references on success. That way, they're non-escaping.

    That should give us a good path forward here. Fewer stack adjustments is also very nice for top-of-stack caching.

  4. markshannon commented on Jan 9, 2025

    @markshannon
    Member

    Note: the above example doesn't crash anymore as we've increased the thresholds. Increasing 999 to 9999 produces a crash.

  5. markshannon commented on Jan 9, 2025

    @markshannon
    Member

    Fixing the crash is actually quite straightforward.

    All the stuff around keeping the stack consistent is potentially important for the GC, but we don't rely on it at the moment.

  6. added a commit that references this issue on Jan 13, 2025
  7. brandtbucher commented on Jan 14, 2025

    @brandtbucher
    MemberAuthor

    Leaving this open because of #128678 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

3.13only security fixes3.14bugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dump

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions