Commit 9bda1d6f authored by Raymond Hettinger's avatar Raymond Hettinger

No longer ignore exceptions raised by comparisons during key lookup.

Inspired by Armin Rigo's suggestion to do the same with dictionaries.
parent c404ff2f
...@@ -15,6 +15,12 @@ def check_pass_thru(): ...@@ -15,6 +15,12 @@ def check_pass_thru():
raise PassThru raise PassThru
yield 1 yield 1
class BadCmp:
def __hash__(self):
return 1
def __cmp__(self, other):
raise RuntimeError
class TestJointOps(unittest.TestCase): class TestJointOps(unittest.TestCase):
# Tests common to both set and frozenset # Tests common to both set and frozenset
...@@ -227,6 +233,17 @@ class TestJointOps(unittest.TestCase): ...@@ -227,6 +233,17 @@ class TestJointOps(unittest.TestCase):
f.add(s) f.add(s)
f.discard(s) f.discard(s)
def test_badcmp(self):
s = self.thetype([BadCmp()])
# Detect comparison errors during insertion and lookup
self.assertRaises(RuntimeError, self.thetype, [BadCmp(), BadCmp()])
self.assertRaises(RuntimeError, s.__contains__, BadCmp())
# Detect errors during mutating operations
if hasattr(s, 'add'):
self.assertRaises(RuntimeError, s.add, BadCmp())
self.assertRaises(RuntimeError, s.discard, BadCmp())
self.assertRaises(RuntimeError, s.remove, BadCmp())
class TestSet(TestJointOps): class TestSet(TestJointOps):
thetype = set thetype = set
......
...@@ -44,11 +44,8 @@ probe indices are computed as explained in Objects/dictobject.c. ...@@ -44,11 +44,8 @@ probe indices are computed as explained in Objects/dictobject.c.
All arithmetic on hash should ignore overflow. All arithmetic on hash should ignore overflow.
The lookup function always succeeds and nevers return NULL. This simplifies Unlike the dictionary implementation, the lookkey functions can return
and speeds client functions which do won't have to test for and handle NULL if the rich comparison returns an error.
errors. To meet that requirement, any errors generated by a user defined
__cmp__() function are simply cleared and ignored.
Previously outstanding exceptions are maintained.
*/ */
static setentry * static setentry *
...@@ -60,10 +57,7 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash) ...@@ -60,10 +57,7 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash)
register unsigned int mask = so->mask; register unsigned int mask = so->mask;
setentry *table = so->table; setentry *table = so->table;
register setentry *entry; register setentry *entry;
register int restore_error;
register int checked_error;
register int cmp; register int cmp;
PyObject *err_type, *err_value, *err_tb;
PyObject *startkey; PyObject *startkey;
i = hash & mask; i = hash & mask;
...@@ -71,31 +65,23 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash) ...@@ -71,31 +65,23 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash)
if (entry->key == NULL || entry->key == key) if (entry->key == NULL || entry->key == key)
return entry; return entry;
restore_error = checked_error = 0;
if (entry->key == dummy) if (entry->key == dummy)
freeslot = entry; freeslot = entry;
else { else {
if (entry->hash == hash) { if (entry->hash == hash) {
/* error can't have been checked yet */
checked_error = 1;
if (_PyErr_OCCURRED()) {
restore_error = 1;
PyErr_Fetch(&err_type, &err_value, &err_tb);
}
startkey = entry->key; startkey = entry->key;
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
if (cmp < 0) if (cmp < 0)
PyErr_Clear(); return NULL;
if (table == so->table && entry->key == startkey) { if (table == so->table && entry->key == startkey) {
if (cmp > 0) if (cmp > 0)
goto Done; return entry;
} }
else { else {
/* The compare did major nasty stuff to the /* The compare did major nasty stuff to the
* set: start over. * set: start over.
*/ */
entry = set_lookkey(so, key, hash); return set_lookkey(so, key, hash);
goto Done;
} }
} }
freeslot = NULL; freeslot = NULL;
...@@ -114,18 +100,10 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash) ...@@ -114,18 +100,10 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash)
if (entry->key == key) if (entry->key == key)
break; break;
if (entry->hash == hash && entry->key != dummy) { if (entry->hash == hash && entry->key != dummy) {
if (!checked_error) {
checked_error = 1;
if (_PyErr_OCCURRED()) {
restore_error = 1;
PyErr_Fetch(&err_type, &err_value,
&err_tb);
}
}
startkey = entry->key; startkey = entry->key;
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
if (cmp < 0) if (cmp < 0)
PyErr_Clear(); return NULL;
if (table == so->table && entry->key == startkey) { if (table == so->table && entry->key == startkey) {
if (cmp > 0) if (cmp > 0)
break; break;
...@@ -134,29 +112,19 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash) ...@@ -134,29 +112,19 @@ set_lookkey(PySetObject *so, PyObject *key, register long hash)
/* The compare did major nasty stuff to the /* The compare did major nasty stuff to the
* set: start over. * set: start over.
*/ */
entry = set_lookkey(so, key, hash); return set_lookkey(so, key, hash);
break;
} }
} }
else if (entry->key == dummy && freeslot == NULL) else if (entry->key == dummy && freeslot == NULL)
freeslot = entry; freeslot = entry;
} }
Done:
if (restore_error)
PyErr_Restore(err_type, err_value, err_tb);
return entry; return entry;
} }
/* /*
* Hacked up version of set_lookkey which can assume keys are always strings; * Hacked up version of set_lookkey which can assume keys are always strings;
* this assumption allows testing for errors during PyObject_Compare() to * This means we can always use _PyString_Eq directly and not have to check to
* be dropped; string-string comparisons never raise exceptions. This also * see if the comparison altered the table.
* means we don't need to go through PyObject_Compare(); we can always use
* _PyString_Eq directly.
*
* This is valuable because the general-case error handling in set_lookkey() is
* expensive, and sets with pure-string keys may be very common.
*/ */
static setentry * static setentry *
set_lookkey_string(PySetObject *so, PyObject *key, register long hash) set_lookkey_string(PySetObject *so, PyObject *key, register long hash)
...@@ -210,7 +178,7 @@ Internal routine to insert a new key into the table. ...@@ -210,7 +178,7 @@ Internal routine to insert a new key into the table.
Used both by the internal resize routine and by the public insert routine. Used both by the internal resize routine and by the public insert routine.
Eats a reference to key. Eats a reference to key.
*/ */
static void static int
set_insert_key(register PySetObject *so, PyObject *key, long hash) set_insert_key(register PySetObject *so, PyObject *key, long hash)
{ {
register setentry *entry; register setentry *entry;
...@@ -218,6 +186,8 @@ set_insert_key(register PySetObject *so, PyObject *key, long hash) ...@@ -218,6 +186,8 @@ set_insert_key(register PySetObject *so, PyObject *key, long hash)
assert(so->lookup != NULL); assert(so->lookup != NULL);
entry = so->lookup(so, key, hash); entry = so->lookup(so, key, hash);
if (entry == NULL)
return -1;
if (entry->key == NULL) { if (entry->key == NULL) {
/* UNUSED */ /* UNUSED */
so->fill++; so->fill++;
...@@ -234,6 +204,7 @@ set_insert_key(register PySetObject *so, PyObject *key, long hash) ...@@ -234,6 +204,7 @@ set_insert_key(register PySetObject *so, PyObject *key, long hash)
/* ACTIVE */ /* ACTIVE */
Py_DECREF(key); Py_DECREF(key);
} }
return 0;
} }
/* /*
...@@ -317,7 +288,11 @@ set_table_resize(PySetObject *so, int minused) ...@@ -317,7 +288,11 @@ set_table_resize(PySetObject *so, int minused)
} else { } else {
/* ACTIVE */ /* ACTIVE */
--i; --i;
set_insert_key(so, entry->key, entry->hash); if(set_insert_key(so, entry->key, entry->hash) == -1) {
if (is_oldtable_malloced)
PyMem_DEL(oldtable);
return -1;
}
} }
} }
...@@ -336,7 +311,8 @@ set_add_entry(register PySetObject *so, setentry *entry) ...@@ -336,7 +311,8 @@ set_add_entry(register PySetObject *so, setentry *entry)
assert(so->fill <= so->mask); /* at least one empty slot */ assert(so->fill <= so->mask); /* at least one empty slot */
n_used = so->used; n_used = so->used;
Py_INCREF(entry->key); Py_INCREF(entry->key);
set_insert_key(so, entry->key, entry->hash); if (set_insert_key(so, entry->key, entry->hash) == -1)
return -1;
if (!(so->used > n_used && so->fill*3 >= (so->mask+1)*2)) if (!(so->used > n_used && so->fill*3 >= (so->mask+1)*2))
return 0; return 0;
return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4); return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);
...@@ -357,7 +333,10 @@ set_add_key(register PySetObject *so, PyObject *key) ...@@ -357,7 +333,10 @@ set_add_key(register PySetObject *so, PyObject *key)
assert(so->fill <= so->mask); /* at least one empty slot */ assert(so->fill <= so->mask); /* at least one empty slot */
n_used = so->used; n_used = so->used;
Py_INCREF(key); Py_INCREF(key);
set_insert_key(so, key, hash); if (set_insert_key(so, key, hash) == -1) {
Py_DECREF(key);
return -1;
}
if (!(so->used > n_used && so->fill*3 >= (so->mask+1)*2)) if (!(so->used > n_used && so->fill*3 >= (so->mask+1)*2))
return 0; return 0;
return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4); return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);
...@@ -372,6 +351,8 @@ set_discard_entry(PySetObject *so, setentry *oldentry) ...@@ -372,6 +351,8 @@ set_discard_entry(PySetObject *so, setentry *oldentry)
PyObject *old_key; PyObject *old_key;
entry = (so->lookup)(so, oldentry->key, oldentry->hash); entry = (so->lookup)(so, oldentry->key, oldentry->hash);
if (entry == NULL)
return -1;
if (entry->key == NULL || entry->key == dummy) if (entry->key == NULL || entry->key == dummy)
return DISCARD_NOTFOUND; return DISCARD_NOTFOUND;
old_key = entry->key; old_key = entry->key;
...@@ -397,6 +378,8 @@ set_discard_key(PySetObject *so, PyObject *key) ...@@ -397,6 +378,8 @@ set_discard_key(PySetObject *so, PyObject *key)
return -1; return -1;
} }
entry = (so->lookup)(so, key, hash); entry = (so->lookup)(so, key, hash);
if (entry == NULL)
return -1;
if (entry->key == NULL || entry->key == dummy) if (entry->key == NULL || entry->key == dummy)
return DISCARD_NOTFOUND; return DISCARD_NOTFOUND;
old_key = entry->key; old_key = entry->key;
...@@ -601,7 +584,10 @@ set_merge(PySetObject *so, PyObject *otherset) ...@@ -601,7 +584,10 @@ set_merge(PySetObject *so, PyObject *otherset)
if (entry->key != NULL && if (entry->key != NULL &&
entry->key != dummy) { entry->key != dummy) {
Py_INCREF(entry->key); Py_INCREF(entry->key);
set_insert_key(so, entry->key, entry->hash); if (set_insert_key(so, entry->key, entry->hash) == -1) {
Py_DECREF(entry->key);
return -1;
}
} }
} }
return 0; return 0;
...@@ -611,6 +597,7 @@ static int ...@@ -611,6 +597,7 @@ static int
set_contains_key(PySetObject *so, PyObject *key) set_contains_key(PySetObject *so, PyObject *key)
{ {
long hash; long hash;
setentry *entry;
if (!PyString_CheckExact(key) || if (!PyString_CheckExact(key) ||
(hash = ((PyStringObject *) key)->ob_shash) == -1) { (hash = ((PyStringObject *) key)->ob_shash) == -1) {
...@@ -618,7 +605,10 @@ set_contains_key(PySetObject *so, PyObject *key) ...@@ -618,7 +605,10 @@ set_contains_key(PySetObject *so, PyObject *key)
if (hash == -1) if (hash == -1)
return -1; return -1;
} }
key = (so->lookup)(so, key, hash)->key; entry = (so->lookup)(so, key, hash);
if (entry == NULL)
return -1;
key = entry->key;
return key != NULL && key != dummy; return key != NULL && key != dummy;
} }
...@@ -626,8 +616,12 @@ static int ...@@ -626,8 +616,12 @@ static int
set_contains_entry(PySetObject *so, setentry *entry) set_contains_entry(PySetObject *so, setentry *entry)
{ {
PyObject *key; PyObject *key;
setentry *lu_entry;
key = (so->lookup)(so, entry->key, entry->hash)->key; lu_entry = (so->lookup)(so, entry->key, entry->hash);
if (lu_entry == NULL)
return -1;
key = lu_entry->key;
return key != NULL && key != dummy; return key != NULL && key != dummy;
} }
...@@ -2096,4 +2090,6 @@ test_c_api(PySetObject *so) ...@@ -2096,4 +2090,6 @@ test_c_api(PySetObject *so)
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
#undef assertRaises
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment