diff --git a/Lib/test/test_ordered_dict.py b/Lib/test/test_ordered_dict.py index 9f131a9110dccb..e93e3bfb61ae06 100644 --- a/Lib/test/test_ordered_dict.py +++ b/Lib/test/test_ordered_dict.py @@ -1,3 +1,4 @@ +import abc import builtins import contextlib import copy @@ -5,7 +6,7 @@ import operator import pickle import re -from random import randrange, shuffle +from random import randrange, shuffle, randint import struct import sys import unittest @@ -729,6 +730,22 @@ def test_merge_operator(self): with self.assertRaises(ValueError): a |= "BAD" + def test_unstable_hash_should_not_abort_issue132461(self): + # OrderedDict should raise, not abort, when used with unstable-hash keys + large_num = 2**64 + + class WeirdBase(abc.ABCMeta): + def __hash__(self): + return randint(0, large_num) + + class weird_bytes(bytes, metaclass=WeirdBase): + pass + + obj = self.OrderedDict() + with self.assertRaises(Exception): + for x in range(100): + obj.setdefault(weird_bytes, None) + @support.cpython_only def test_ordered_dict_items_result_gc(self): # bpo-42536: OrderedDict.items's tuple-reuse speed trick breaks the GC's diff --git a/Objects/odictobject.c b/Objects/odictobject.c index 1412acb50ac5ff..ce10eb2ad3b985 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -1033,7 +1033,11 @@ OrderedDict_setdefault_impl(PyODictObject *self, PyObject *key, if (result == NULL) { if (PyErr_Occurred()) return NULL; - assert(_odict_find_node(self, key) == NULL); + if (_odict_find_node(self, key) != NULL) { + PyErr_SetString(PyExc_RuntimeError, + "OrderedDict key appears to change its hash value over time"); + return NULL; + } if (PyODict_SetItem((PyObject *)self, key, default_value) >= 0) { result = Py_NewRef(default_value); }