From f904356610d7e82bbc28bebf57afed7ef7a2c1fa Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 12 Jan 2026 20:21:23 +0900 Subject: [PATCH] [3.13] gh-143189: fix insertdict() for non-Unicode key (GH-143285) (cherry picked from commit 43c76587c1ba2c3937fa6834db10cffc604e39e0) Co-authored-by: Inada Naoki --- Lib/test/test_dict.py | 19 +++++++++++++++++++ ...-12-30-06-48-48.gh-issue-143189.in_sv2.rst | 3 +++ Objects/dictobject.c | 7 +++++-- 3 files changed, 27 insertions(+), 2 deletions(-) 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/Lib/test/test_dict.py b/Lib/test/test_dict.py index c7b6f64b53b42f..52ef0c14ea3a64 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1655,6 +1655,7 @@ def make_pairs(): self.assertGreaterEqual(eq_count, 1) def test_clear_at_lookup(self): + # gh-140551 dict crash if clear is called at lookup stage class X: def __hash__(self): return 1 @@ -1675,6 +1676,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() @@ -1682,6 +1685,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 + self.assertIsInstance(list(d)[0], MyStr) + class CAPITest(unittest.TestCase): 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. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c987af31c45dd1..3e0537c18177a7 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1832,7 +1832,7 @@ static int insertdict(PyInterpreterState *interp, 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); @@ -1856,11 +1856,14 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp, } - 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(interp, mp, hash, key, value) < 0) { goto Fail; }