mirror of
https://github.com/python/cpython.git
synced 2026-05-06 12:49:07 -04:00
[3.13] gh-130327: Always traverse managed dictionaries, even when inline values are available (GH-130469) (#145440)
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
This commit is contained in:
@@ -1663,6 +1663,25 @@ class DictTest(unittest.TestCase):
|
||||
self.assertEqual(d.get(key3_3), 44)
|
||||
self.assertGreaterEqual(eq_count, 1)
|
||||
|
||||
def test_overwrite_managed_dict(self):
|
||||
# GH-130327: Overwriting an object's managed dictionary with another object's
|
||||
# skipped traversal in favor of inline values, causing the GC to believe that
|
||||
# the __dict__ wasn't reachable.
|
||||
import gc
|
||||
|
||||
class Shenanigans:
|
||||
pass
|
||||
|
||||
to_be_deleted = Shenanigans()
|
||||
to_be_deleted.attr = "whatever"
|
||||
holds_reference = Shenanigans()
|
||||
holds_reference.__dict__ = to_be_deleted.__dict__
|
||||
holds_reference.ref = {"circular": to_be_deleted, "data": 42}
|
||||
|
||||
del to_be_deleted
|
||||
gc.collect()
|
||||
self.assertEqual(holds_reference.ref['data'], 42)
|
||||
self.assertEqual(holds_reference.attr, "whatever")
|
||||
def test_clear_at_lookup(self):
|
||||
# gh-140551 dict crash if clear is called at lookup stage
|
||||
class X:
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Fix erroneous clearing of an object's :attr:`~object.__dict__` if
|
||||
overwritten at runtime.
|
||||
+10
-7
@@ -4557,10 +4557,8 @@ dict_traverse(PyObject *op, visitproc visit, void *arg)
|
||||
|
||||
if (DK_IS_UNICODE(keys)) {
|
||||
if (_PyDict_HasSplitTable(mp)) {
|
||||
if (!mp->ma_values->embedded) {
|
||||
for (i = 0; i < n; i++) {
|
||||
Py_VISIT(mp->ma_values->values[i]);
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
Py_VISIT(mp->ma_values->values[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -7128,16 +7126,21 @@ PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
|
||||
if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
|
||||
PyDictObject *dict = _PyObject_ManagedDictPointer(obj)->dict;
|
||||
if (dict != NULL) {
|
||||
// GH-130327: If there's a managed dictionary available, we should
|
||||
// *always* traverse it. The dict is responsible for traversing the
|
||||
// inline values if it points to them.
|
||||
Py_VISIT(dict);
|
||||
}
|
||||
else if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
|
||||
PyDictValues *values = _PyObject_InlineValues(obj);
|
||||
if (values->valid) {
|
||||
for (Py_ssize_t i = 0; i < values->capacity; i++) {
|
||||
Py_VISIT(values->values[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Py_VISIT(_PyObject_ManagedDictPointer(obj)->dict);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user