From 8be26339cba5f933dd63549687a5e5e0560d0e40 Mon Sep 17 00:00:00 2001 From: Bhuvansh Kataria Date: Tue, 28 Jul 2026 12:45:55 +0000 Subject: [PATCH] gh-154817: Fix OrderedDict.pop() crash with inconsistent equality --- Lib/test/test_ordered_dict.py | 22 +++++++++++++++++++ ...-07-28-17-56-37.gh-issue-154817.PYfwpm.rst | 4 ++++ Objects/odictobject.c | 7 +++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-28-17-56-37.gh-issue-154817.PYfwpm.rst diff --git a/Lib/test/test_ordered_dict.py b/Lib/test/test_ordered_dict.py index ac24110cf63e823..ba6673cc9cbbb15 100644 --- a/Lib/test/test_ordered_dict.py +++ b/Lib/test/test_ordered_dict.py @@ -1014,6 +1014,28 @@ def test_weakref_list_is_not_traversed(self): gc.collect() + def test_pop_inconsistent_eq(self): + class K: + def __init__(self): + self.calls = 0 + + def __hash__(self): + return 12345 + + # Behave inconsistently across repeated equality checks. + def __eq__(self, other): + self.calls += 1 + return self.calls <= 2 + + k1 = K() + k2 = K() + + od = self.OrderedDict() + od[k1] = "value" + + with self.assertRaises(KeyError): + od.pop(k2) + class PurePythonOrderedDictSubclassTests(PurePythonOrderedDictTests): diff --git a/Misc/NEWS.d/next/Library/2026-07-28-17-56-37.gh-issue-154817.PYfwpm.rst b/Misc/NEWS.d/next/Library/2026-07-28-17-56-37.gh-issue-154817.PYfwpm.rst new file mode 100644 index 000000000000000..4e8f3bf2053eff8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-28-17-56-37.gh-issue-154817.PYfwpm.rst @@ -0,0 +1,4 @@ +Fix a crash in collections.OrderedDict.pop() when a key's __eq__ +implementation returns inconsistent results across repeated comparisons. +OrderedDict.pop() now raises KeyError instead of crashing when no default +value is provided. diff --git a/Objects/odictobject.c b/Objects/odictobject.c index c51fd4b7195da42..9c78808887e0248 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -1095,7 +1095,12 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj, /* Now delete the value from the dict. */ if (_PyDict_Pop_KnownHash((PyDictObject *)od, key, hash, &value) == 0) { - value = Py_NewRef(failobj); + if (failobj) { + value = Py_NewRef(failobj); + } + else { + PyErr_SetObject(PyExc_KeyError, key); + } } } else if (value == NULL && !PyErr_Occurred()) {