Skip to content

Commit f5df0c3

Browse files
committed
Fixes for type_modified_unlocked().
* Since type_modified_unlocked() can be called with the world stopped then we need to re-start if making re-entrant calls (like calling type watcher callbacks). * Re-order code before type_modified_unlocked(). This fixes a potential bug if called functions end up re-assigning the type version tag. If they do, the cache can contain invalid entries. We should do the dictionary update first then set the version to zero. * Replace 'is_new_type' with 'world_stopped' boolean parameter. This seems a bit more clear.
1 parent a81e9e3 commit f5df0c3

File tree

1 file changed

+54
-37
lines changed

1 file changed

+54
-37
lines changed

Objects/typeobject.c

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,7 @@ set_version_unlocked(PyTypeObject *tp, unsigned int version)
10881088
}
10891089

10901090
static void
1091-
type_modified_unlocked(PyTypeObject *type)
1091+
type_modified_unlocked(PyTypeObject *type, bool world_stopped)
10921092
{
10931093
/* Invalidate any cached data for the specified type and all
10941094
subclasses. This function is called after the base
@@ -1105,6 +1105,15 @@ type_modified_unlocked(PyTypeObject *type)
11051105
We don't assign new version tags eagerly, but only as
11061106
needed.
11071107
*/
1108+
#ifdef Py_DEBUG
1109+
if (world_stopped) {
1110+
assert(types_world_is_stopped());
1111+
} else {
1112+
if (TYPE_IS_REVEALED(type)) {
1113+
ASSERT_TYPE_LOCK_HELD();
1114+
}
1115+
}
1116+
#endif
11081117
if (type->tp_version_tag == 0) {
11091118
return;
11101119
}
@@ -1122,7 +1131,7 @@ type_modified_unlocked(PyTypeObject *type)
11221131
if (subclass == NULL) {
11231132
continue;
11241133
}
1125-
type_modified_unlocked(subclass);
1134+
type_modified_unlocked(subclass, world_stopped);
11261135
Py_DECREF(subclass);
11271136
}
11281137
}
@@ -1139,6 +1148,9 @@ type_modified_unlocked(PyTypeObject *type)
11391148
if (type->tp_watched) {
11401149
// Note that PyErr_FormatUnraisable is re-entrant and the watcher
11411150
// callback might be too.
1151+
if (world_stopped) {
1152+
types_start_world();
1153+
}
11421154
PyInterpreterState *interp = _PyInterpreterState_GET();
11431155
int bits = type->tp_watched;
11441156
int i = 0;
@@ -1155,6 +1167,9 @@ type_modified_unlocked(PyTypeObject *type)
11551167
i++;
11561168
bits >>= 1;
11571169
}
1170+
if (world_stopped) {
1171+
types_stop_world();
1172+
}
11581173
}
11591174
}
11601175

@@ -1167,7 +1182,7 @@ PyType_Modified(PyTypeObject *type)
11671182
}
11681183

11691184
BEGIN_TYPE_LOCK();
1170-
type_modified_unlocked(type);
1185+
type_modified_unlocked(type, false);
11711186
END_TYPE_LOCK();
11721187
}
11731188

@@ -1195,7 +1210,7 @@ has_custom_mro(PyTypeObject *tp)
11951210
}
11961211

