diff options
author | Barry Warsaw <barry@python.org> | 2002-04-23 22:45:44 (GMT) |
---|---|---|
committer | Barry Warsaw <barry@python.org> | 2002-04-23 22:45:44 (GMT) |
commit | f16951cffe9e4171769213bbe1aecec3d8f5200a (patch) | |
tree | 65880c11281cc4b26c89a05c0ad134da17280d30 | |
parent | 3adf8d1daef8d323c405fd157e40cbae1d636c23 (diff) | |
download | cpython-f16951cffe9e4171769213bbe1aecec3d8f5200a.zip cpython-f16951cffe9e4171769213bbe1aecec3d8f5200a.tar.gz cpython-f16951cffe9e4171769213bbe1aecec3d8f5200a.tar.bz2 |
abstract_get_bases(): Clarify exactly what the return values and
states can be for this function, and ensure that only AttributeErrors
are masked. Any other exception raised via the equivalent of
getattr(cls, '__bases__') should be propagated up.
abstract_issubclass(): If abstract_get_bases() returns NULL, we must
call PyErr_Occurred() to see if an exception is being propagated, and
return -1 or 0 as appropriate. This is the specific fix for a problem
whereby if getattr(derived, '__bases__') raised an exception, an
"undetected error" would occur (under a debug build). This nasty
situation was uncovered when writing a security proxy extension type
for the Zope3 project, where the security proxy raised a Forbidden
exception on getattr of __bases__.
PyObject_IsInstance(), PyObject_IsSubclass(): After both calls to
abstract_get_bases(), where we're setting the TypeError if the return
value is NULL, we must first check to see if an exception occurred,
and /not/ mask an existing exception.
Neil Schemenauer should double check that these changes don't break
his ExtensionClass examples (there aren't any test cases for those
examples and abstract_get_bases() was added by him in response to
problems with ExtensionClass). Neil, please add test cases if
possible!
I belive this is a bug fix candidate for Python 2.2.2.
-rw-r--r-- | Objects/abstract.c | 55 |
1 files changed, 46 insertions, 9 deletions
diff --git a/Objects/abstract.c b/Objects/abstract.c index 622e819..d10d9d4 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1861,6 +1861,32 @@ PyObject_CallFunctionObjArgs(PyObject *callable, ...) /* isinstance(), issubclass() */ +/* abstract_get_bases() has logically 4 return states, with a sort of 0th + * state that will almost never happen. + * + * 0. creating the __bases__ static string could get a MemoryError + * 1. getattr(cls, '__bases__') could raise an AttributeError + * 2. getattr(cls, '__bases__') could raise some other exception + * 3. getattr(cls, '__bases__') could return a tuple + * 4. getattr(cls, '__bases__') could return something other than a tuple + * + * Only state #3 is a non-error state and only it returns a non-NULL object + * (it returns the retrieved tuple). + * + * Any raised AttributeErrors are masked by clearing the exception and + * returning NULL. If an object other than a tuple comes out of __bases__, + * then again, the return value is NULL. So yes, these two situations + * produce exactly the same results: NULL is returned and no error is set. + * + * If some exception other than AttributeError is raised, then NULL is also + * returned, but the exception is not cleared. That's because we want the + * exception to be propagated along. + * + * Callers are expected to test for PyErr_Occurred() when the return value + * is NULL to decide whether a valid exception should be propagated or not. + * When there's no exception to propagate, it's customary for the caller to + * set a TypeError. + */ static PyObject * abstract_get_bases(PyObject *cls) { @@ -1872,13 +1898,16 @@ abstract_get_bases(PyObject *cls) if (__bases__ == NULL) return NULL; } - bases = PyObject_GetAttr(cls, __bases__); - if (bases == NULL || !PyTuple_Check(bases)) { - Py_XDECREF(bases); + if (bases == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) + PyErr_Clear(); + return NULL; + } + if (!PyTuple_Check(bases)) { + Py_DECREF(bases); return NULL; } - return bases; } @@ -1895,9 +1924,11 @@ abstract_issubclass(PyObject *derived, PyObject *cls) return 1; bases = abstract_get_bases(derived); - if (bases == NULL) + if (bases == NULL) { + if (PyErr_Occurred()) + return -1; return 0; - + } n = PyTuple_GET_SIZE(bases); for (i = 0; i < n; i++) { r = abstract_issubclass(PyTuple_GET_ITEM(bases, i), cls); @@ -1942,7 +1973,9 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls) else { PyObject *cls_bases = abstract_get_bases(cls); if (cls_bases == NULL) { - PyErr_SetString(PyExc_TypeError, + /* Do not mask errors. */ + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_TypeError, "isinstance() arg 2 must be a class or type"); return -1; } @@ -1977,7 +2010,9 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls) derived_bases = abstract_get_bases(derived); if (derived_bases == NULL) { - PyErr_SetString(PyExc_TypeError, + /* Do not mask errors */ + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_TypeError, "issubclass() arg 1 must be a class"); return -1; } @@ -1985,7 +2020,9 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls) cls_bases = abstract_get_bases(cls); if (cls_bases == NULL) { - PyErr_SetString(PyExc_TypeError, + /* Do not mask errors */ + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_TypeError, "issubclass() arg 2 must be a class"); return -1; } |