diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-03-17-13-51.gh-issue-148059.-WQMGn.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-03-17-13-51.gh-issue-148059.-WQMGn.rst new file mode 100644 index 00000000000000..335bf0dbfdda09 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-03-17-13-51.gh-issue-148059.-WQMGn.rst @@ -0,0 +1,5 @@ +For codegen method ``codegen_deferred_annotations_body`` we can fail to +decref ``mangled`` during the generation of the bytecode for an annotation +entry. This change creates a subfunction for the bytecode generation of the +annotation entry which will also to decref ``mangled`` in the event of +errors. diff --git a/Python/codegen.c b/Python/codegen.c index aca590d055f466..6df9a73f5f7df0 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -777,6 +777,38 @@ codegen_leave_annotations_scope(compiler *c, location loc) return SUCCESS; } +static int +codegen_deferred_annotations_entry(compiler *c, location loc, int scope_type, stmt_ty st, PyObject *mangled, PyObject *cond_index) +{ + long idx = PyLong_AS_LONG(cond_index); + NEW_JUMP_TARGET_LABEL(c, not_set); + + if (idx != -1) { + ADDOP_LOAD_CONST(c, LOC(st), cond_index); + if (scope_type == COMPILE_SCOPE_CLASS) { + ADDOP_NAME( + c, LOC(st), LOAD_DEREF, &_Py_ID(__conditional_annotations__), freevars); + } + else { + ADDOP_NAME( + c, LOC(st), LOAD_GLOBAL, &_Py_ID(__conditional_annotations__), names); + } + + ADDOP_I(c, LOC(st), CONTAINS_OP, 0); + ADDOP_JUMP(c, LOC(st), POP_JUMP_IF_FALSE, not_set); + } + + VISIT(c, expr, st->v.AnnAssign.annotation); + ADDOP_I(c, LOC(st), COPY, 2); + ADDOP_LOAD_CONST(c, LOC(st), mangled); + // stack now contains + ADDOP(c, loc, STORE_SUBSCR); + // stack now contains + + USE_LABEL(c, not_set); + return SUCCESS; +} + static int codegen_deferred_annotations_body(compiler *c, location loc, PyObject *deferred_anno, PyObject *conditional_annotation_indices, int scope_type) @@ -794,41 +826,18 @@ codegen_deferred_annotations_body(compiler *c, location loc, if (st == NULL) { return ERROR; } - PyObject *mangled = _PyCompile_Mangle(c, st->v.AnnAssign.target->v.Name.id); - if (!mangled) { - return ERROR; - } - // NOTE: ref of mangled can be leaked on ADDOP* and VISIT macros due to early returns - // fixing would require an overhaul of these macros PyObject *cond_index = PyList_GET_ITEM(conditional_annotation_indices, i); assert(PyLong_CheckExact(cond_index)); - long idx = PyLong_AS_LONG(cond_index); - NEW_JUMP_TARGET_LABEL(c, not_set); - - if (idx != -1) { - ADDOP_LOAD_CONST(c, LOC(st), cond_index); - if (scope_type == COMPILE_SCOPE_CLASS) { - ADDOP_NAME( - c, LOC(st), LOAD_DEREF, &_Py_ID(__conditional_annotations__), freevars); - } - else { - ADDOP_NAME( - c, LOC(st), LOAD_GLOBAL, &_Py_ID(__conditional_annotations__), names); - } - ADDOP_I(c, LOC(st), CONTAINS_OP, 0); - ADDOP_JUMP(c, LOC(st), POP_JUMP_IF_FALSE, not_set); + PyObject *mangled = _PyCompile_Mangle(c, st->v.AnnAssign.target->v.Name.id); + if (!mangled) { + return ERROR; } - VISIT(c, expr, st->v.AnnAssign.annotation); - ADDOP_I(c, LOC(st), COPY, 2); - ADDOP_LOAD_CONST_NEW(c, LOC(st), mangled); - // stack now contains - ADDOP(c, loc, STORE_SUBSCR); - // stack now contains - - USE_LABEL(c, not_set); + int ret = codegen_deferred_annotations_entry(c, loc, scope_type, st, mangled, cond_index); + Py_DECREF(mangled); + RETURN_IF_ERROR(ret); } return SUCCESS; }