From f220069382f0d61f3573061b3cb08c12356a7331 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Wed, 12 Jun 2024 03:10:23 +0800 Subject: [PATCH 1/3] gh-120198: Fix race condition when editing __class__ with an audit hook active (GH-120195) --- Lib/test/test_super.py | 35 ++++++++++++++++++- ...-06-10-15-07-16.gh-issue-120198.WW_pjO.rst | 1 + Objects/typeobject.c | 3 +- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-10-15-07-16.gh-issue-120198.WW_pjO.rst diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 3ea01413c8e900..af350ab446d09c 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -1,9 +1,10 @@ """Unit tests for zero-argument super() & related machinery.""" import textwrap +import threading import unittest from unittest.mock import patch -from test.support import import_helper +from test.support import import_helper, threading_helper ADAPTIVE_WARMUP_DELAY = 2 @@ -478,6 +479,38 @@ def some(cls): for _ in range(ADAPTIVE_WARMUP_DELAY): C.some(C) + @threading_helper.requires_working_threading() + def test___class___modification_multithreaded(self): + """ Note: this test isn't actually testing anything on its own. + It requires a sys audithook to be set to crash on older Python. + This should be the case anyways as our test suite sets + an audit hook. + """ + class Foo: + pass + + class Bar: + pass + + thing = Foo() + def work(): + foo = thing + for _ in range(5000): + foo.__class__ = Bar + type(foo) + foo.__class__ = Foo + type(foo) + + + threads = [] + for _ in range(6): + thread = threading.Thread(target=work) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-10-15-07-16.gh-issue-120198.WW_pjO.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-10-15-07-16.gh-issue-120198.WW_pjO.rst new file mode 100644 index 00000000000000..8dc8aec44d80c4 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-10-15-07-16.gh-issue-120198.WW_pjO.rst @@ -0,0 +1 @@ +Fix a crash when multiple threads read and write to the same ``__class__`` of an object concurrently. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index bf2be42f73fdd4..9d05798e1715b4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5709,7 +5709,6 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char* static int object_set_class(PyObject *self, PyObject *value, void *closure) { - PyTypeObject *oldto = Py_TYPE(self); if (value == NULL) { PyErr_SetString(PyExc_TypeError, @@ -5729,6 +5728,8 @@ object_set_class(PyObject *self, PyObject *value, void *closure) return -1; } + PyTypeObject *oldto = Py_TYPE(self); + /* In versions of CPython prior to 3.5, the code in compatible_for_assignment was not set up to correctly check for memory layout / slot / etc. compatibility for non-HEAPTYPE classes, so we just From f898c67dedd6f10642ddaa3176e99b2c8fd5be69 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:59:39 +0800 Subject: [PATCH 2/3] Update typeobject.c --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 9d05798e1715b4..0aa2876536f8ed 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5728,7 +5728,7 @@ object_set_class(PyObject *self, PyObject *value, void *closure) return -1; } - PyTypeObject *oldto = Py_TYPE(self); + PyTypeObject *oldto = Py_TYPE(self); /* In versions of CPython prior to 3.5, the code in compatible_for_assignment was not set up to correctly check for memory From 88c2ace489b46d373915de1642101d817f605521 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:59:53 +0800 Subject: [PATCH 3/3] Update typeobject.c Co-Authored-By: Nadeshiko Manju --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0aa2876536f8ed..9d05798e1715b4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5728,7 +5728,7 @@ object_set_class(PyObject *self, PyObject *value, void *closure) return -1; } - PyTypeObject *oldto = Py_TYPE(self); + PyTypeObject *oldto = Py_TYPE(self); /* In versions of CPython prior to 3.5, the code in compatible_for_assignment was not set up to correctly check for memory