From 929272e58bf26c60e217722e099f2e4a0aea88f5 Mon Sep 17 00:00:00 2001 From: ligade Date: Fri, 26 Jun 2026 22:19:00 +0530 Subject: [PATCH 1/2] gh-152235: Defer GC tracking until set_init completes on tp_new path set_new() used make_new_set(), which GC-tracked the empty set before set_init() populated it from the iterable. That left the same half-built window the vectorcall path already closed, so concurrent GC / get_objects() could observe an inconsistent set and crash on edge cases. Allocate untracked in set_new() and call _PyObject_GC_TRACK() only after set_init() succeeds (skipping if already tracked for re-init). --- Objects/setobject.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index d8c38ff1c1d899..522a7c65bfc4d8 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1483,7 +1483,11 @@ frozenset_vectorcall(PyObject *type, PyObject * const*args, static PyObject * set_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - return make_new_set(type, NULL); + /* Leave GC-untracked until set_init() finishes filling (gh-152235). + * The vectorcall path uses make_new_set() and tracks at the end; the + * classic tp_new/tp_init path must do the same so a half-built set is + * never visible to gc.get_objects() / another thread during __init__. */ + return make_new_set_untracked(type, NULL); } #ifdef Py_GIL_DISABLED @@ -2750,6 +2754,7 @@ set_init(PyObject *so, PyObject *args, PyObject *kwds) { PySetObject *self = _PySet_CAST(so); PyObject *iterable = NULL; + int rv; if (!_PyArg_NoKeywords("set", kwds)) return -1; @@ -2759,19 +2764,30 @@ set_init(PyObject *so, PyObject *args, PyObject *kwds) if (_PyObject_IsUniquelyReferenced((PyObject *)self) && self->fill == 0) { self->hash = -1; if (iterable == NULL) { - return 0; + rv = 0; + } + else { + rv = set_update_local(self, iterable); } - return set_update_local(self, iterable); } - Py_BEGIN_CRITICAL_SECTION(self); - if (self->fill) - set_clear_internal((PyObject*)self); - self->hash = -1; - Py_END_CRITICAL_SECTION(); + else { + Py_BEGIN_CRITICAL_SECTION(self); + if (self->fill) + set_clear_internal((PyObject*)self); + self->hash = -1; + Py_END_CRITICAL_SECTION(); - if (iterable == NULL) - return 0; - return set_update_internal(self, iterable); + if (iterable == NULL) + rv = 0; + else + rv = set_update_internal(self, iterable); + } + + /* Track only once fully built (pairs with set_new leaving it untracked). */ + if (rv == 0 && !_PyObject_GC_IS_TRACKED(so)) { + _PyObject_GC_TRACK(so); + } + return rv; } static PyObject* From d8615cbb106009ee96ca4bee07ce07698c111077 Mon Sep 17 00:00:00 2001 From: ligade Date: Fri, 26 Jun 2026 23:22:19 +0530 Subject: [PATCH 2/2] gh-151672: Treat string fromlist like __import__ for lazy imports __lazy_import__(name, fromlist="attr") resolved to the attribute instead of the module. Normalize a string fromlist to a 1-tuple so resolve() returns the module, matching __import__ behavior. --- Python/import.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Python/import.c b/Python/import.c index 6da6faf5f28cc3..7abe6b04eb5ebe 100644 --- a/Python/import.c +++ b/Python/import.c @@ -4567,8 +4567,22 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, } } + // Match __import__: a string fromlist is a sequence of names, not a + // single attribute. Normalize to a 1-tuple so resolve() returns the + // module (gh-151672). + PyObject *fromlist_owned = NULL; + if (fromlist != NULL && PyUnicode_Check(fromlist)) { + fromlist_owned = PyTuple_Pack(1, fromlist); + if (fromlist_owned == NULL) { + Py_DECREF(abs_name); + return NULL; + } + fromlist = fromlist_owned; + } + // here, 'filter' is either NULL or is equivalent to a borrowed reference PyObject *res = _PyLazyImport_New(frame, builtins, abs_name, fromlist); + Py_XDECREF(fromlist_owned); if (res == NULL) { Py_DECREF(abs_name); return NULL; @@ -4580,12 +4594,7 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, goto error; } - if (fromlist && PyUnicode_Check(fromlist)) { - if (register_from_lazy_on_parent(tstate, abs_name, fromlist) < 0) { - goto error; - } - } - else if (fromlist && PyTuple_Check(fromlist) && + if (fromlist && PyTuple_Check(fromlist) && PyTuple_GET_SIZE(fromlist)) { for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(fromlist); i++) { if (register_from_lazy_on_parent(tstate, abs_name,