From f1e7bccbb15180961a46ae8de2dd430222b4174f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 30 Dec 2025 06:40:08 +0000 Subject: [PATCH 1/3] gh-143189: fix insertdict() for non-Unicode key When iserting non unicode key into split table, matching Unicode key may be in the shared split table without its value. --- Lib/test/test_dict.py | 19 +++++++++++++++++++ Objects/dictobject.c | 7 +++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 77a5f2a108d07f..fc92ffa13ae94b 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1602,6 +1602,7 @@ def __hash__(self): d.get(key2) def test_clear_at_lookup(self): + # gh-140551 dict crash if clear is called at lookup stage class X: def __hash__(self): return 1 @@ -1622,6 +1623,8 @@ def __eq__(self, other): self.assertEqual(len(d), 1) def test_split_table_update_with_str_subclass(self): + # gh-142218: inserting into a split table dictionary with a non str + # key that matches an existing key. class MyStr(str): pass class MyClass: pass obj = MyClass() @@ -1629,6 +1632,22 @@ class MyClass: pass obj.__dict__[MyStr('attr')] = 2 self.assertEqual(obj.attr, 2) + def test_split_table_insert_with_str_subclass(self): + # gh-143189: inserting into split table dictionary with a non str + # key that matches an existing key in the shared table but not in + # the dict yet. + + class MyStr(str): pass + class MyClass: pass + + obj = MyClass() + obj.attr1 = 1 + + obj2 = MyClass() + d = obj2.__dict__ + d[MyStr("attr1")] = 2 + assert isinstance(list(d)[0], MyStr) + class CAPITest(unittest.TestCase): diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 5a2bb7d3d8cd2d..a4e2fd19cefb63 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1877,7 +1877,7 @@ static int insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) { - PyObject *old_value; + PyObject *old_value = NULL; Py_ssize_t ix; ASSERT_DICT_LOCKED(mp); @@ -1898,11 +1898,14 @@ insertdict(PyDictObject *mp, goto Fail; } - if (ix == DKIX_EMPTY) { + if (old_value == NULL) { // insert_combined_dict() will convert from non DICT_KEYS_GENERAL table // into DICT_KEYS_GENERAL table if key is not Unicode. // We don't convert it before _Py_dict_lookup because non-Unicode key // may change generic table into Unicode table. + // + // NOTE: ix may not be DKIX_EMPTY because split table may have key + // without value. if (insert_combined_dict(mp, hash, key, value) < 0) { goto Fail; } From c7bf4ab2c6ac06cd97c6420b55104816f652843f Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 30 Dec 2025 06:52:28 +0000 Subject: [PATCH 2/3] add news --- .../2025-12-30-06-48-48.gh-issue-143189.in_sv2.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-30-06-48-48.gh-issue-143189.in_sv2.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-30-06-48-48.gh-issue-143189.in_sv2.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-30-06-48-48.gh-issue-143189.in_sv2.rst new file mode 100644 index 00000000000000..706b9ded20c4f1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-30-06-48-48.gh-issue-143189.in_sv2.rst @@ -0,0 +1,3 @@ +Fix crash when inserting a non-:class:`str` key into a split table +dictionary when the key matches an existing key in the split table +but has no corresponding value in the dict. From c9ae925c5706baa66293cfca72197dc137fb0a66 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 30 Dec 2025 07:08:03 +0000 Subject: [PATCH 3/3] use assertIsInstance --- Lib/test/test_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index fc92ffa13ae94b..3b4e95015e5b4c 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1646,7 +1646,7 @@ class MyClass: pass obj2 = MyClass() d = obj2.__dict__ d[MyStr("attr1")] = 2 - assert isinstance(list(d)[0], MyStr) + self.assertIsInstance(list(d)[0], MyStr) class CAPITest(unittest.TestCase):