From f4874f381c1ef9a3e0e17fde870f2d38d89e043b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 29 Aug 2026 05:14:29 -0700 Subject: [PATCH 1/7] gh-156466: Fix additional compiler scope cleanup errors (#156547) --- Python/codegen.c | 2 +- Python/compile.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index b3e9488b0236fbb..7eb96f08c84d7f2 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -1634,7 +1634,7 @@ codegen_class_body(compiler *c, stmt_ty s, int firstlineno) ADDOP_N_IN_SCOPE(c, loc, STORE_DEREF, &_Py_ID(__classdict__), cellvars); } if (SYMTABLE_ENTRY(c)->ste_has_conditional_annotations) { - ADDOP_I(c, loc, BUILD_SET, 0); + ADDOP_I_IN_SCOPE(c, loc, BUILD_SET, 0); ADDOP_N_IN_SCOPE(c, loc, STORE_DEREF, &_Py_ID(__conditional_annotations__), cellvars); } /* compile the body proper */ diff --git a/Python/compile.c b/Python/compile.c index fefb2b04b78db86..2717037a988ec7f 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1735,7 +1735,9 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags, finally: Py_XDECREF(consts_list); Py_XDECREF(metadata); - _PyCompile_ExitScope(c); + if (c->u != NULL) { + _PyCompile_ExitScope(c); + } compiler_free(c); _PyArena_Free(arena); return res; From 8b082aa405101ab806433cd4d7305b19397271cf Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:22:25 +0100 Subject: [PATCH 2/7] gh-156459: Fix cleanup on error in compiler_set_qualname (#156462) --- Include/internal/pycore_compile.h | 1 + .../2026-08-27-13-26-50.gh-issue-156459.Z8Zqik.rst | 3 +++ Python/codegen.c | 1 + Python/compile.c | 11 ++++++----- 4 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-13-26-50.gh-issue-156459.Z8Zqik.rst diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index 911cc1f10f15131..7e248429af8eb8a 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -137,6 +137,7 @@ int _PyCompile_EnterScope(struct _PyCompiler *c, identifier name, int scope_type void *key, int lineno, PyObject *private, _PyCompile_CodeUnitMetadata *umd); void _PyCompile_ExitScope(struct _PyCompiler *c); +int _PyCompile_SetQualname(struct _PyCompiler *c); Py_ssize_t _PyCompile_AddConst(struct _PyCompiler *c, PyObject *o); _PyInstructionSequence *_PyCompile_InstrSequence(struct _PyCompiler *c); int _PyCompile_StartAnnotationSetup(struct _PyCompiler *c); diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-13-26-50.gh-issue-156459.Z8Zqik.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-13-26-50.gh-issue-156459.Z8Zqik.rst new file mode 100644 index 000000000000000..2de2e0e72273c6c --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-27-13-26-50.gh-issue-156459.Z8Zqik.rst @@ -0,0 +1,3 @@ +Fix cleanup on error in ``compiler_set_qualname``. Previously it was called in +``_PyCompile_EnterScope``, after the scope had been entered, and this was not +reversed in case of an error. diff --git a/Python/codegen.c b/Python/codegen.c index 7eb96f08c84d7f2..f4cdb17799d3f77 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -687,6 +687,7 @@ codegen_enter_scope(compiler *c, identifier name, int scope_type, { RETURN_IF_ERROR( _PyCompile_EnterScope(c, name, scope_type, key, lineno, private, umd)); + RETURN_IF_ERROR_IN_SCOPE(c, _PyCompile_SetQualname(c)); location loc = LOCATION(lineno, lineno, 0, 0); if (scope_type == COMPILE_SCOPE_MODULE) { loc.lineno = 0; diff --git a/Python/compile.c b/Python/compile.c index 2717037a988ec7f..f3852041bce69ca 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -233,13 +233,17 @@ _PyCompile_MaybeAddStaticAttributeToClass(compiler *c, expr_ty e) return SUCCESS; } -static int -compiler_set_qualname(compiler *c) +int +_PyCompile_SetQualname(compiler *c) { Py_ssize_t stack_size; struct compiler_unit *u = c->u; PyObject *name, *base; + if (u->u_scope_type == COMPILE_SCOPE_MODULE) { + return SUCCESS; + } + base = NULL; stack_size = PyList_GET_SIZE(c->c_stack); assert(stack_size >= 1); @@ -724,9 +728,6 @@ _PyCompile_EnterScope(compiler *c, identifier name, int scope_type, u->u_private = Py_XNewRef(private); c->u = u; - if (scope_type != COMPILE_SCOPE_MODULE) { - RETURN_IF_ERROR(compiler_set_qualname(c)); - } return SUCCESS; } From d94c4ad1b65b102d10634b43bea61d35dae7baeb Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:01:55 +0100 Subject: [PATCH 3/7] gh-156466: fix cleanup on error in codegen_function_body (#156511) --- Python/codegen.c | 71 ++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index f4cdb17799d3f77..875c963a6078c66 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -1391,6 +1391,44 @@ codegen_type_params(compiler *c, asdl_type_param_seq *type_params) return SUCCESS; } +static int +codegen_emit_function_body(compiler *c, asdl_stmt_seq *body) +{ + PySTEntryObject *ste = SYMTABLE_ENTRY(c); + Py_ssize_t first_instr = 0; + if (ste->ste_has_docstring) { + PyObject *docstring = _PyAST_GetDocString(body); + assert(docstring); + first_instr = 1; + docstring = _PyCompile_CleanDoc(docstring); + if (docstring == NULL) { + return ERROR; + } + Py_ssize_t idx = _PyCompile_AddConst(c, docstring); + Py_DECREF(docstring); + RETURN_IF_ERROR(idx < 0 ? ERROR : SUCCESS); + } + + NEW_JUMP_TARGET_LABEL(c, start); + USE_LABEL(c, start); + bool add_stopiteration_handler = ste->ste_coroutine || ste->ste_generator; + if (add_stopiteration_handler) { + /* codegen_wrap_in_stopiteration_handler will push a block, so we need to account for that */ + RETURN_IF_ERROR( + _PyCompile_PushFBlock(c, NO_LOCATION, COMPILE_FBLOCK_STOP_ITERATION, + start, NO_LABEL, NULL)); + } + + for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(body); i++) { + VISIT(c, stmt, (stmt_ty)asdl_seq_GET(body, i)); + } + if (add_stopiteration_handler) { + RETURN_IF_ERROR(codegen_wrap_in_stopiteration_handler(c)); + _PyCompile_PopFBlock(c, COMPILE_FBLOCK_STOP_ITERATION, start); + } + return SUCCESS; +} + static int codegen_function_body(compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags, int firstlineno) @@ -1426,39 +1464,8 @@ codegen_function_body(compiler *c, stmt_ty s, int is_async, Py_ssize_t funcflags RETURN_IF_ERROR( codegen_enter_scope(c, name, scope_type, (void *)s, firstlineno, NULL, &umd)); - PySTEntryObject *ste = SYMTABLE_ENTRY(c); - Py_ssize_t first_instr = 0; - if (ste->ste_has_docstring) { - PyObject *docstring = _PyAST_GetDocString(body); - assert(docstring); - first_instr = 1; - docstring = _PyCompile_CleanDoc(docstring); - if (docstring == NULL) { - _PyCompile_ExitScope(c); - return ERROR; - } - Py_ssize_t idx = _PyCompile_AddConst(c, docstring); - Py_DECREF(docstring); - RETURN_IF_ERROR_IN_SCOPE(c, idx < 0 ? ERROR : SUCCESS); - } + RETURN_IF_ERROR_IN_SCOPE(c, codegen_emit_function_body(c, body)); - NEW_JUMP_TARGET_LABEL(c, start); - USE_LABEL(c, start); - bool add_stopiteration_handler = ste->ste_coroutine || ste->ste_generator; - if (add_stopiteration_handler) { - /* codegen_wrap_in_stopiteration_handler will push a block, so we need to account for that */ - RETURN_IF_ERROR( - _PyCompile_PushFBlock(c, NO_LOCATION, COMPILE_FBLOCK_STOP_ITERATION, - start, NO_LABEL, NULL)); - } - - for (Py_ssize_t i = first_instr; i < asdl_seq_LEN(body); i++) { - VISIT_IN_SCOPE(c, stmt, (stmt_ty)asdl_seq_GET(body, i)); - } - if (add_stopiteration_handler) { - RETURN_IF_ERROR_IN_SCOPE(c, codegen_wrap_in_stopiteration_handler(c)); - _PyCompile_PopFBlock(c, COMPILE_FBLOCK_STOP_ITERATION, start); - } PyCodeObject *co = _PyCompile_OptimizeAndAssemble(c, 1); _PyCompile_ExitScope(c); if (co == NULL) { From e1d969ad95c21cccd9b2d47d4ce9c40343aa7b6d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 29 Aug 2026 19:01:09 +0300 Subject: [PATCH 4/7] gh-148653: Forbid marshalling recursive tuples (GH-155903) Also fix a crash when unmarshalling a self-referencing tuple. Co-authored-by: Michael Bommarito Co-authored-by: Claude Opus 5 (1M context) --- Lib/test/test_marshal.py | 53 +++++++++---------- ...-08-16-19-33-01.gh-issue-148653.Kt3vQx.rst | 2 + Python/marshal.c | 11 ++-- 3 files changed, 36 insertions(+), 30 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-16-19-33-01.gh-issue-148653.Kt3vQx.rst diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 0e71d65f22b4f0d..b5bacadbfd381fc 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -342,14 +342,26 @@ def test_reference_loop_dict(self): def test_reference_loop_tuple(self): a = ([],) a[0].append(a) - for v in range(3): + for v in range(marshal.version + 1): self.assertRaises(ValueError, marshal.dumps, a, v) + + a = ({},) + a[0][None] = a + for v in range(marshal.version + 1): + self.assertRaises(ValueError, marshal.dumps, a, v) + + def test_shared_reference_tuple(self): + # A tuple referenced more than once still round-trips with the + # shared identity preserved. + a = (1, 2) for v in range(3, marshal.version + 1): - d = marshal.dumps(a, v) - b = marshal.loads(d) - self.assertIsInstance(b, tuple) - self.assertIsInstance(b[0], list) - self.assertIs(b[0][0], b) + b = marshal.loads(marshal.dumps([a, a], v)) + self.assertEqual(b[0], a) + self.assertIs(b[0], b[1]) + big = tuple(range(300)) # too large for TYPE_SMALL_TUPLE + b = marshal.loads(marshal.dumps([big, big])) + self.assertEqual(b[0], big) + self.assertIs(b[0], b[1]) def test_reference_loop_code(self): def f(): @@ -409,27 +421,6 @@ def test_loads_reference_loop_dict(self): self.assertIs(a[None], a) def test_loads_abnormal_reference_loops(self): - # Indirect self-references of tuples. - data = b'\xa8\x01\x00\x00\x00[\x01\x00\x00\x00r\x00\x00\x00\x00' # ([],) - a = marshal.loads(data) - self.assertIsInstance(a, tuple) - self.assertIsInstance(a[0], list) - self.assertIs(a[0][0], a) - - data = b'\xa8\x01\x00\x00\x00{Nr\x00\x00\x00\x000' # ({None: },) - a = marshal.loads(data) - self.assertIsInstance(a, tuple) - self.assertIsInstance(a[0], dict) - self.assertIs(a[0][None], a) - - # Direct self-reference which cannot be created in Python. - # This creates a reference loop which cannot be collected. - if False: - data = b'\xa8\x01\x00\x00\x00r\x00\x00\x00\x00' # (,) - a = marshal.loads(data) - self.assertIsInstance(a, tuple) - self.assertIs(a[0], a) - # Direct self-references which cannot be created in Python # because of unhashability. data = b'\xfbr\x00\x00\x00\x00N0' # {: None} @@ -439,6 +430,8 @@ def test_loads_abnormal_reference_loops(self): for data in [ # Indirect self-references of immutable objects. + b'\xa8\x01\x00\x00\x00[\x01\x00\x00\x00r\x00\x00\x00\x00', # ([],) + b'\xa8\x01\x00\x00\x00{Nr\x00\x00\x00\x000', # ({None: },) b'\xba[\x01\x00\x00\x00r\x00\x00\x00\x00NN', # slice([], None) b'\xbaN[\x01\x00\x00\x00r\x00\x00\x00\x00N', # slice(None, []) b'\xbaNN[\x01\x00\x00\x00r\x00\x00\x00\x00', # slice(None, None, []) @@ -449,12 +442,18 @@ def test_loads_abnormal_reference_loops(self): b'\xfdN{Nr\x00\x00\x00\x0000', # frozendict({None: {None: }) # Direct self-references which cannot be created in Python. + b'\xa8\x01\x00\x00\x00r\x00\x00\x00\x00', # (,) b'\xbe\x01\x00\x00\x00r\x00\x00\x00\x00', # frozenset({}) b'\xfdNr\x00\x00\x00\x000', # frozendict({None: }) b'\xfdr\x00\x00\x00\x00N0', # frozendict({: None}) b'\xbar\x00\x00\x00\x00NN', # slice(, None) b'\xbaNr\x00\x00\x00\x00N', # slice(None, ) b'\xbaNNr\x00\x00\x00\x00', # slice(None, None, ) + + # Indirect self-references which cannot be created in Python + # because of unhashability. + b'\xa8\x01\x00\x00\x00{r\x00\x00\x00\x00N0', # ({: None},) + b'\xa8\x01\x00\x00\x00<\x01\x00\x00\x00r\x00\x00\x00\x00', # ({},) ]: with self.subTest(data=data): self.assertRaises(ValueError, marshal.loads, data) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-16-19-33-01.gh-issue-148653.Kt3vQx.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-16-19-33-01.gh-issue-148653.Kt3vQx.rst new file mode 100644 index 000000000000000..78502cc4c6a747b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-16-19-33-01.gh-issue-148653.Kt3vQx.rst @@ -0,0 +1,2 @@ +Forbid :mod:`marshalling ` recursive tuples, and fix a crash when +unmarshalling a self-referencing tuple. diff --git a/Python/marshal.c b/Python/marshal.c index b11f2dca57a226c..ef5a8d3840cd805 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -417,7 +417,9 @@ w_ref(PyObject *v, char *flag, WFILE *p) } // Corresponding code should call w_complete() after // writing the object. - if (PyCode_Check(v) || PySlice_Check(v) || PyFrozenDict_CheckExact(v)) { + if (PyTuple_CheckExact(v) || PyCode_Check(v) || PySlice_Check(v) || + PyFrozenDict_CheckExact(v)) + { w |= 0x80000000LU; } if (_Py_hashtable_set(p->hashtable, Py_NewRef(v), @@ -596,6 +598,7 @@ w_complex_object(PyObject *v, char flag, WFILE *p) for (i = 0; i < n; i++) { w_object(PyTuple_GET_ITEM(v, i), p); } + w_complete(v, p); } else if (PyList_CheckExact(v)) { W_TYPE(TYPE_LIST, p); @@ -1417,8 +1420,10 @@ r_object(RFILE *p) break; } _read_tuple: + idx = r_ref_reserve(flag, p); + if (idx < 0) + break; v = PyTuple_New(n); - R_REF(v); if (v == NULL) break; @@ -1433,7 +1438,7 @@ r_object(RFILE *p) } PyTuple_SET_ITEM(v, i, v2); } - retval = v; + retval = r_ref_insert(v, idx, flag, p); break; case TYPE_LIST: From 5cb7c39e6378d6cfd2bca140aa1eebf72cc7ffd3 Mon Sep 17 00:00:00 2001 From: Evan A Mavrin <36235551+SynaptSea@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:02:26 -0400 Subject: [PATCH 5/7] gh-154200: Use sysconfig.get_platform() in test_importlib.test_windows (#154210) instead of a port of distutils.util.get_platform() --- Lib/test/test_importlib/test_windows.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_importlib/test_windows.py b/Lib/test/test_importlib/test_windows.py index bef4fb46f859a4e..146a7394b2b648e 100644 --- a/Lib/test/test_importlib/test_windows.py +++ b/Lib/test/test_importlib/test_windows.py @@ -4,6 +4,7 @@ import os import re import sys +import sysconfig import unittest from test import support from test.support import import_helper @@ -17,25 +18,6 @@ EnumKey, CloseKey, DeleteKey, OpenKey ) -def get_platform(): - # Port of distutils.util.get_platform(). - TARGET_TO_PLAT = { - 'x86' : 'win32', - 'x64' : 'win-amd64', - 'arm' : 'win-arm32', - } - if ('VSCMD_ARG_TGT_ARCH' in os.environ and - os.environ['VSCMD_ARG_TGT_ARCH'] in TARGET_TO_PLAT): - return TARGET_TO_PLAT[os.environ['VSCMD_ARG_TGT_ARCH']] - elif 'amd64' in sys.version.lower(): - return 'win-amd64' - elif '(arm)' in sys.version.lower(): - return 'win-arm32' - elif '(arm64)' in sys.version.lower(): - return 'win-arm64' - else: - return sys.platform - def delete_registry_tree(root, subkey): try: hkey = OpenKey(root, subkey, access=KEY_ALL_ACCESS) @@ -143,7 +125,7 @@ def test_tagged_suffix(self): suffixes = self.machinery.EXTENSION_SUFFIXES abi_flags = "t" if support.Py_GIL_DISABLED else "" ver = sys.version_info - platform = re.sub('[^a-zA-Z0-9]', '_', get_platform()) + platform = re.sub('[^a-zA-Z0-9]', '_', sysconfig.get_platform()) expected_tag = f".cp{ver.major}{ver.minor}{abi_flags}-{platform}.pyd" try: untagged_i = suffixes.index(".pyd") From a175da7a3ae3ba8d8aab9225c755bb41782edb4c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 29 Aug 2026 19:03:00 +0300 Subject: [PATCH 6/7] gh-155905: Fix error handling in _testcapi helpers (GH-155906) Py_fopen() sets an exception and returns NULL on error. The pyobject_print*() helpers did not check the result and crashed, and the pymarshal_*() helpers set a second exception on top of it. The pyobject_print*() helpers which take a single argument now use METH_O, and the result of PyUnicode_FromString() is now checked. Co-authored-by: Claude Opus 5 (1M context) --- Modules/_testcapi/object.c | 40 ++++++++++++++++++++++---------------- Modules/_testcapimodule.c | 6 ------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 09a548fd2e24489..425ec540b62dcc0 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -16,6 +16,9 @@ call_pyobject_print(PyObject *self, PyObject * args) } fp = Py_fopen(filename, "w+"); + if (fp == NULL) { + return NULL; + } if (Py_IsTrue(print_raw)) { flags = Py_PRINT_RAW; @@ -32,17 +35,15 @@ call_pyobject_print(PyObject *self, PyObject * args) } static PyObject * -pyobject_print_null(PyObject *self, PyObject *args) +pyobject_print_null(PyObject *self, PyObject *filename) { - PyObject *filename; FILE *fp; - if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) { + fp = Py_fopen(filename, "w+"); + if (fp == NULL) { return NULL; } - fp = Py_fopen(filename, "w+"); - if (PyObject_Print(NULL, fp, 0) < 0) { fclose(fp); return NULL; @@ -54,26 +55,29 @@ pyobject_print_null(PyObject *self, PyObject *args) } static PyObject * -pyobject_print_noref_object(PyObject *self, PyObject *args) +pyobject_print_noref_object(PyObject *self, PyObject *filename) { PyObject *test_string; - PyObject *filename; FILE *fp; char correct_string[100]; test_string = PyUnicode_FromString("Spam spam spam"); + if (test_string == NULL) { + return NULL; + } Py_SET_REFCNT(test_string, 0); PyOS_snprintf(correct_string, 100, "", Py_REFCNT(test_string), (void *)test_string); - if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) { + fp = Py_fopen(filename, "w+"); + if (fp == NULL) { + Py_SET_REFCNT(test_string, 1); + Py_DECREF(test_string); return NULL; } - fp = Py_fopen(filename, "w+"); - if (PyObject_Print(test_string, fp, 0) < 0){ fclose(fp); Py_SET_REFCNT(test_string, 1); @@ -90,20 +94,22 @@ pyobject_print_noref_object(PyObject *self, PyObject *args) } static PyObject * -pyobject_print_os_error(PyObject *self, PyObject *args) +pyobject_print_os_error(PyObject *self, PyObject *filename) { PyObject *test_string; - PyObject *filename; FILE *fp; test_string = PyUnicode_FromString("Spam spam spam"); - - if (!PyArg_UnpackTuple(args, "call_pyobject_print", 1, 1, &filename)) { + if (test_string == NULL) { return NULL; } // open file in read mode to induce OSError fp = Py_fopen(filename, "r"); + if (fp == NULL) { + Py_DECREF(test_string); + return NULL; + } if (PyObject_Print(test_string, fp, 0) < 0) { fclose(fp); @@ -582,9 +588,9 @@ pysentinel_checkexact(PyObject *self, PyObject *obj) static PyMethodDef test_methods[] = { {"call_pyobject_print", call_pyobject_print, METH_VARARGS}, - {"pyobject_print_null", pyobject_print_null, METH_VARARGS}, - {"pyobject_print_noref_object", pyobject_print_noref_object, METH_VARARGS}, - {"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS}, + {"pyobject_print_null", pyobject_print_null, METH_O}, + {"pyobject_print_noref_object", pyobject_print_noref_object, METH_O}, + {"pyobject_print_os_error", pyobject_print_os_error, METH_O}, {"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O}, {"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O}, {"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O}, diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index fb18a866e628128..c01197d15bad5f0 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1431,7 +1431,6 @@ pymarshal_write_long_to_file(PyObject* self, PyObject *args) fp = Py_fopen(filename, "wb"); if (fp == NULL) { - PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -1456,7 +1455,6 @@ pymarshal_write_object_to_file(PyObject* self, PyObject *args) fp = Py_fopen(filename, "wb"); if (fp == NULL) { - PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -1480,7 +1478,6 @@ pymarshal_read_short_from_file(PyObject* self, PyObject *args) fp = Py_fopen(filename, "rb"); if (fp == NULL) { - PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -1505,7 +1502,6 @@ pymarshal_read_long_from_file(PyObject* self, PyObject *args) fp = Py_fopen(filename, "rb"); if (fp == NULL) { - PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -1527,7 +1523,6 @@ pymarshal_read_last_object_from_file(PyObject* self, PyObject *args) FILE *fp = Py_fopen(filename, "rb"); if (fp == NULL) { - PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -1550,7 +1545,6 @@ pymarshal_read_object_from_file(PyObject* self, PyObject *args) FILE *fp = Py_fopen(filename, "rb"); if (fp == NULL) { - PyErr_SetFromErrno(PyExc_OSError); return NULL; } From b0da7c72691d9a165f7849099aee1a9cc7299fc1 Mon Sep 17 00:00:00 2001 From: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com> Date: Sat, 29 Aug 2026 19:27:05 +0300 Subject: [PATCH 7/7] gh-156327: Fix asyncio.print_call_graph() reporting its own frame (#156329) --- Lib/asyncio/graph.py | 3 ++- Lib/test/test_asyncio/test_graph.py | 10 +++++++++- .../2026-08-24-22-20-55.gh-issue-156327.arC8Ph.rst | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-24-22-20-55.gh-issue-156327.arC8Ph.rst diff --git a/Lib/asyncio/graph.py b/Lib/asyncio/graph.py index e240c6dd77d1245..94fcd33d7088a68 100644 --- a/Lib/asyncio/graph.py +++ b/Lib/asyncio/graph.py @@ -274,4 +274,5 @@ def print_call_graph( limit: int | None = None, ) -> None: """Print the async call graph for the current task or the provided Future.""" - print(format_call_graph(future, depth=depth, limit=limit), file=file) + # gh-156327: print_call_graph() must not report its own frame + print(format_call_graph(future, depth=depth + 1, limit=limit), file=file) diff --git a/Lib/test/test_asyncio/test_graph.py b/Lib/test/test_asyncio/test_graph.py index 2c5bb2e8f52e668..50d528fb9fb2c51 100644 --- a/Lib/test/test_asyncio/test_graph.py +++ b/Lib/test/test_asyncio/test_graph.py @@ -41,7 +41,7 @@ def walk(s): return ret buf = io.StringIO() - asyncio.print_call_graph(fut, file=buf, depth=depth+1) + asyncio.print_call_graph(fut, file=buf, depth=depth) stack = asyncio.capture_call_graph(fut, depth=depth) return walk(stack), buf.getvalue() @@ -484,6 +484,14 @@ def test_capture_call_graph_non_future(self): with self.assertRaises(TypeError): asyncio.capture_call_graph("not a future") + async def test_print_call_graph_innermost_frame(self): + # gh-156327: print_call_graph() must not report its own frame + buf = io.StringIO() + lineno = sys._getframe().f_lineno + 1 + asyncio.print_call_graph(file=buf) + first_frame = buf.getvalue().splitlines()[2] + self.assertIn(f'File {__file__!r}, line {lineno},', first_frame) + async def test_call_graph_finished_task(self): # gh-156408: the call graph must not record a finished coroutine's None frame async def boom(): diff --git a/Misc/NEWS.d/next/Library/2026-08-24-22-20-55.gh-issue-156327.arC8Ph.rst b/Misc/NEWS.d/next/Library/2026-08-24-22-20-55.gh-issue-156327.arC8Ph.rst new file mode 100644 index 000000000000000..979db7a733a3eae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-24-22-20-55.gh-issue-156327.arC8Ph.rst @@ -0,0 +1,2 @@ +Fix :func:`asyncio.print_call_graph` including its own frame in the printed +call stack.