11971212
static void
1198-
type_mro_modified(PyTypeObject *type, PyObject *bases, bool is_new_type)
1213+
type_mro_modified(PyTypeObject *type, PyObject *bases, bool world_stopped)
11991214
{
12001215
ASSERT_NEW_OR_STOPPED(type);
12011216
/*
@@ -1212,12 +1227,12 @@ type_mro_modified(PyTypeObject *type, PyObject *bases, bool is_new_type)
12121227
*/
12131228
if (!Py_IS_TYPE(type, &PyType_Type)) {
12141229
unsigned int meta_version = Py_TYPE(type)->tp_version_tag;
1215-
if (!is_new_type) {
1230+
if (world_stopped) {
12161231
types_start_world();
12171232
}
12181233
// This can be re-entrant.
12191234
bool is_custom = has_custom_mro(type);
1220-
if (!is_new_type) {
1235+
if (world_stopped) {
12211236
types_stop_world();
12221237
}
12231238
if (meta_version != Py_TYPE(type)->tp_version_tag) {
@@ -1621,7 +1636,7 @@ type_set_abstractmethods(PyObject *tp, PyObject *value, void *Py_UNUSED(closure)
16211636
}
16221637

16231638
types_stop_world();
1624-
type_modified_unlocked(type);
1639+
type_modified_unlocked(type, true);
16251640
if (abstract)
16261641
type_set_flags_with_mask(type, 0, Py_TPFLAGS_IS_ABSTRACT);
16271642
else
@@ -1675,17 +1690,17 @@ static int recurse_down_subclasses(PyTypeObject *type, PyObject *name,
16751690
update_callback callback, void *data);
16761691

16771692
static int
1678-
mro_hierarchy(PyTypeObject *type, PyObject *temp, bool is_new_type)
1693+
mro_hierarchy(PyTypeObject *type, PyObject *temp, bool world_stopped)
16791694
{
16801695
#ifdef Py_DEBUG
1681-
if (is_new_type) {
1696+
if (!world_stopped) {
16821697
assert(!TYPE_IS_REVEALED(type));
16831698
} else {
16841699
assert(types_world_is_stopped());
16851700
}
16861701
#endif
16871702
PyObject *old_mro;
1688-
int res = mro_internal(type, 0, &old_mro, is_new_type);
1703+
int res = mro_internal(type, 0, &old_mro, world_stopped);
16891704
if (res <= 0) {
16901705
/* error / reentrance */
16911706
return res;
@@ -1735,7 +1750,7 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp, bool is_new_type)
17351750
Py_ssize_t n = PyList_GET_SIZE(subclasses);
17361751
for (Py_ssize_t i = 0; i < n; i++) {
17371752
PyTypeObject *subclass = _PyType_CAST(PyList_GET_ITEM(subclasses, i));
1738-
res = mro_hierarchy(subclass, temp, is_new_type);
1753+
res = mro_hierarchy(subclass, temp, world_stopped);
17391754
if (res < 0) {
17401755
break;
17411756
}
@@ -1826,7 +1841,7 @@ type_set_bases_world_stopped(PyTypeObject *type, PyObject *new_bases, PyTypeObje
18261841
if (temp == NULL) {
18271842
goto bail;
18281843
}
1829-
if (mro_hierarchy(type, temp, false) < 0) {
1844+
if (mro_hierarchy(type, temp, true) < 0) {
18301845
goto undo;
18311846
}
18321847
Py_DECREF(temp);
@@ -3464,7 +3479,7 @@ mro_check(PyTypeObject *type, PyObject *mro)
34643479
- through a finalizer of the return value of mro().
34653480
*/
34663481
static PyObject *
3467-
mro_invoke(PyTypeObject *type, bool is_new_type)
3482+
mro_invoke(PyTypeObject *type, bool world_stopped)
34683483
{
34693484
PyObject *mro_result;
34703485
PyObject *new_mro;
@@ -3474,7 +3489,7 @@ mro_invoke(PyTypeObject *type, bool is_new_type)
34743489
if (custom) {
34753490
// Custom mro() method on metaclass. This is potentially re-entrant.
34763491
// We are called either from type_ready() or from type_set_bases().
3477-
if (!is_new_type) {
3492+
if (world_stopped) {
34783493
// Called for type_set_bases(), re-assigment of __bases__
34793494
types_start_world();
34803495
}
@@ -3486,7 +3501,7 @@ mro_invoke(PyTypeObject *type, bool is_new_type)
34863501
else {
34873502
new_mro = NULL;
34883503
}
3489-
if (!is_new_type) {
3504+
if (world_stopped) {
34903505
types_stop_world();
34913506
}
34923507
}
@@ -3542,10 +3557,10 @@ mro_invoke(PyTypeObject *type, bool is_new_type)
35423557
- Returns -1 in case of an error.
35433558
*/
35443559
static int
3545-
mro_internal(PyTypeObject *type, int initial, PyObject **p_old_mro, bool is_new_type)
3560+
mro_internal(PyTypeObject *type, int initial, PyObject **p_old_mro, bool world_stopped)
35463561
{
35473562
#ifdef Py_DEBUG
3548-
if (is_new_type) {
3563+
if (!world_stopped) {
35493564
assert(!TYPE_IS_REVEALED(type));
35503565
} else {
35513566
assert(types_world_is_stopped());
@@ -3559,7 +3574,7 @@ mro_internal(PyTypeObject *type, int initial, PyObject **p_old_mro, bool is_new_
35593574
Don't let old_mro be GC'ed and its address be reused for
35603575
another object, like (suddenly!) a new tp_mro. */
35613576
old_mro = Py_XNewRef(lookup_tp_mro(type));
3562-
new_mro = mro_invoke(type, is_new_type); /* might cause reentrance */
3577+
new_mro = mro_invoke(type, world_stopped); /* might cause reentrance */
35633578
reent = (lookup_tp_mro(type) != old_mro);
35643579
Py_XDECREF(old_mro);
35653580
if (new_mro == NULL) {
@@ -3573,14 +3588,14 @@ mro_internal(PyTypeObject *type, int initial, PyObject **p_old_mro, bool is_new_
35733588

35743589
set_tp_mro(type, new_mro, initial);
35753590

3576-
type_mro_modified(type, new_mro, is_new_type);
3591+
type_mro_modified(type, new_mro, world_stopped);
35773592
/* corner case: the super class might have been hidden
35783593
from the custom MRO */
3579-
type_mro_modified(type, lookup_tp_bases(type), is_new_type);
3594+
type_mro_modified(type, lookup_tp_bases(type), world_stopped);
35803595

35813596
// XXX Expand this to Py_TPFLAGS_IMMUTABLETYPE?
35823597
if (!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)) {
3583-
type_modified_unlocked(type);
3598+
type_modified_unlocked(type, world_stopped);
35843599
}
35853600
else {
35863601
/* For static builtin types, this is only called during init
@@ -6162,7 +6177,7 @@ _Py_type_getattro(PyObject *tp, PyObject *name)
61626177
// the type versions.
61636178
static int
61646179
type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name,
6165-
PyObject *value, PyObject **old_value)
6180+
PyObject *value, PyObject **old_value, bool world_stopped)
61666181
{
61676182
// We don't want any re-entrancy between when we update the dict
61686183
// and call type_modified_unlocked, including running the destructor
@@ -6174,20 +6189,20 @@ type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name,
61746189
return -1;
61756190
}
61766191

6177-
/* Clear the VALID_VERSION flag of 'type' and all its
6178-
subclasses. This could possibly be unified with the
6179-
update_subclasses() recursion in update_slot(), but carefully:
6180-
they each have their own conditions on which to stop
6181-
recursing into subclasses. */
6182-
type_modified_unlocked(type);
6183-
61846192
if (_PyDict_SetItem_LockHeld(dict, name, value) < 0) {
61856193
PyErr_Format(PyExc_AttributeError,
61866194
"type object '%.50s' has no attribute '%U'",
61876195
((PyTypeObject*)type)->tp_name, name);
61886196
return -2;
61896197
}
61906198

6199+
/* Clear the VALID_VERSION flag of 'type' and all its
6200+
subclasses. This could possibly be unified with the
6201+
update_subclasses() recursion in update_slot(), but carefully:
6202+
they each have their own conditions on which to stop
6203+
recursing into subclasses. */
6204+
type_modified_unlocked(type, world_stopped);
6205+
61916206
return 0;
61926207
}
61936208

@@ -6267,7 +6282,8 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
62676282
// that the dict object mutex is held. So, we acquire it here to
62686283
// avoid those assert failing.
62696284
Py_BEGIN_CRITICAL_SECTION(dict);
6270-
res = type_update_dict(type, (PyDictObject *)dict, name, value, &old_value);
6285+
res = type_update_dict(type, (PyDictObject *)dict, name, value,
6286+
&old_value, true);
62716287
if (res == 0) {
62726288
res = update_slot(type, name);
62736289
}
@@ -6276,7 +6292,8 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
62766292
} else {
62776293
// The name is not a slot.
62786294
BEGIN_TYPE_DICT_LOCK(dict);
6279-
res = type_update_dict(type, (PyDictObject *)dict, name, value, &old_value);
6295+
res = type_update_dict(type, (PyDictObject *)dict, name, value,
6296+
&old_value, false);
62806297
END_TYPE_DICT_LOCK();
62816298
}
62826299
if (res == -2) {
@@ -8625,7 +8642,7 @@ type_ready_mro(PyTypeObject *type, int initial)
86258642
}
86268643

86278644
/* Calculate method resolution order */
8628-
if (mro_internal(type, initial, NULL, true) < 0) {
8645+
if (mro_internal(type, initial, NULL, false) < 0) {
86298646
return -1;
86308647
}
86318648
PyObject *mro = lookup_tp_mro(type);
@@ -11434,15 +11451,15 @@ update_all_slots(PyTypeObject* type)
1143411451
{
1143511452
pytype_slotdef *p;
1143611453

11437-
ASSERT_NEW_OR_STOPPED(type);
11438-
11439-
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
11440-
type_modified_unlocked(type);
11454+
assert(types_world_is_stopped());
1144111455

1144211456
for (p = slotdefs; p->name; p++) {
1144311457
/* update_slot returns int but can't actually fail */
1144411458
update_slot(type, p->name_strobj);
1144511459
}
11460+
11461+
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
11462+
type_modified_unlocked(type, true);
1144611463
}
1144711464

1144811465

@@ -11715,7 +11732,7 @@ PyType_Freeze(PyTypeObject *type)
1171511732

1171611733
types_stop_world();
1171711734
type_add_flags(type, Py_TPFLAGS_IMMUTABLETYPE);
11718-
type_modified_unlocked(type);
11735+
type_modified_unlocked(type, true);
1171911736
types_start_world();
1172011737

1172111738
return 0;

0 commit comments

Comments
 (0)