From 33dab1bdae0974ec1e1eb076b7b9b0f39c204936 Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Tue, 13 Oct 2026 00:07:34 -0300 Subject: [PATCH 1/2] gh-159285: Fix crash when copying an empty dict fails with MemoryError (GH-159286) copy_lock_held_untracked() asserted that the new empty dict was not tracked by the GC without checking whether its allocation had failed, so a debug build dereferenced NULL instead of raising MemoryError. This affected dict.copy(), PyDict_Copy() and _PyDict_CopyAsDict(), which type() uses for the class namespace. (cherry picked from commit f9132f1c555f0e47d7e38251f96adaf0355f9c08) Co-authored-by: Christian Aurich Zanettini Martins --- Lib/test/test_dict.py | 7 +++++++ .../2026-10-12-23-32-50.gh-issue-159285.nlLuwQ.rst | 2 ++ Objects/dictobject.c | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-23-32-50.gh-issue-159285.nlLuwQ.rst diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 3b9ff672a1aa03..691c6229aa0ea1 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -459,6 +459,13 @@ def test_copy_noncompact(self): d2 = d.copy() self.assertEqual(d2, d) + @support.nomemtest + def test_copy_empty_no_memory(self): + import _testcapi + d = {} + with self.assertRaises(MemoryError): + _testcapi.call_with_nomemory(0, 0, d.copy) + def test_get(self): d = {} self.assertIs(d.get('c'), None) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-23-32-50.gh-issue-159285.nlLuwQ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-23-32-50.gh-issue-159285.nlLuwQ.rst new file mode 100644 index 00000000000000..0cb795f8309f73 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-23-32-50.gh-issue-159285.nlLuwQ.rst @@ -0,0 +1,2 @@ +Fix a crash in a debug build when copying an empty :class:`dict` fails with +:exc:`MemoryError`. Patch by Christian Aurich Zanettini Martins. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e900ef33cc42f5..89b4abd596761c 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4448,7 +4448,7 @@ copy_lock_held_untracked(PyObject *o, int as_frozendict) else { d = dict_new_untracked(&PyDict_Type); } - assert(!_PyObject_GC_IS_TRACKED(d)); + assert(d == NULL || !_PyObject_GC_IS_TRACKED(d)); return d; } From 08e8461c06a62ffb9855df73ad30c18d6e06cb3b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 13 Oct 2026 13:55:00 +0900 Subject: [PATCH 2/2] Apply suggestion from @methane --- Lib/test/test_dict.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 691c6229aa0ea1..4fc851c0ce464b 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -463,8 +463,13 @@ def test_copy_noncompact(self): def test_copy_empty_no_memory(self): import _testcapi d = {} + d_copy = d.copy with self.assertRaises(MemoryError): - _testcapi.call_with_nomemory(0, 0, d.copy) + try: + _testcapi.set_nomemory(0) + d_copy() + finally: + _testcapi.remove_mem_hooks() def test_get(self): d = {}