diff -r e9169a8c0692 Lib/test/test_dict.py --- a/Lib/test/test_dict.py Sun Oct 30 23:00:20 2016 +0200 +++ b/Lib/test/test_dict.py Mon Oct 31 19:27:21 2016 +0800 @@ -839,6 +839,32 @@ pass self._tracked(MyDict()) + @support.cpython_only + def test_getitem_knownhash(self): + from _testcapi import dict_getitem_knownhash + + d = {'x': 1, 'y': 2, 'z': 3} + self.assertEqual(dict_getitem_knownhash(d, 'x', hash('x')), 1) + self.assertEqual(dict_getitem_knownhash(d, 'y', hash('y')), 2) + self.assertEqual(dict_getitem_knownhash(d, 'z', hash('z')), 3) + + # not a dict + self.assertRaises(SystemError, dict_getitem_knownhash, [], 1, hash(1)) + # key does not exist + self.assertRaises(KeyError, dict_getitem_knownhash, {}, 1, hash(1)) + + class Exc(Exception): pass + class BadEq: + def __eq__(self, other): + raise Exc + def __hash__(self): + return 7 + + k1, k2 = BadEq(), BadEq() + d = {k1: 1} + self.assertEqual(dict_getitem_knownhash(d, k1, hash(k1)), 1) + self.assertRaises(Exc, dict_getitem_knownhash, d, k2, hash(k2)) + def make_shared_key_dict(self, n): class C: pass diff -r e9169a8c0692 Modules/_collectionsmodule.c --- a/Modules/_collectionsmodule.c Sun Oct 30 23:00:20 2016 +0200 +++ b/Modules/_collectionsmodule.c Mon Oct 31 19:27:21 2016 +0800 @@ -2300,6 +2300,8 @@ oldval = _PyDict_GetItem_KnownHash(mapping, key, hash); if (oldval == NULL) { + if (PyErr_Occurred()) + goto done; if (_PyDict_SetItem_KnownHash(mapping, key, one, hash) < 0) goto done; } else { diff -r e9169a8c0692 Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c Sun Oct 30 23:00:20 2016 +0200 +++ b/Modules/_testcapimodule.c Mon Oct 31 19:27:21 2016 +0800 @@ -238,6 +238,43 @@ return Py_None; } +static PyObject* +dict_getitem_knownhash(PyObject *self, PyObject *args) +{ + int err; + char *bytes; + PyObject *mp, *key, *result; + PyObject *hash_l, *hash_b; + + if (!PyArg_ParseTuple(args, "OOO!:dict_getitem_knownhash", + &mp, &key, &PyLong_Type, &hash_l)) { + return NULL; + } + + hash_b = PyBytes_FromStringAndSize(NULL, SIZEOF_PY_HASH_T); + if (!hash_b) { + return NULL; + } + + bytes = PyBytes_AS_STRING(hash_b); + + err = _PyLong_AsByteArray((PyLongObject *)hash_l, (unsigned char *)bytes, + PyBytes_GET_SIZE(hash_b), PY_LITTLE_ENDIAN, 1); + if (err < 0) { + Py_DECREF(hash_b); + return NULL; + } + + result = _PyDict_GetItem_KnownHash(mp, key, *((Py_hash_t *)bytes)); + Py_DECREF(hash_b); + if (result == NULL && !PyErr_Occurred()) { + _PyErr_SetKeyError(key); + return NULL; + } + + Py_XINCREF(result); + return result; +} /* Issue #4701: Check that PyObject_Hash implicitly calls * PyType_Ready if it hasn't already been called @@ -3999,6 +4036,7 @@ {"test_datetime_capi", test_datetime_capi, METH_NOARGS}, {"test_list_api", (PyCFunction)test_list_api, METH_NOARGS}, {"test_dict_iteration", (PyCFunction)test_dict_iteration,METH_NOARGS}, + {"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS}, {"test_lazy_hash_inheritance", (PyCFunction)test_lazy_hash_inheritance,METH_NOARGS}, {"test_long_api", (PyCFunction)test_long_api, METH_NOARGS}, {"test_xincref_doesnt_leak",(PyCFunction)test_xincref_doesnt_leak, METH_NOARGS}, diff -r e9169a8c0692 Objects/dictobject.c --- a/Objects/dictobject.c Sun Oct 30 23:00:20 2016 +0200 +++ b/Objects/dictobject.c Mon Oct 31 19:27:21 2016 +0800 @@ -724,8 +724,10 @@ Py_INCREF(startkey); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); Py_DECREF(startkey); - if (cmp < 0) + if (cmp < 0) { + *value_addr = NULL; return DKIX_ERROR; + } if (dk == mp->ma_keys && ep->me_key == startkey) { if (cmp > 0) { *value_addr = &ep->me_value; @@ -1428,39 +1430,24 @@ return *value_addr; } +/* This returns NULL *with* an exception set if an exception occurred. + It returns NULL *without* an exception set if the key wasn't present. +*/ PyObject * _PyDict_GetItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) { Py_ssize_t ix; PyDictObject *mp = (PyDictObject *)op; - PyThreadState *tstate; PyObject **value_addr; - if (!PyDict_Check(op)) + if (!PyDict_Check(op)) { + PyErr_BadInternalCall(); return NULL; - - /* We can arrive here with a NULL tstate during initialization: try - running "python -Wi" for an example related to string interning. - Let's just hope that no exception occurs then... This must be - _PyThreadState_Current and not PyThreadState_GET() because in debug - mode, the latter complains if tstate is NULL. */ - tstate = _PyThreadState_UncheckedGet(); - if (tstate != NULL && tstate->curexc_type != NULL) { - /* preserve the existing exception */ - PyObject *err_type, *err_value, *err_tb; - PyErr_Fetch(&err_type, &err_value, &err_tb); - ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, NULL); - /* ignore errors */ - PyErr_Restore(err_type, err_value, err_tb); - if (ix == DKIX_EMPTY) - return NULL; } - else { - ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, NULL); - if (ix == DKIX_EMPTY) { - PyErr_Clear(); - return NULL; - } + + ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, NULL); + if (ix < 0) { + return NULL; } return *value_addr; } @@ -2435,8 +2422,13 @@ int err = 0; Py_INCREF(key); Py_INCREF(value); - if (override == 1 || _PyDict_GetItem_KnownHash(a, key, hash) == NULL) + if (override == 1) err = insertdict(mp, key, hash, value); + else if (_PyDict_GetItem_KnownHash(a, key, hash) == NULL) { + if (PyErr_Occurred()) + PyErr_Clear(); + err = insertdict(mp, key, hash, value); + } else if (override != 0) { _PyErr_SetKeyError(key); Py_DECREF(value);