Issue #28123: _PyDict_GetItem_KnownHash() now can raise an exception as
PyDict_GetItemWithError(). Patch by Xiang Zhang.
This commit is contained in:
parent
873305b28c
commit
f0b311bd73
|
@ -1055,6 +1055,37 @@ class DictTest(unittest.TestCase):
|
||||||
support.check_free_after_iterating(self, lambda d: iter(d.values()), dict)
|
support.check_free_after_iterating(self, lambda d: iter(d.values()), dict)
|
||||||
support.check_free_after_iterating(self, lambda d: iter(d.items()), dict)
|
support.check_free_after_iterating(self, lambda d: iter(d.items()), dict)
|
||||||
|
|
||||||
|
|
||||||
|
class CAPITest(unittest.TestCase):
|
||||||
|
|
||||||
|
# Test _PyDict_GetItem_KnownHash()
|
||||||
|
@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))
|
||||||
|
|
||||||
|
|
||||||
from test import mapping_tests
|
from test import mapping_tests
|
||||||
|
|
||||||
class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):
|
class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):
|
||||||
|
|
|
@ -2300,6 +2300,8 @@ _count_elements(PyObject *self, PyObject *args)
|
||||||
|
|
||||||
oldval = _PyDict_GetItem_KnownHash(mapping, key, hash);
|
oldval = _PyDict_GetItem_KnownHash(mapping, key, hash);
|
||||||
if (oldval == NULL) {
|
if (oldval == NULL) {
|
||||||
|
if (PyErr_Occurred())
|
||||||
|
goto done;
|
||||||
if (_PyDict_SetItem_KnownHash(mapping, key, one, hash) < 0)
|
if (_PyDict_SetItem_KnownHash(mapping, key, one, hash) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -238,6 +238,26 @@ test_dict_iteration(PyObject* self)
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
dict_getitem_knownhash(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
PyObject *mp, *key, *result;
|
||||||
|
Py_ssize_t hash;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "OOn:dict_getitem_knownhash",
|
||||||
|
&mp, &key, &hash)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = _PyDict_GetItem_KnownHash(mp, key, (Py_hash_t)hash);
|
||||||
|
if (result == NULL && !PyErr_Occurred()) {
|
||||||
|
_PyErr_SetKeyError(key);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XINCREF(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/* Issue #4701: Check that PyObject_Hash implicitly calls
|
/* Issue #4701: Check that PyObject_Hash implicitly calls
|
||||||
* PyType_Ready if it hasn't already been called
|
* PyType_Ready if it hasn't already been called
|
||||||
|
@ -4003,6 +4023,7 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"test_datetime_capi", test_datetime_capi, METH_NOARGS},
|
{"test_datetime_capi", test_datetime_capi, METH_NOARGS},
|
||||||
{"test_list_api", (PyCFunction)test_list_api, METH_NOARGS},
|
{"test_list_api", (PyCFunction)test_list_api, METH_NOARGS},
|
||||||
{"test_dict_iteration", (PyCFunction)test_dict_iteration,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_lazy_hash_inheritance", (PyCFunction)test_lazy_hash_inheritance,METH_NOARGS},
|
||||||
{"test_long_api", (PyCFunction)test_long_api, METH_NOARGS},
|
{"test_long_api", (PyCFunction)test_long_api, METH_NOARGS},
|
||||||
{"test_xincref_doesnt_leak",(PyCFunction)test_xincref_doesnt_leak, METH_NOARGS},
|
{"test_xincref_doesnt_leak",(PyCFunction)test_xincref_doesnt_leak, METH_NOARGS},
|
||||||
|
|
|
@ -723,8 +723,10 @@ top:
|
||||||
Py_INCREF(startkey);
|
Py_INCREF(startkey);
|
||||||
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
|
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
|
||||||
Py_DECREF(startkey);
|
Py_DECREF(startkey);
|
||||||
if (cmp < 0)
|
if (cmp < 0) {
|
||||||
|
*value_addr = NULL;
|
||||||
return DKIX_ERROR;
|
return DKIX_ERROR;
|
||||||
|
}
|
||||||
if (dk == mp->ma_keys && ep->me_key == startkey) {
|
if (dk == mp->ma_keys && ep->me_key == startkey) {
|
||||||
if (cmp > 0) {
|
if (cmp > 0) {
|
||||||
*value_addr = &ep->me_value;
|
*value_addr = &ep->me_value;
|
||||||
|
@ -1424,39 +1426,25 @@ PyDict_GetItem(PyObject *op, PyObject *key)
|
||||||
return *value_addr;
|
return *value_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Same as PyDict_GetItemWithError() but with hash supplied by caller.
|
||||||
|
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 *
|
PyObject *
|
||||||
_PyDict_GetItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
|
_PyDict_GetItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash)
|
||||||
{
|
{
|
||||||
Py_ssize_t ix;
|
Py_ssize_t ix;
|
||||||
PyDictObject *mp = (PyDictObject *)op;
|
PyDictObject *mp = (PyDictObject *)op;
|
||||||
PyThreadState *tstate;
|
|
||||||
PyObject **value_addr;
|
PyObject **value_addr;
|
||||||
|
|
||||||
if (!PyDict_Check(op))
|
if (!PyDict_Check(op)) {
|
||||||
|
PyErr_BadInternalCall();
|
||||||
return NULL;
|
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);
|
ix = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr, NULL);
|
||||||
if (ix == DKIX_EMPTY) {
|
if (ix < 0) {
|
||||||
PyErr_Clear();
|
return NULL;
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return *value_addr;
|
return *value_addr;
|
||||||
}
|
}
|
||||||
|
@ -2431,8 +2419,16 @@ dict_merge(PyObject *a, PyObject *b, int override)
|
||||||
int err = 0;
|
int err = 0;
|
||||||
Py_INCREF(key);
|
Py_INCREF(key);
|
||||||
Py_INCREF(value);
|
Py_INCREF(value);
|
||||||
if (override == 1 || _PyDict_GetItem_KnownHash(a, key, hash) == NULL)
|
if (override == 1)
|
||||||
err = insertdict(mp, key, hash, value);
|
err = insertdict(mp, key, hash, value);
|
||||||
|
else if (_PyDict_GetItem_KnownHash(a, key, hash) == NULL) {
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
Py_DECREF(value);
|
||||||
|
Py_DECREF(key);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
err = insertdict(mp, key, hash, value);
|
||||||
|
}
|
||||||
else if (override != 0) {
|
else if (override != 0) {
|
||||||
_PyErr_SetKeyError(key);
|
_PyErr_SetKeyError(key);
|
||||||
Py_DECREF(value);
|
Py_DECREF(value);
|
||||||
|
|
Loading…
Reference in New Issue