Skip to content

Commit 97cb44b

Browse files
committed
gh-151722: Defer GC tracking in frozendict.copy()
Fix _PyDict_Or() and frozendict.copy(): only track the frozendict by the GC once the dictionary is fully initialized. Functions modifying frozendict now ensures that the object is not tracked by the GC (in debug mode). * can_modify_dict() checks that _PyObject_GC_IS_TRACKED() is false for frozendicts. * dict_merge_api() makes sure that the dictionary is tracked by the GC.
1 parent 55bc312 commit 97cb44b

1 file changed

Lines changed: 70 additions & 37 deletions

File tree

Objects/dictobject.c

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -305,16 +305,20 @@ static inline int
305305
can_modify_dict(PyDictObject *mp)
306306
{
307307
if (PyFrozenDict_Check(mp)) {
308+
// gh-151722: A frozendict must not be tracked by the GC
309+
// when it's being modified.
310+
assert(!_PyObject_GC_IS_TRACKED(mp));
311+
308312
// No locking required to modify a newly created frozendict
309313
// since it's only accessible from the current thread.
310-
return PyUnstable_Object_IsUniquelyReferenced(_PyObject_CAST(mp));
314+
assert(PyUnstable_Object_IsUniquelyReferenced(_PyObject_CAST(mp)));
311315
}
312316
else {
313317
// Locking is only required if the dictionary is not
314318
// uniquely referenced.
315319
ASSERT_DICT_LOCKED(mp);
316-
return 1;
317320
}
321+
return 1;
318322
}
319323
#endif
320324

@@ -937,7 +941,7 @@ free_values(PyDictValues *values, bool use_qsbr)
937941
static inline PyObject *
938942
new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys,
939943
PyDictValues *values, Py_ssize_t used,
940-
int free_values_on_failure, int frozendict)
944+
int free_values_on_failure, int frozendict, int gc_track)
941945
{
942946
assert(keys != NULL);
943947
if (mp == NULL) {
@@ -956,12 +960,14 @@ new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys,
956960
((PyFrozenDictObject *)mp)->ma_hash = -1;
957961
}
958962
ASSERT_CONSISTENT(mp);
959-
_PyObject_GC_TRACK(mp);
963+
if (gc_track) {
964+
_PyObject_GC_TRACK(mp);
965+
}
960966
return (PyObject *)mp;
961967
}
962968

963969
/* Consumes a reference to the keys object */
964-
static PyObject *
970+
static PyObject*
965971
new_dict(PyDictKeysObject *keys, PyDictValues *values,
966972
Py_ssize_t used, int free_values_on_failure)
967973
{
@@ -971,16 +977,30 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values,
971977
}
972978
assert(mp == NULL || Py_IS_TYPE(mp, &PyDict_Type));
973979

974-
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0);
980+
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0, 1);
975981
}
976982

977983
/* Consumes a reference to the keys object */
978-
static PyObject *
979-
new_frozendict(PyDictKeysObject *keys, PyDictValues *values,
980-
Py_ssize_t used, int free_values_on_failure)
984+
static PyObject*
985+
new_dict_untracked(PyDictKeysObject *keys, PyDictValues *values,
986+
Py_ssize_t used, int free_values_on_failure)
987+
{
988+
PyDictObject *mp = _Py_FREELIST_POP(PyDictObject, dicts);
989+
if (mp == NULL) {
990+
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
991+
}
992+
assert(mp == NULL || Py_IS_TYPE(mp, &PyDict_Type));
993+
994+
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0, 0);
995+
}
996+
997+
/* Consumes a reference to the keys object */
998+
static PyObject*
999+
new_frozendict_untracked(PyDictKeysObject *keys, PyDictValues *values,
1000+
Py_ssize_t used, int free_values_on_failure)
9811001
{
9821002
PyDictObject *mp = PyObject_GC_New(PyDictObject, &PyFrozenDict_Type);
983-
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 1);
1003+
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 1, 0);
9841004
}
9851005

9861006
static PyObject *
@@ -3471,7 +3491,6 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34713491
}
34723492
if (PyFrozenDict_Check(d)) {
34733493
assert(can_modify_dict((PyDictObject*)d));
3474-
assert(!_PyObject_GC_IS_TRACKED(d));
34753494
}
34763495

34773496
if (PyDict_CheckExact(d)) {
@@ -4076,6 +4095,7 @@ merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override)
40764095
assert(d != NULL);
40774096
assert(PyAnyDict_Check(d));
40784097
assert(seq2 != NULL);
4098+
assert(can_modify_dict((PyDictObject*)d));
40794099

