Skip to content

Commit e8a9f0c

Browse files
gh-120198: Fix race condition when editing __class__ with an audit hook active (GH-120195)
Co-authored-by: Nadeshiko Manju <[email protected]>
1 parent 0315fdc commit e8a9f0c

File tree

3 files changed

+37
-2
lines changed

3 files changed

+37
-2
lines changed

Lib/test/test_super.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Unit tests for zero-argument super() & related machinery."""
22

33
import textwrap
4+
import threading
45
import unittest
56
from unittest.mock import patch
6-
from test.support import import_helper
7+
from test.support import import_helper, threading_helper
78

89

910
ADAPTIVE_WARMUP_DELAY = 2
@@ -478,6 +479,38 @@ def some(cls):
478479
for _ in range(ADAPTIVE_WARMUP_DELAY):
479480
C.some(C)
480481

482+
@threading_helper.requires_working_threading()
483+
def test___class___modification_multithreaded(self):
484+
""" Note: this test isn't actually testing anything on its own.
485+
It requires a sys audithook to be set to crash on older Python.
486+
This should be the case anyways as our test suite sets
487+
an audit hook.
488+
"""
489+
class Foo:
490+
pass
491+
492+
class Bar:
493+
pass
494+
495+
thing = Foo()
496+
def work():
497+
foo = thing
498+
for _ in range(5000):
499+
foo.__class__ = Bar
500+
type(foo)
501+
foo.__class__ = Foo
502+
type(foo)
503+
504+
505+
threads = []
506+
for _ in range(6):
507+
thread = threading.Thread(target=work)
508+
thread.start()
509+
threads.append(thread)
510+
511+
for thread in threads:
512+
thread.join()
513+
481514

482515
if __name__ == "__main__":
483516
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash when multiple threads read and write to the same ``__class__`` of an object concurrently.

Objects/typeobject.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5709,7 +5709,6 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char*
57095709
static int
57105710
object_set_class(PyObject *self, PyObject *value, void *closure)
57115711
{
5712-
PyTypeObject *oldto = Py_TYPE(self);
57135712

57145713
if (value == NULL) {
57155714
PyErr_SetString(PyExc_TypeError,
@@ -5729,6 +5728,8 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
57295728
return -1;
57305729
}
57315730

5731+
PyTypeObject *oldto = Py_TYPE(self);
5732+
57325733
/* In versions of CPython prior to 3.5, the code in
57335734
compatible_for_assignment was not set up to correctly check for memory
57345735
layout / slot / etc. compatibility for non-HEAPTYPE classes, so we just

0 commit comments

Comments
 (0)