diff --git a/Doc/c-api/frame.rst b/Doc/c-api/frame.rst index 4159ff6e5965fb..8d1dbb2c3fadd1 100644 --- a/Doc/c-api/frame.rst +++ b/Doc/c-api/frame.rst @@ -226,11 +226,31 @@ Unless using :pep:`523`, you will not need this. .. c:function:: PyObject* PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame); - Return a :term:`strong reference` to the code object for the frame. + Return a :term:`strong reference` to the code object for the frame. + Does not raise an exception. + + If allocation and reference count changes are not permitted (for example, + from a signal handler or a custom memory allocator), use + :c:func:`PyUnstable_InterpreterFrame_GetCodeSafe` instead. .. versionadded:: 3.12 +.. c:function:: PyObject* PyUnstable_InterpreterFrame_GetCodeSafe(struct _PyInterpreterFrame *frame); + + Return a :term:`borrowed reference` to the code object for the frame. + The reference is valid as long as the frame is alive. + + Use this instead of :c:func:`PyUnstable_InterpreterFrame_GetCode` when + allocation and reference count changes are not permitted (for example, + from a signal handler or a custom memory allocator). Does not allocate + memory, does not change any reference counts, does not acquire or release + the GIL, and does not raise an exception. Uses heuristics to detect freed + memory — not 100% reliable in the presence of concurrent deallocation. + + .. versionadded:: 3.15 + + .. c:function:: int PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame); Return the byte offset into the last executed instruction. @@ -243,3 +263,53 @@ Unless using :pep:`523`, you will not need this. Return the currently executing line number, or -1 if there is no line number. .. versionadded:: 3.12 + + +.. c:function:: int PyUnstable_InterpreterFrame_GetLineSafe(struct _PyInterpreterFrame *frame) + + Return the currently executing line number, or ``-1`` if there is no line + number or the frame is invalid. Does not raise an exception. + + Unlike :c:func:`PyUnstable_InterpreterFrame_GetLine`, validates the code + object and instruction offset before accessing the line table rather than + asserting them, making it safe to call when the frame state may be + partially torn down. + + .. versionadded:: 3.15 + + +.. c:function:: struct _PyInterpreterFrame* PyUnstable_ThreadState_GetInterpreterFrame(PyThreadState *tstate) + + Return the innermost complete interpreter frame of *tstate*, or ``NULL`` if + the thread has no complete frame or freed memory is detected. Incomplete + frames (interpreter entry trampolines and frames that have not yet begun + executing) are skipped automatically. + + Does not allocate memory, does not raise an exception, and does not acquire + or release the GIL. Safe to call from a signal handler; racy reads from + other threads are intentional. Uses heuristics to detect freed memory — + not 100% reliable in the presence of concurrent deallocation. + + To iterate over the full call stack, call + :c:func:`PyUnstable_InterpreterFrame_GetNextComplete` repeatedly on the + returned frame until it returns ``NULL``. + + .. versionadded:: 3.15 + + +.. c:function:: struct _PyInterpreterFrame* PyUnstable_InterpreterFrame_GetNextComplete(struct _PyInterpreterFrame *frame) + + Return the next (calling) complete frame, or ``NULL`` if *frame* is the + outermost complete frame or freed memory is detected. Incomplete frames are + skipped automatically. + + Does not allocate memory, does not raise an exception, and does not acquire + or release the GIL. Safe to call from a signal handler; racy reads from + other threads are intentional. Uses heuristics to detect freed memory — + not 100% reliable in the presence of concurrent deallocation. + + Unlike :c:func:`PyFrame_GetBack`, this function never allocates memory, + making it safe to call from a custom memory allocator hook without risking + re-entrant allocation. + + .. versionadded:: 3.15 diff --git a/Include/cpython/pyframe.h b/Include/cpython/pyframe.h index 51529763923ec3..93d1889cd76fd0 100644 --- a/Include/cpython/pyframe.h +++ b/Include/cpython/pyframe.h @@ -25,9 +25,20 @@ PyAPI_FUNC(PyObject*) PyFrame_GetVarString(PyFrameObject *frame, const char *nam struct _PyInterpreterFrame; /* Returns the code object of the frame (strong reference). - * Does not raise an exception. */ + * Does not raise an exception. + * If allocation and reference count changes are not permitted, use + * PyUnstable_InterpreterFrame_GetCodeSafe instead. */ PyAPI_FUNC(PyObject *) PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame); +/* Returns the code object of the frame as a borrowed reference. + * The reference is valid as long as the frame is alive. + * Use instead of PyUnstable_InterpreterFrame_GetCode when allocation and + * reference count changes are not permitted (e.g. from a signal handler or + * a custom memory allocator). Does not allocate, does not change any + * reference counts, does not acquire or release the GIL, does not raise an + * exception. Uses heuristics to detect freed memory; not 100% reliable. */ +PyAPI_FUNC(PyObject *) PyUnstable_InterpreterFrame_GetCodeSafe(struct _PyInterpreterFrame *frame); + /* Returns a byte offset into the last executed instruction. * Does not raise an exception. */ PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame); @@ -36,6 +47,36 @@ PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame * Does not raise an exception. */ PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLine(struct _PyInterpreterFrame *frame); +/* Returns the currently executing line number, or -1 if there is no line + * number or the frame is invalid. + * Unlike PyUnstable_InterpreterFrame_GetLine, validates the code object and + * instruction offset before accessing the line table rather than asserting + * them, making it safe to call when the frame state may be partially torn + * down. Does not raise an exception. */ +PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLineSafe(struct _PyInterpreterFrame *frame); + + +/* Returns the innermost complete interpreter frame of the thread state, or + * NULL if the thread has no complete frame or freed memory is detected. + * Skips over incomplete frames (interpreter entry trampolines and frames that + * have not yet begun executing) automatically. + * Does not allocate memory, does not acquire or release the GIL, does not + * raise an exception. Safe to call from signal handlers; racy reads from + * other threads are intentional and suppressed (_Py_NO_SANITIZE_THREAD). + * Uses heuristics to detect freed memory; not 100% reliable. */ +PyAPI_FUNC(struct _PyInterpreterFrame *) +PyUnstable_ThreadState_GetInterpreterFrame(PyThreadState *tstate); + +/* Returns the next (calling) complete frame, or NULL if frame is the + * outermost complete frame or freed memory is detected. + * Skips over incomplete frames automatically. + * Does not allocate memory, does not acquire or release the GIL, does not + * raise an exception. Safe to call from signal handlers; racy reads from + * other threads are intentional and suppressed (_Py_NO_SANITIZE_THREAD). + * Uses heuristics to detect freed memory; not 100% reliable. */ +PyAPI_FUNC(struct _PyInterpreterFrame *) +PyUnstable_InterpreterFrame_GetNextComplete(struct _PyInterpreterFrame *frame); + #define PyUnstable_EXECUTABLE_KIND_SKIP 0 #define PyUnstable_EXECUTABLE_KIND_PY_FUNCTION 1 #define PyUnstable_EXECUTABLE_KIND_BUILTIN_FUNCTION 3 diff --git a/Include/internal/pycore_interpframe.h b/Include/internal/pycore_interpframe.h index 28370ababc47b9..ea2d5d0d5a1d95 100644 --- a/Include/internal/pycore_interpframe.h +++ b/Include/internal/pycore_interpframe.h @@ -67,31 +67,25 @@ _PyFrame_GetBytecode(_PyInterpreterFrame *f) #endif } -// Similar to PyUnstable_InterpreterFrame_GetLasti(), but return NULL if the -// frame is invalid or freed. Used by dump_frame() in Python/traceback.c. The -// function uses heuristics to detect freed memory, it's not 100% reliable. -static inline int _Py_NO_SANITIZE_THREAD -_PyFrame_SafeGetLasti(struct _PyInterpreterFrame *f) +// Safe version of _PyFrame_GetBytecode() for racy signal-handler contexts. +// Takes an already-validated code object. Returns NULL if tlbc_index is +// out of range (GIL-disabled only). +static inline _Py_CODEUNIT * _Py_NO_SANITIZE_THREAD +_PyFrame_SafeGetBytecode(_PyInterpreterFrame *f, PyCodeObject *code) { - // Code based on _PyFrame_GetBytecode() but replace _PyFrame_GetCode() - // with _PyFrame_SafeGetCode(). - PyCodeObject *co = _PyFrame_SafeGetCode(f); - if (co == NULL) { - return -1; - } - - _Py_CODEUNIT *bytecode; #ifdef Py_GIL_DISABLED - _PyCodeArray *tlbc = _PyCode_GetTLBCArray(co); - assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size); - bytecode = (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index]; + _PyCodeArray *tlbc = _PyCode_GetTLBCArray(code); + int idx = f->tlbc_index; + if (idx < 0 || idx >= tlbc->size) { + return NULL; + } + return (_Py_CODEUNIT *)tlbc->entries[idx]; #else - bytecode = _PyCode_CODE(co); + return _PyCode_CODE(code); #endif - - return (int)(f->instr_ptr - bytecode) * sizeof(_Py_CODEUNIT); } + static inline PyFunctionObject *_PyFrame_GetFunction(_PyInterpreterFrame *f) { PyObject *func = PyStackRef_AsPyObjectBorrow(f->f_funcobj); assert(PyFunction_Check(func)); @@ -262,9 +256,18 @@ _PyFrame_IsIncomplete(_PyInterpreterFrame *frame) if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) { return true; } - return frame->owner != FRAME_OWNED_BY_GENERATOR && - frame->instr_ptr < _PyFrame_GetBytecode(frame) + - _PyFrame_GetCode(frame)->_co_firsttraceable; + if (frame->owner == FRAME_OWNED_BY_GENERATOR) { + return false; + } + PyCodeObject *code = _PyFrame_SafeGetCode(frame); + if (code == NULL) { + return true; + } + _Py_CODEUNIT *bytecode = _PyFrame_SafeGetBytecode(frame, code); + if (bytecode == NULL) { + return true; + } + return frame->instr_ptr < bytecode + code->_co_firsttraceable; } static inline _PyInterpreterFrame * diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 4c16bbd4cb0acf..3e1c71bc5effb4 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2789,6 +2789,86 @@ def test_line(self): firstline = self.func.__code__.co_firstlineno self.assertEqual(line, firstline + 2) + def test_tstate_getframe_is_current(self): + # PyUnstable_ThreadState_GetInterpreterFrame must return the same + # PyFrameObject as sys._getframe(0) when called at the same level. + frame_from_c = _testinternalcapi.tstate_getframe() + self.assertIs(frame_from_c, sys._getframe(0)) + + def test_getnextcomplete_matches_f_back(self): + # GetNextComplete must match f_back at every step, all the way to None. + c_frame = _testinternalcapi.tstate_getframe() + py_frame = sys._getframe(0) + while c_frame is not None: + self.assertIs(c_frame, py_frame) + c_frame = _testinternalcapi.iframe_getnextcomplete(c_frame) + py_frame = py_frame.f_back + self.assertIsNone(py_frame) + + def test_stack_to_yaml(self): + # stack_to_yaml uses only signal-safe operations for the walk and + # emission (no allocation, no refcount changes, no GIL release). + # Verify the output is well-formed and contains the expected frames. + def inner(): + return _testinternalcapi.stack_to_yaml() + + yaml_str = inner() + self.assertIsInstance(yaml_str, str) + + # Parse the YAML manually to avoid a PyYAML dependency. + # Format is blocks of: + # - filename: + # name: + # lineno: + frames = [] + current = {} + for line in yaml_str.splitlines(): + if line.startswith('- filename: '): + if current: + frames.append(current) + current = {'filename': line[len('- filename: '):]} + elif line.startswith(' name: '): + current['name'] = line[len(' name: '):] + elif line.startswith(' lineno: '): + current['lineno'] = int(line[len(' lineno: '):]) + if current: + frames.append(current) + + self.assertGreater(len(frames), 1) + # Innermost frame is the Python function that called stack_to_yaml. + self.assertEqual(frames[0]['name'], 'inner') + # Next frame is this test method. + self.assertEqual(frames[1]['name'], 'test_stack_to_yaml') + # Every frame must have a non-empty filename and a valid lineno. + for f in frames: + self.assertTrue(f.get('filename'), f) + self.assertIsInstance(f.get('lineno'), int, f) + + def test_getcodesafe_matches_fcode(self): + # GetCodeSafe must return the same code object as frame.f_code. + frame = _testinternalcapi.tstate_getframe() + self.assertIs(_testinternalcapi.iframe_getcodesafe(frame), frame.f_code) + + + def test_iframe_getlinesafe(self): + # Use a generator frame frozen at a yield point so that iframe_getlasti + # and iframe_getlinesafe both read the same (stable) instruction pointer. + def gen(): + yield + g = gen() + next(g) + frame = g.gi_frame + lasti = _testinternalcapi.iframe_getlasti(frame) + lineno = _testinternalcapi.iframe_getlinesafe(frame) + if lasti >= 0: + expected = None + for start, end, ln in frame.f_code.co_lines(): + if ln is not None and start <= lasti < end: + expected = ln + break + if expected is not None: + self.assertEqual(lineno, expected) + SUFFICIENT_TO_DEOPT_AND_SPECIALIZE = 100 diff --git a/Misc/NEWS.d/next/C_API/2026-04-23-14-08-40.gh-issue-148925.qnLUh5.rst b/Misc/NEWS.d/next/C_API/2026-04-23-14-08-40.gh-issue-148925.qnLUh5.rst new file mode 100644 index 00000000000000..cbaa68e9a35edc --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-04-23-14-08-40.gh-issue-148925.qnLUh5.rst @@ -0,0 +1,7 @@ +Add four new ``PyUnstable_*`` C API functions for signal-safe, allocation-free +call-stack iteration: :c:func:`PyUnstable_ThreadState_GetInterpreterFrame`, +:c:func:`PyUnstable_InterpreterFrame_GetNextComplete`, +:c:func:`PyUnstable_InterpreterFrame_GetCodeSafe`, and +:c:func:`PyUnstable_InterpreterFrame_GetLineSafe`. These functions do not +allocate memory, do not change reference counts, and do not acquire the GIL, +making them safe to call from signal handlers and custom memory allocator hooks. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index deac8570fe3241..b4e80fb5f5b578 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1307,6 +1307,168 @@ iframe_getlasti(PyObject *self, PyObject *frame) return PyLong_FromLong(PyUnstable_InterpreterFrame_GetLasti(f)); } +/* ---- signal-safe buffer helpers ---------------------------------------- + * Both helpers use only memcpy and arithmetic — no libc printf, no locale, + * unambiguously async-signal-safe on all POSIX platforms. + * ------------------------------------------------------------------------- */ + +/* Append `len` bytes from `src` into buf[pos..cap-1]. + * Returns the new position, or cap (saturated) on overflow. */ +static int +_yaml_write(char *buf, int pos, int cap, const char *src, int len) +{ + if (pos + len >= cap) { return cap; } + memcpy(buf + pos, src, len); + return pos + len; +} + +/* Append a string literal whose length is known at compile time. */ +#define _yaml_lit(buf, pos, cap, lit) \ + _yaml_write((buf), (pos), (cap), (lit), (int)(sizeof(lit) - 1)) + +/* Append a decimal integer. */ +static int +_yaml_decimal(char *buf, int pos, int cap, int value) +{ + char tmp[12]; + int len = 0; + int neg = value < 0; + if (neg) { value = -value; } + do { tmp[len++] = '0' + value % 10; value /= 10; } while (value); + if (neg) { tmp[len++] = '-'; } + for (int i = 0, j = len - 1; i < j; i++, j--) { + char t = tmp[i]; tmp[i] = tmp[j]; tmp[j] = t; + } + return _yaml_write(buf, pos, cap, tmp, len); +} + +/* ---- signal-safe stack-to-YAML ----------------------------------------- */ + +#define STACK_YAML_BUFSZ 8192 + +/* Walk the call stack and write a YAML sequence into buf using only + * public PyUnstable_* APIs and signal-safe operations: no allocation, + * no refcount changes, no GIL release. + * Returns bytes written (excluding NUL), or -1 on overflow. */ +static int _Py_NO_SANITIZE_THREAD +_emit_stack_yaml_nosignal(char *buf, int cap, PyThreadState *tstate) +{ + int pos = 0; + struct _PyInterpreterFrame *frame = + PyUnstable_ThreadState_GetInterpreterFrame(tstate); + while (frame != NULL && pos < cap) { + PyCodeObject *code = + (PyCodeObject *)PyUnstable_InterpreterFrame_GetCodeSafe(frame); + if (code == NULL) { break; } + int lineno = PyUnstable_InterpreterFrame_GetLineSafe(frame); + + PyObject *filename = code->co_filename; + PyObject *name = code->co_name; + + pos = _yaml_lit(buf, pos, cap, "- filename: "); + if (filename && PyUnicode_IS_ASCII(filename)) { + pos = _yaml_write(buf, pos, cap, + (const char *)PyUnicode_DATA(filename), + (int)PyUnicode_GET_LENGTH(filename)); + } else { pos = _yaml_lit(buf, pos, cap, "???"); } + + pos = _yaml_lit(buf, pos, cap, "\n name: "); + if (name && PyUnicode_IS_ASCII(name)) { + pos = _yaml_write(buf, pos, cap, + (const char *)PyUnicode_DATA(name), + (int)PyUnicode_GET_LENGTH(name)); + } else { pos = _yaml_lit(buf, pos, cap, "???"); } + + pos = _yaml_lit(buf, pos, cap, "\n lineno: "); + pos = _yaml_decimal(buf, pos, cap, lineno); + pos = _yaml_lit(buf, pos, cap, "\n"); + + frame = PyUnstable_InterpreterFrame_GetNextComplete(frame); + } + if (pos >= cap) { return -1; } + buf[pos] = '\0'; + return pos; +} + +/* Return the current call stack as a YAML string. + * The walk and emission are entirely signal-safe (no allocation, no refcount + * changes, no GIL release). The only non-signal-safe step is the final + * PyUnicode_FromStringAndSize call after the walk completes. */ +static PyObject * +stack_to_yaml(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + char buf[STACK_YAML_BUFSZ]; + PyThreadState *tstate = _PyThreadState_GET(); + int len = _emit_stack_yaml_nosignal(buf, sizeof(buf), tstate); + if (len < 0) { + PyErr_SetString(PyExc_OverflowError, + "stack YAML output exceeded buffer"); + return NULL; + } + return PyUnicode_FromStringAndSize(buf, len); +} + +static PyObject * +iframe_getnextcomplete(PyObject *self, PyObject *frame) +{ + if (!PyFrame_Check(frame)) { + PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + return NULL; + } + struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame; + struct _PyInterpreterFrame *next = PyUnstable_InterpreterFrame_GetNextComplete(f); + if (next == NULL) { + Py_RETURN_NONE; + } + PyObject *result = (PyObject *)_PyFrame_GetFrameObject(next); + if (result == NULL) { + return NULL; + } + return Py_NewRef(result); +} + +static PyObject * +tstate_getframe(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyThreadState *tstate = _PyThreadState_GET(); + struct _PyInterpreterFrame *f = PyUnstable_ThreadState_GetInterpreterFrame(tstate); + if (f == NULL) { + Py_RETURN_NONE; + } + PyObject *result = (PyObject *)_PyFrame_GetFrameObject(f); + if (result == NULL) { + return NULL; + } + return Py_NewRef(result); +} + +static PyObject * +iframe_getcodesafe(PyObject *self, PyObject *frame) +{ + if (!PyFrame_Check(frame)) { + PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + return NULL; + } + struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame; + PyObject *code = PyUnstable_InterpreterFrame_GetCodeSafe(f); + if (code == NULL) { + Py_RETURN_NONE; + } + return Py_NewRef(code); +} + + +static PyObject * +iframe_getlinesafe(PyObject *self, PyObject *frame) +{ + if (!PyFrame_Check(frame)) { + PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + return NULL; + } + struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame; + return PyLong_FromLong(PyUnstable_InterpreterFrame_GetLineSafe(f)); +} + static PyObject * code_returns_only_none(PyObject *self, PyObject *arg) { @@ -2929,6 +3091,12 @@ static PyMethodDef module_functions[] = { {"iframe_getcode", iframe_getcode, METH_O, NULL}, {"iframe_getline", iframe_getline, METH_O, NULL}, {"iframe_getlasti", iframe_getlasti, METH_O, NULL}, + {"iframe_getnextcomplete", iframe_getnextcomplete, METH_O, NULL}, + {"iframe_getcodesafe", iframe_getcodesafe, METH_O, NULL}, + {"tstate_getframe", tstate_getframe, METH_NOARGS, NULL}, + {"stack_to_yaml", stack_to_yaml, METH_NOARGS, NULL}, + + {"iframe_getlinesafe", iframe_getlinesafe, METH_O, NULL}, {"code_returns_only_none", code_returns_only_none, METH_O, NULL}, {"get_co_framesize", get_co_framesize, METH_O, NULL}, {"get_co_localskinds", get_co_localskinds, METH_O, NULL}, diff --git a/Python/frame.c b/Python/frame.c index ff81eb0b3020c7..6a4a69d6ebecdb 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -150,6 +150,55 @@ PyUnstable_InterpreterFrame_GetLine(_PyInterpreterFrame *frame) return PyCode_Addr2Line(_PyFrame_GetCode(frame), addr); } +PyObject * _Py_NO_SANITIZE_THREAD +PyUnstable_InterpreterFrame_GetCodeSafe(struct _PyInterpreterFrame *frame) +{ + return (PyObject *)_PyFrame_SafeGetCode(frame); +} + +int _Py_NO_SANITIZE_THREAD +PyUnstable_InterpreterFrame_GetLineSafe(struct _PyInterpreterFrame *frame) +{ + PyCodeObject *code = _PyFrame_SafeGetCode(frame); + if (code == NULL) { + return -1; + } + _Py_CODEUNIT *bytecode = _PyFrame_SafeGetBytecode(frame, code); + if (bytecode == NULL) { + return -1; + } + int addr = (int)(frame->instr_ptr - bytecode) * sizeof(_Py_CODEUNIT); + return _PyCode_SafeAddr2Line(code, addr); +} + + +static _PyInterpreterFrame * _Py_NO_SANITIZE_THREAD +_first_complete_frame(_PyInterpreterFrame *frame) +{ + while (frame != NULL) { + if (_PyMem_IsPtrFreed(frame)) { + return NULL; + } + if (!_PyFrame_IsIncomplete(frame)) { + return frame; + } + frame = frame->previous; + } + return NULL; +} + +struct _PyInterpreterFrame * _Py_NO_SANITIZE_THREAD +PyUnstable_ThreadState_GetInterpreterFrame(PyThreadState *tstate) +{ + return _first_complete_frame(tstate->current_frame); +} + +struct _PyInterpreterFrame * _Py_NO_SANITIZE_THREAD +PyUnstable_InterpreterFrame_GetNextComplete(struct _PyInterpreterFrame *frame) +{ + return _first_complete_frame(frame->previous); +} + const PyTypeObject *const PyUnstable_ExecutableKinds[PyUnstable_EXECUTABLE_KINDS+1] = { [PyUnstable_EXECUTABLE_KIND_SKIP] = &_PyNone_Type, [PyUnstable_EXECUTABLE_KIND_PY_FUNCTION] = &PyCode_Type, diff --git a/Python/traceback.c b/Python/traceback.c index 1e8c9c879f9aac..f3eb0b90b546e3 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1038,12 +1038,7 @@ _Py_DumpWideString(int fd, wchar_t *str) static int _Py_NO_SANITIZE_THREAD dump_frame(int fd, _PyInterpreterFrame *frame) { - if (frame->owner == FRAME_OWNED_BY_INTERPRETER) { - /* Ignore trampoline frames and base frame sentinel */ - return 0; - } - - PyCodeObject *code = _PyFrame_SafeGetCode(frame); + PyCodeObject *code = (PyCodeObject *)PyUnstable_InterpreterFrame_GetCodeSafe(frame); if (code == NULL) { return -1; } @@ -1063,11 +1058,7 @@ dump_frame(int fd, _PyInterpreterFrame *frame) } PUTS(fd, ", line "); - int lasti = _PyFrame_SafeGetLasti(frame); - int lineno = -1; - if (lasti >= 0) { - lineno = _PyCode_SafeAddr2Line(code, lasti); - } + int lineno = PyUnstable_InterpreterFrame_GetLineSafe(frame); if (lineno >= 0) { _Py_DumpDecimal(fd, (size_t)lineno); } @@ -1123,7 +1114,7 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) return; } - _PyInterpreterFrame *frame = tstate->current_frame; + _PyInterpreterFrame *frame = PyUnstable_ThreadState_GetInterpreterFrame(tstate); if (frame == NULL) { PUTS(fd, " \n"); return; @@ -1144,9 +1135,9 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header) PUTS(fd, " \n"); break; } - // Read frame->previous early since memory can be freed during - // dump_frame() - _PyInterpreterFrame *previous = frame->previous; + // Read frame->previous early: memory may be freed during dump_frame(), + // so advance to the next frame while frame is still live. + _PyInterpreterFrame *previous = PyUnstable_InterpreterFrame_GetNextComplete(frame); if (dump_frame(fd, frame) < 0) { PUTS(fd, " \n"); diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c index 0afc84e021817c..44a29d1a959f63 100644 --- a/Python/tracemalloc.c +++ b/Python/tracemalloc.c @@ -224,14 +224,11 @@ tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame) assert(PyStackRef_CodeCheck(pyframe->f_executable)); frame->filename = &_Py_STR(anon_unknown); - int lineno = -1; - PyCodeObject *code = _PyFrame_GetCode(pyframe); - // PyUnstable_InterpreterFrame_GetLine() cannot but used, since it uses - // a critical section which can trigger a deadlock. - int lasti = _PyFrame_SafeGetLasti(pyframe); - if (lasti >= 0) { - lineno = _PyCode_SafeAddr2Line(code, lasti); + PyCodeObject *code = (PyCodeObject *)PyUnstable_InterpreterFrame_GetCodeSafe(pyframe); + if (code == NULL) { + return; } + int lineno = PyUnstable_InterpreterFrame_GetLineSafe(pyframe); if (lineno < 0) { lineno = 0; } @@ -308,7 +305,7 @@ traceback_get_frames(traceback_t *traceback) PyThreadState *tstate = _PyThreadState_GET(); assert(tstate != NULL); - _PyInterpreterFrame *pyframe = _PyThreadState_GetFrame(tstate); + _PyInterpreterFrame *pyframe = PyUnstable_ThreadState_GetInterpreterFrame(tstate); while (pyframe) { if (traceback->nframe < tracemalloc_config.max_nframe) { tracemalloc_get_frame(pyframe, &traceback->frames[traceback->nframe]); @@ -318,7 +315,7 @@ traceback_get_frames(traceback_t *traceback) if (traceback->total_nframe < UINT16_MAX) { traceback->total_nframe++; } - pyframe = _PyFrame_GetFirstComplete(pyframe->previous); + pyframe = PyUnstable_InterpreterFrame_GetNextComplete(pyframe); } }