(EditableScrollablePane, ScrollablePane, EditablePane,
Pane, ScrollingMixin, EditingMixin, object))
+def raises(exc, expected, callable, *args):
+ try:
+ callable(*args)
+ except exc, msg:
+ if not str(msg).startswith(expected):
+ raise TestFailed, "Message %r, expected %r" % (str(msg),
+ expected)
+ else:
+ raise TestFailed, "Expected %s" % exc
+
mro_err_msg = """Cannot create a consistent method resolution
order (MRO) for bases """
def mro_disagreement():
if verbose: print "Testing error messages for MRO disagreement..."
- def raises(exc, expected, callable, *args):
- try:
- callable(*args)
- except exc, msg:
- if not str(msg).startswith(expected):
- raise TestFailed, "Message %r, expected %r" % (str(msg),
- expected)
- else:
- raise TestFailed, "Expected %s" % exc
class A(object): pass
class B(A): pass
class C(object): pass
vereq(t.__add__, (7,).__add__)
vereq(hash(t.__add__), hash((7,).__add__))
+def test_getattr_hooks():
+ # issue 4230
+ class Descriptor(object):
+ counter = 0
+ def __get__(self, obj, objtype=None):
+ def getter(name):
+ self.counter += 1
+ raise AttributeError(name)
+ return getter
+
+ descr = Descriptor()
+ class A(object):
+ __getattribute__ = descr
+ class B(object):
+ __getattr__ = descr
+ class C(object):
+ __getattribute__ = descr
+ __getattr__ = descr
+
+ raises(AttributeError, "attr", getattr, A(), "attr")
+ vereq(descr.counter, 1)
+ raises(AttributeError, "attr", getattr, B(), "attr")
+ vereq(descr.counter, 2)
+ raises(AttributeError, "attr", getattr, C(), "attr")
+ vereq(descr.counter, 4)
+
+ import gc
+ class EvilGetattribute(object):
+ # This used to segfault
+ def __getattr__(self, name):
+ raise AttributeError(name)
+ def __getattribute__(self, name):
+ del EvilGetattribute.__getattr__
+ for i in range(5):
+ gc.collect()
+ raise AttributeError(name)
+
+ raises(AttributeError, "attr", getattr, EvilGetattribute(), "attr")
+
def notimplemented():
# all binary methods should be able to return a NotImplemented
if verbose:
methodwrapper()
notimplemented()
test_assign_slice()
+ test_getattr_hooks()
if verbose: print "All OK"
"(O)", name);
}
+static PyObject *
+call_attribute(PyObject *self, PyObject *attr, PyObject *name)
+{
+ PyObject *res, *descr = NULL;
+ descrgetfunc f = attr->ob_type->tp_descr_get;
+
+ if (f != NULL) {
+ descr = f(attr, self, (PyObject *)(self->ob_type));
+ if (descr == NULL)
+ return NULL;
+ else
+ attr = descr;
+ }
+ res = PyObject_CallFunctionObjArgs(attr, name, NULL);
+ Py_XDECREF(descr);
+ return res;
+}
+
static PyObject *
slot_tp_getattr_hook(PyObject *self, PyObject *name)
{
if (getattribute_str == NULL)
return NULL;
}
+ /* speed hack: we could use lookup_maybe, but that would resolve the
+ method fully for each attribute lookup for classes with
+ __getattr__, even when the attribute is present. So we use
+ _PyType_Lookup and create the method only when needed, with
+ call_attribute. */
getattr = _PyType_Lookup(tp, getattr_str);
if (getattr == NULL) {
/* No __getattr__ hook: use a simpler dispatcher */
tp->tp_getattro = slot_tp_getattro;
return slot_tp_getattro(self, name);
}
+ Py_INCREF(getattr);
+ /* speed hack: we could use lookup_maybe, but that would resolve the
+ method fully for each attribute lookup for classes with
+ __getattr__, even when self has the default __getattribute__
+ method. So we use _PyType_Lookup and create the method only when
+ needed, with call_attribute. */
getattribute = _PyType_Lookup(tp, getattribute_str);
if (getattribute == NULL ||
(getattribute->ob_type == &PyWrapperDescr_Type &&
((PyWrapperDescrObject *)getattribute)->d_wrapped ==
(void *)PyObject_GenericGetAttr))
res = PyObject_GenericGetAttr(self, name);
- else
- res = PyObject_CallFunctionObjArgs(getattribute, self, name, NULL);
+ else {
+ Py_INCREF(getattribute);
+ res = call_attribute(self, getattribute, name);
+ Py_DECREF(getattribute);
+ }
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
- res = PyObject_CallFunctionObjArgs(getattr, self, name, NULL);
+ res = call_attribute(self, getattr, name);
}
+ Py_DECREF(getattr);
return res;
}