cpython icon indicating copy to clipboard operation
cpython copied to clipboard

Assertion failure at Python/generated_cases.c.h:10059 `PyObject *_PyEval_EvalFrameDefault(PyThreadState *, _PyInterpreterFrame *, int): Assertion 'STACK_LEVEL() == 0' failed`

Open YuanchengJiang opened this issue 3 weeks ago • 9 comments

Crash report

What happened?

import unittest
import sys
mon = sys.monitoring
def add_line(
    code: "types.CodeType",
    lineno: int,
) -> None:
    return mon.DISABLE
def enable():
    mon.use_tool_id(mon.COVERAGE_ID, "regrtest coverage")
    mon.register_callback(mon.COVERAGE_ID, mon.events.LINE, add_line)
    mon.set_events(mon.COVERAGE_ID, mon.events.LINE)
enable()

class CLanguage:
    def __init__(self, filename=None):
        self.filename = filename
    def __init__(self, clang, filename='file', limited_capi=False):
        self.limited_capi = limited_capi
    def parse(self, text):
        norm = dedent(text).strip()
def _make_clinic(*, filename=bytes(range(256)), limited_capi=' '):
    clang = CLanguage(filename)
    return c

class ClinicWholeFileTest(unittest.TestCase):
    def expect_failure(self, raw, errmsg, *, filename=None, lineno=None):
        self.clinic = _make_clinic(filename='test.c')
    def test_directive_output_cant_pop(self):
        raw = '\n            /*[clinic input]\n            output pop\n            [clinic start generated code]*/\n        '
        err = "Can't 'output pop', stack is empty"
        self.expect_failure(raw, err)

if __name__ == "__main__":
    unittest.main()
python: ../Python/generated_cases.c.h:10059: PyObject *_PyEval_EvalFrameDefault(PyThreadState *, _PyInterpreterFrame *, int): Assertion `STACK_LEVEL() == 0' failed

config: --with-pydebug --enable-experimental-jit=yes --with-address-sanitizer

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

No response

Linked PRs

  • gh-142842

YuanchengJiang avatar Dec 09 '25 08:12 YuanchengJiang

one similar poc is:

import sys
import inspect
import unittest
mon = sys.monitoring

def add_line(
    code: b'\x00\xff\x80\x7f',
    lineno: int,
) -> "typing.Literal[sys.monitoring.DISABLE]":
    return mon.DISABLE
def enable():
    mon.use_tool_id(mon.COVERAGE_ID, "regrtest coverage")
    mon.register_callback(mon.COVERAGE_ID, mon.events.LINE, add_line)
    mon.set_events(mon.COVERAGE_ID, mon.events.LINE)
def disable():
    mon.free_tool_id(mon.COVERAGE_ID)
enable()

class TestSignatureDefinitions(unittest.TestCase):
    def test_thread_module_has_signatures(fusion):
        import _thread
        if hasattr(_thread, 'get_ident'):
            try:
                sig = inspect.signature(_thread.get_ident)
            except ValueError:
                self.assertEqual(len(sig.parameters), 0)
            self.assertTrue(callable(obj))

if __name__ == "__main__":
    unittest.main()

it outputs:

Stack overflow (depth = 11) at ../Python/generated_cases.c.h:8578

it seems to be related. if not, I can open up another issue

YuanchengJiang avatar Dec 09 '25 08:12 YuanchengJiang

Nice find, thank you! Does it reproduce with PYTHON_JIT=0?

devdanzin avatar Dec 09 '25 08:12 devdanzin

it doesnt reproduce in PYTHON_JIT=0. i guess it would be related to JIT

YuanchengJiang avatar Dec 09 '25 09:12 YuanchengJiang

I can reproduce the second one. Unfortunately it seems LOAD_FAST_BORROW is recorded twice in the trace. I have no clue why, it might be something to do with instrumentation/sys.monitoring, but again I don't know enough about that to know. @markshannon

Fidget-Spinner avatar Dec 09 '25 11:12 Fidget-Spinner

The tracer be abandoning the trace if it encounters an instrumented instruction. And instrumentation should invalid the trace currently being recorded as well as all existing ones.

@Fidget-Spinner is this the case?

markshannon avatar Dec 09 '25 15:12 markshannon

The tracer be abandoning the trace if it encounters an instrumented instruction. And instrumentation should invalid the trace currently being recorded as well as all existing ones.

@Fidget-Spinner is this the case?

Yes it's already doing that.

Fidget-Spinner avatar Dec 09 '25 15:12 Fidget-Spinner

We aren't abandoning the trace if we encounter an instrumented instruction.

https://github.com/python/cpython/blob/main/Python/bytecodes.c#L5608 should include opcode >= MIN_INSTRUMENTED_OPCODE

markshannon avatar Dec 11 '25 11:12 markshannon

We also need to abandon the trace if an exception is raised, and before calling any monitoring callback functions on exceptional paths.

markshannon avatar Dec 11 '25 11:12 markshannon

We also need to abandon the trace if an exception is raised, and before calling any monitoring callback functions on exceptional paths.

We already protect against this through checking the current and previous frame. This also protects against re-entrancy.

Fidget-Spinner avatar Dec 16 '25 20:12 Fidget-Spinner