40804100
it = PyObject_GetIter(seq2);
40814101
if (it == NULL)
@@ -4277,6 +4297,8 @@ dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey)
42774297
assert(0 <= override && override <= 2);
42784298

42794299
PyDictObject *mp = _PyAnyDict_CAST(a);
4300+
assert(can_modify_dict(mp));
4301+
42804302
int res = 0;
42814303
if (PyAnyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) {
42824304
PyDictObject *other = (PyDictObject*)b;
@@ -4382,9 +4404,7 @@ dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey)
43824404
}
43834405

43844406
int res = dict_merge(a, b, override, dupkey);
4385-
if (PyDict_Check(a)) {
4386-
assert(_PyObject_GC_IS_TRACKED(a));
4387-
}
4407+
assert(_PyObject_GC_IS_TRACKED(a));
43884408
return res;
43894409
}
43904410

@@ -4442,7 +4462,7 @@ copy_values(PyDictValues *values)
44424462
}
44434463

44444464
static PyObject *
4445-
copy_lock_held(PyObject *o, int as_frozendict)
4465+
copy_lock_held_untracked(PyObject *o, int as_frozendict)
44464466
{
44474467
PyObject *copy;
44484468
PyDictObject *mp;
@@ -4455,12 +4475,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44554475
mp = (PyDictObject *)o;
44564476
if (mp->ma_used == 0) {
44574477
/* The dict is empty; just return a new dict. */
4478+
PyObject *d;
44584479
if (as_frozendict) {
4459-
return PyFrozenDict_New(NULL);
4480+
d = frozendict_new_untracked(&PyFrozenDict_Type);
44604481
}
44614482
else {
4462-
return PyDict_New();
4483+
d = dict_new_untracked(&PyDict_Type);
44634484
}
4485+
assert(!_PyObject_GC_IS_TRACKED(d));
4486+
return d;
44644487
}
44654488

44664489
if (_PyDict_HasSplitTable(mp)) {
@@ -4492,7 +4515,7 @@ copy_lock_held(PyObject *o, int as_frozendict)
44924515
PyFrozenDictObject *frozen = (PyFrozenDictObject *)split_copy;
44934516
frozen->ma_hash = -1;
44944517
}
4495-
_PyObject_GC_TRACK(split_copy);
4518+
assert(!_PyObject_GC_IS_TRACKED(split_copy));
44964519
return (PyObject *)split_copy;
44974520
}
44984521

