summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2001-01-17 21:27:02 (GMT)
committerGuido van Rossum <guido@python.org>2001-01-17 21:27:02 (GMT)
commit2ffbf6b1125ca3c2a296e479af8b982854311600 (patch)
tree7642bddcd61c7ef3383109e62a116b46ae3e3bbc
parenta99202a017b69f176c27a4f2cee222a9b4fa9a11 (diff)
downloadcpython-2ffbf6b1125ca3c2a296e479af8b982854311600.zip
cpython-2ffbf6b1125ca3c2a296e479af8b982854311600.tar.gz
cpython-2ffbf6b1125ca3c2a296e479af8b982854311600.tar.bz2
Deal properly (?) with comparing recursive datastructures.
- Use the compare nesting level and in-progress dictionary properly in PyObject_RichCompare(). - Change the in-progress code to use static variables instead of globals (both the nesting level and the key for the thread dict were globals but have no reason to be globals; the key can even be a function-static variable in get_inprogress_dict()). - Rewrote try_rich_to_3way_compare() to benefit from the similarity of the three cases, making it table-driven. - In try_rich_to_3way_compare(), test for EQ before LT and GT. This turns out essential when comparing recursive UserList instances; with the old code, these would recurse into rich comparison three times for each nesting level up to NESTING_LIMIT/2, making the total number of calls in the order of 3**(NESTING_LIMIT/2)! NOTE: I'm not 100% comfortable with this. It works for the standard test suite (which compares a few trivial recursive data structures only), but I'm not sure that the in-progress dictionary is used properly by the rich comparison code. Jeremy suggested that maybe the operation should be included in the dict. Currently I presume that objects in the dict are equal unless proven otherwise, and I set the outcome for the rich comparison accordingly: true for operators EQ, LE, GE, and false for the other three. But Jeremy seems to think that there may be counter-examples where this doesn't do the right thing.
-rw-r--r--Objects/object.c131
1 files changed, 69 insertions, 62 deletions
diff --git a/Objects/object.c b/Objects/object.c
index 20950c1..7bf2a63 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -376,40 +376,28 @@ try_rich_compare_bool(PyObject *v, PyObject *w, int op)
static int
try_rich_to_3way_compare(PyObject *v, PyObject *w)
{
+ static struct { int op; int outcome; } tries[3] = {
+ /* Try this operator, and if it is true, use this outcome: */
+ {Py_EQ, 0},
+ {Py_LT, -1},
+ {Py_GT, 1},
+ };
+ int i;
+
if (v->ob_type->tp_richcompare == NULL &&
w->ob_type->tp_richcompare == NULL)
return 2; /* Shortcut */
- switch (try_rich_compare_bool(v, w, Py_LT)) {
- case -1: /* Error */
- return -1;
- case 0: /* False: not less */
- break;
- case 1: /* True: less */
- return -1;
- case 2: /* NotImplemented */
- break;
- }
- switch (try_rich_compare_bool(v, w, Py_GT)) {
- case -1: /* Error */
- return -1;
- case 0: /* False: not greater */
- break;
- case 1: /* True: greater */
- return 1;
- case 2: /* NotImplemented */
- break;
- }
- switch (try_rich_compare_bool(v, w, Py_EQ)) {
- case -1: /* Error */
- return -1;
- case 0: /* False: not equal */
- break;
- case 1: /* True: equal */
- return 0;
- case 2: /* NotImplemented */
- break;
+
+ for (i = 0; i < 3; i++) {
+ switch (try_rich_compare_bool(v, w, tries[i].op)) {
+ case -1:
+ return -1;
+ case 1:
+ return tries[i].outcome;
+ }
}
- return 2; /* XXX Even if all three returned FALSE?! */
+
+ return 2;
}
/* Try a 3-way comparison, returning an int. Return:
@@ -530,9 +518,7 @@ do_cmp(PyObject *v, PyObject *w)
return default_3way_compare(v, w);
}
-PyObject *_PyCompareState_Key;
-
-/* _PyCompareState_nesting is incremented before calling compare (for
+/* compare_nesting is incremented before calling compare (for
some types) and decremented on exit. If the count exceeds the
nesting limit, enable code to detect circular data structures.
*/
@@ -541,25 +527,31 @@ PyObject *_PyCompareState_Key;
#else
#define NESTING_LIMIT 500
#endif
-int _PyCompareState_nesting = 0;
+static int compare_nesting = 0;
static PyObject*
get_inprogress_dict(void)
{
+ static PyObject *key;
PyObject *tstate_dict, *inprogress;
+ if (key == NULL) {
+ key = PyString_InternFromString("cmp_state");
+ if (key == NULL)
+ return NULL;
+ }
+
tstate_dict = PyThreadState_GetDict();
if (tstate_dict == NULL) {
PyErr_BadInternalCall();
return NULL;
}
- inprogress = PyDict_GetItem(tstate_dict, _PyCompareState_Key);
+ inprogress = PyDict_GetItem(tstate_dict, key);
if (inprogress == NULL) {
inprogress = PyDict_New();
if (inprogress == NULL)
return NULL;
- if (PyDict_SetItem(tstate_dict, _PyCompareState_Key,
- inprogress) == -1) {
+ if (PyDict_SetItem(tstate_dict, key, inprogress) == -1) {
Py_DECREF(inprogress);
return NULL;
}
@@ -656,8 +648,8 @@ PyObject_Compare(PyObject *v, PyObject *w)
return 0;
vtp = v->ob_type;
wtp = w->ob_type;
- _PyCompareState_nesting++;
- if (_PyCompareState_nesting > NESTING_LIMIT &&
+ compare_nesting++;
+ if (compare_nesting > NESTING_LIMIT &&
(vtp->tp_as_mapping
|| PyInstance_Check(v)
|| (vtp->tp_as_sequence && !PyString_Check(v)))) {
@@ -690,7 +682,7 @@ PyObject_Compare(PyObject *v, PyObject *w)
result = do_cmp(v, w);
}
exit_cmp:
- _PyCompareState_nesting--;
+ compare_nesting--;
return result < 0 ? -1 : result;
}
@@ -738,30 +730,45 @@ PyObject_RichCompare(PyObject *v, PyObject *w, int op)
assert(Py_LT <= op && op <= Py_GE);
- if (_PyCompareState_nesting > NESTING_LIMIT) {
- /* Too deeply nested -- assume equal */
- /* XXX This is an unfair shortcut!
- Should use the same logic as PyObject_Compare. */
- switch (op) {
- case Py_LT:
- case Py_NE:
- case Py_GT:
- res = Py_False;
- break;
- case Py_LE:
- case Py_EQ:
- case Py_GE:
- res = Py_True;
- break;
+ compare_nesting++;
+ if (compare_nesting > NESTING_LIMIT &&
+ (v->ob_type->tp_as_mapping
+ || PyInstance_Check(v)
+ || (v->ob_type->tp_as_sequence && !PyString_Check(v)))) {
+ /* try to detect circular data structures */
+ PyObject *inprogress, *pair;
+
+ inprogress = get_inprogress_dict();
+ if (inprogress == NULL) {
+ res = NULL;
+ goto exit_cmp;
}
- Py_INCREF(res);
- return res;
+ pair = make_pair(v, w);
+ if (PyDict_GetItem(inprogress, pair)) {
+ /* already comparing these objects. assume
+ they're equal until shown otherwise */
+ Py_DECREF(pair);
+ if (op == Py_EQ || op == Py_LE || op == Py_GE)
+ res = Py_True;
+ else
+ res = Py_False;
+ Py_INCREF(res);
+ goto exit_cmp;
+ }
+ if (PyDict_SetItem(inprogress, pair, pair) == -1) {
+ res = NULL;
+ goto exit_cmp;
+ }
+ res = do_richcmp(v, w, op);
+ /* XXX DelItem shouldn't fail */
+ PyDict_DelItem(inprogress, pair);
+ Py_DECREF(pair);
}
-
- _PyCompareState_nesting++;
- res = do_richcmp(v, w, op);
- _PyCompareState_nesting--;
-
+ else {
+ res = do_richcmp(v, w, op);
+ }
+ exit_cmp:
+ compare_nesting--;
return res;
}