[3.15] gh-155978: Fix leak in update_slot_after_setattr() (GH-155979) (#155984)

hugovk <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/b0a43d4ec627f904e59fd4d2b7207bec01ffe25d
commit: b0a43d4ec627f904e59fd4d2b7207bec01ffe25d
branch: 3.15
author: Neil Schemenauer <[email protected]>
committer: hugovk <[email protected]>
date: 2026-08-25T08:24:31+09:00
summary:

[3.15] gh-155978: Fix leak in update_slot_after_setattr() (GH-155979) (#155984)

(cherry picked from commit f381d1634c1eddedddcb8ea5d4ae5a4dd7564822)

files:
A Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
M Lib/test/test_free_threading/test_type.py
M Objects/typeobject.c

diff --git a/Lib/test/test_free_threading/test_type.py b/Lib/test/test_free_threading/test_type.py
index 497c94170ae1387..6dac77f68829661 100644
--- a/Lib/test/test_free_threading/test_type.py
+++ b/Lib/test/test_free_threading/test_type.py
@@ -181,6 +181,27 @@ def wrapper():
         for reader in readers:
             reader.join()
 
+    def test_setattr_many_subclasses(self):
+        # gh-155978: Updating a special method queues a slot update for every
+        # affected subclass.  Keep enough subclasses alive to require
+        # heap-allocated queue chunks in addition to the stack chunk.
+        class Base:
+            pass
+
+        subclasses = [type(f"Sub{i}", (Base,), {}) for i in range(100)]
+
+        def custom_repr(self):
+            return "custom repr"
+
+        Base.__repr__ = custom_repr
+        self.assertTrue(all(repr(cls()) == "custom repr"
+                            for cls in subclasses))
+
+        del Base.__repr__
+        self.assertTrue(all(repr(cls()) != "custom repr"
+                            for cls in subclasses))
+
+
     def test_concurrent_setattr_deadlock(self):
         # gh-155400: two threads assigning to a special method of the same
         # class could deadlock.  One thread held the type lock and waited for
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
new file mode 100644
index 000000000000000..8b807d763d604a2
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-17-15-13-14.gh-issue-155978.4ztALD.rst
@@ -0,0 +1,2 @@
+Fix a memory leak in the free-threaded build when setting or deleting a
+special method (such as ``__repr__``) on a class that has many subclasses.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index b648b60cfc89dc5..0d3f98ff446aacb 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -6760,24 +6760,29 @@ static int
 update_slot_after_setattr(PyTypeObject *type, PyObject *name)
 {
 #ifdef Py_GIL_DISABLED
-    // stack allocate one chunk since that's all we need
     assert(SLOT_UPDATE_CHUNK_SIZE >= MAX_EQUIV);
     slot_update_chunk_t chunk = {0};
+    // Stack allocate the first chunk.  It is usually the only one needed but
+    // updates are queued for subclasses as well, so more chunks are needed if
+    // the type has more than SLOT_UPDATE_CHUNK_SIZE subclasses.
     slot_update_t queued_updates = {&chunk};
 
-    if (update_slot(type, name, &queued_updates) < 0) {
-        return -1;
-    }
-    if (queued_updates.head->n > 0) {
+    int res = update_slot(type, name, &queued_updates);
+    if (res == 0 && queued_updates.head->n > 0) {
         apply_type_slot_updates(&queued_updates);
         ASSERT_TYPE_LOCK_HELD();
-        // should never allocate another chunk
-        assert(chunk.prev == NULL);
     }
+    slot_update_chunk_t *cur = queued_updates.head;
+    while (cur != &chunk) {
+        slot_update_chunk_t *prev = cur->prev;
+        PyMem_Free(cur);
+        cur = prev;
+    }
+    return res;
 #else
     update_slot(type, name, NULL);
-#endif
     return 0;
+#endif
 }
 
 static int

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.