@@ -4520,10 +4543,10 @@ copy_lock_held(PyObject *o, int as_frozendict)
45204543
}
45214544
PyDictObject *new;
45224545
if (as_frozendict) {
4523-
new = (PyDictObject *)new_frozendict(keys, NULL, 0, 0);
4546+
new = (PyDictObject *)new_frozendict_untracked(keys, NULL, 0, 0);
45244547
}
45254548
else {
4526-
new = (PyDictObject *)new_dict(keys, NULL, 0, 0);
4549+
new = (PyDictObject *)new_dict_untracked(keys, NULL, 0, 0);
45274550
}
45284551
if (new == NULL) {
45294552
/* In case of an error, new_dict()/new_frozendict() takes care of
@@ -4533,14 +4556,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
45334556

45344557
new->ma_used = mp->ma_used;
45354558
ASSERT_CONSISTENT(new);
4559+
assert(!_PyObject_GC_IS_TRACKED(new));
45364560
return (PyObject *)new;
45374561
}
45384562

45394563
if (as_frozendict) {
4540-
copy = PyFrozenDict_New(NULL);
4564+
copy = frozendict_new_untracked(&PyFrozenDict_Type);
45414565
}
45424566
else {
4543-
copy = PyDict_New();
4567+
copy = dict_new_untracked(&PyDict_Type);
45444568
}
45454569
if (copy == NULL)
45464570
return NULL;
@@ -4549,9 +4573,7 @@ copy_lock_held(PyObject *o, int as_frozendict)
45494573
return NULL;
45504574
}
45514575

4552-
if (PyDict_Check(copy)) {
4553-
assert(_PyObject_GC_IS_TRACKED(copy));
4554-
}
4576+
assert(!_PyObject_GC_IS_TRACKED(copy));
45554577
return copy;
45564578
}
45574579

@@ -4565,25 +4587,28 @@ PyDict_Copy(PyObject *o)
45654587

45664588
PyObject *res;
45674589
Py_BEGIN_CRITICAL_SECTION(o);
4568-
res = copy_lock_held(o, 0);
4590+
res = copy_lock_held_untracked(o, 0);
45694591
Py_END_CRITICAL_SECTION();
4592+
if (res != NULL) {
4593+
_PyObject_GC_TRACK(res);
4594+
}
45704595
return res;
45714596
}
45724597

45734598
// Similar to PyDict_Copy(), but return a frozendict if the argument
45744599
// is a frozendict.
45754600
static PyObject *
4576-
anydict_copy(PyObject *o)
4601+
anydict_copy_untracked(PyObject *o)
45774602
{
45784603
assert(PyAnyDict_Check(o));
45794604

45804605
PyObject *res;
45814606
if (PyFrozenDict_Check(o)) {
4582-
res = copy_lock_held(o, 1);
4607+
res = copy_lock_held_untracked(o, 1);
45834608
}
45844609
else {
45854610
Py_BEGIN_CRITICAL_SECTION(o);
4586-
res = copy_lock_held(o, 0);
4611+
res = copy_lock_held_untracked(o, 0);
45874612
Py_END_CRITICAL_SECTION();
45884613
}
45894614
return res;
@@ -4598,13 +4623,16 @@ _PyDict_CopyAsDict(PyObject *o)
45984623

45994624
PyObject *res;
46004625
if (PyFrozenDict_Check(o)) {
4601-
res = copy_lock_held(o, 0);
4626+
res = copy_lock_held_untracked(o, 0);
46024627
}
46034628
else {
46044629
Py_BEGIN_CRITICAL_SECTION(o);
4605-
res = copy_lock_held(o, 0);
4630+
res = copy_lock_held_untracked(o, 0);
46064631
Py_END_CRITICAL_SECTION();
46074632
}
4633+
if (res != NULL) {
4634+
_PyObject_GC_TRACK(res);
4635+
}
46084636
return res;
46094637
}
46104638

@@ -5157,14 +5185,15 @@ _PyDict_Or(PyObject *self, PyObject *other)
51575185
if (!PyAnyDict_Check(self) || !PyAnyDict_Check(other)) {
51585186
Py_RETURN_NOTIMPLEMENTED;
51595187
}
5160-
PyObject *new = anydict_copy(self);
5188+
PyObject *new = anydict_copy_untracked(self);
51615189
if (new == NULL) {
51625190
return NULL;
51635191
}
51645192
if (dict_update_arg(new, other)) {
51655193
Py_DECREF(new);
51665194
return NULL;
51675195
}
5196+
_PyObject_GC_TRACK(new);
51685197
return new;
51695198
}
51705199

@@ -5352,7 +5381,6 @@ dict_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds
53525381
if (self == NULL) {
53535382
return NULL;
53545383
}
5355-
assert(!_PyObject_GC_IS_TRACKED(self));
53565384
_PyObject_GC_TRACK(self);
53575385
return self;
53585386
}
@@ -5434,7 +5462,7 @@ frozendict_vectorcall(PyObject *type, PyObject * const*args,
54345462
}
54355463
}
54365464
}
5437-
assert(!_PyObject_GC_IS_TRACKED(self));
5465+
54385466
_PyObject_GC_TRACK(self);
54395467
return self;
54405468
}
@@ -6753,10 +6781,12 @@ dictitems_xor_lock_held(PyObject *d1, PyObject *d2)
67536781
ASSERT_DICT_LOCKED(d1);
67546782
ASSERT_DICT_LOCKED(d2);
67556783

6756-
PyObject *temp_dict = copy_lock_held(d1, 0);
6784+
PyObject *temp_dict = copy_lock_held_untracked(d1, 0);
67576785
if (temp_dict == NULL) {
67586786
return NULL;
67596787
}
6788+
_PyObject_GC_TRACK(temp_dict);
6789+
67606790
PyObject *result_set = PySet_New(NULL);
67616791
if (result_set == NULL) {
67626792
Py_CLEAR(temp_dict);
@@ -8488,7 +8518,6 @@ frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
84888518
assert(kwds == NULL);
84898519
}
84908520

8491-
assert(!_PyObject_GC_IS_TRACKED(d));
84928521
_PyObject_GC_TRACK(d);
84938522
return d;
84948523
}
@@ -8533,7 +8562,11 @@ frozendict_copy_impl(PyFrozenDictObject *self)
85338562
return Py_NewRef(self);
85348563
}
85358564

8536-
return anydict_copy((PyObject*)self);
8565+
PyObject *copy = anydict_copy_untracked((PyObject*)self);
8566+
if (copy != NULL) {
8567+
_PyObject_GC_TRACK(copy);
8568+
}
8569+
return copy;
85378570
}
85388571

85398572

0 commit comments

Comments
 (0)