summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Rigo <arigo@tunes.org>2015-07-04 21:52:34 +0200
committerArmin Rigo <arigo@tunes.org>2015-07-04 21:52:34 +0200
commitda438dc658d9007d2d375116ce647a4b982aa227 (patch)
treeb541af40bb402b6ee556cc99c06706c92737a7cd
parenta108629085d42284fa84fcc0f563cff98dd78af7 (diff)
downloadcffi-da438dc658d9007d2d375116ce647a4b982aa227.tar.gz
New argument "onerror" on ffi.callback()
-rw-r--r--c/_cffi_backend.c51
-rw-r--r--c/ffi_obj.c12
-rw-r--r--c/test_c.py30
-rw-r--r--cffi/api.py5
-rw-r--r--cffi/backend_ctypes.py3
-rw-r--r--testing/cffi0/test_ffi_backend.py16
-rw-r--r--testing/cffi1/test_ffi_obj.py29
7 files changed, 131 insertions, 15 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
index 7f1a085..586fb28 100644
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -4725,9 +4725,11 @@ static void _my_PyErr_WriteUnraisable(PyObject *obj, char *extra_error_line)
#endif
f = PySys_GetObject("stderr");
if (f != NULL) {
- PyFile_WriteString("From cffi callback ", f);
- PyFile_WriteObject(obj, f, 0);
- PyFile_WriteString(":\n", f);
+ if (obj != NULL) {
+ PyFile_WriteString("From cffi callback ", f);
+ PyFile_WriteObject(obj, f, 0);
+ PyFile_WriteString(":\n", f);
+ }
if (extra_error_line != NULL)
PyFile_WriteString(extra_error_line, f);
PyErr_Display(t, v, tb);
@@ -4752,6 +4754,7 @@ static void invoke_callback(ffi_cif *cif, void *result, void **args,
PyObject *py_args = NULL;
PyObject *py_res = NULL;
PyObject *py_rawerr;
+ PyObject *onerror_cb;
Py_ssize_t i, n;
char *extra_error_line = NULL;
@@ -4789,7 +4792,35 @@ static void invoke_callback(ffi_cif *cif, void *result, void **args,
return;
error:
+ onerror_cb = PyTuple_GET_ITEM(cb_args, 3);
+ if (onerror_cb != Py_None) {
+ PyObject *exc1, *val1, *tb1, *res1, *exc2, *val2, *tb2;
+ PyErr_Fetch(&exc1, &val1, &tb1);
+ PyErr_NormalizeException(&exc1, &val1, &tb1);
+ res1 = PyObject_CallFunctionObjArgs(onerror_cb,
+ exc1 ? exc1 : Py_None,
+ val1 ? val1 : Py_None,
+ tb1 ? tb1 : Py_None,
+ NULL);
+ Py_XDECREF(res1);
+
+ if (!PyErr_Occurred()) {
+ Py_XDECREF(exc1);
+ Py_XDECREF(val1);
+ Py_XDECREF(tb1);
+ goto no_more_exception;
+ }
+ /* double exception! print a double-traceback... */
+ PyErr_Fetch(&exc2, &val2, &tb2);
+ PyErr_Restore(exc1, val1, tb1);
+ _my_PyErr_WriteUnraisable(py_ob, extra_error_line);
+ PyErr_Restore(exc2, val2, tb2);
+ extra_error_line = ("\nDuring the call to 'onerror', "
+ "another exception occurred:\n\n");
+ py_ob = NULL;
+ }
_my_PyErr_WriteUnraisable(py_ob, extra_error_line);
+ no_more_exception:
if (SIGNATURE(1)->ct_size > 0) {
py_rawerr = PyTuple_GET_ITEM(cb_args, 2);
memcpy(result, PyBytes_AS_STRING(py_rawerr),
@@ -4805,14 +4836,14 @@ static PyObject *b_callback(PyObject *self, PyObject *args)
{
CTypeDescrObject *ct, *ctresult;
CDataObject *cd;
- PyObject *ob, *error_ob = Py_None;
+ PyObject *ob, *error_ob = Py_None, *onerror_ob = Py_None;
PyObject *py_rawerr, *infotuple = NULL;
cif_description_t *cif_descr;
ffi_closure *closure;
Py_ssize_t size;
- if (!PyArg_ParseTuple(args, "O!O|O:callback", &CTypeDescr_Type, &ct, &ob,
- &error_ob))
+ if (!PyArg_ParseTuple(args, "O!O|OO:callback", &CTypeDescr_Type, &ct, &ob,
+ &error_ob, &onerror_ob))
return NULL;
if (!(ct->ct_flags & CT_FUNCTIONPTR)) {
@@ -4826,6 +4857,12 @@ static PyObject *b_callback(PyObject *self, PyObject *args)
Py_TYPE(ob)->tp_name);
return NULL;
}
+ if (onerror_ob != Py_None && !PyCallable_Check(onerror_ob)) {
+ PyErr_Format(PyExc_TypeError,
+ "expected a callable object for 'onerror', not %.200s",
+ Py_TYPE(onerror_ob)->tp_name);
+ return NULL;
+ }
ctresult = (CTypeDescrObject *)PyTuple_GET_ITEM(ct->ct_stuff, 1);
size = ctresult->ct_size;
@@ -4842,7 +4879,7 @@ static PyObject *b_callback(PyObject *self, PyObject *args)
return NULL;
}
}
- infotuple = Py_BuildValue("OOO", ct, ob, py_rawerr);
+ infotuple = Py_BuildValue("OOOO", ct, ob, py_rawerr, onerror_ob);
Py_DECREF(py_rawerr);
if (infotuple == NULL)
return NULL;
diff --git a/c/ffi_obj.c b/c/ffi_obj.c
index 0f407e3..3899289 100644
--- a/c/ffi_obj.c
+++ b/c/ffi_obj.c
@@ -714,11 +714,13 @@ static PyObject *_ffi_callback_decorator(PyObject *outer_args, PyObject *fn)
static PyObject *ffi_callback(FFIObject *self, PyObject *args, PyObject *kwds)
{
PyObject *c_decl, *python_callable = Py_None, *error = Py_None;
- PyObject *res;
- static char *keywords[] = {"cdecl", "python_callable", "error", NULL};
+ PyObject *res, *onerror = Py_None;
+ static char *keywords[] = {"cdecl", "python_callable", "error",
+ "onerror", NULL};
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO", keywords,
- &c_decl, &python_callable, &error))
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", keywords,
+ &c_decl, &python_callable, &error,
+ &onerror))
return NULL;
c_decl = (PyObject *)_ffi_type(self, c_decl, ACCEPT_STRING | ACCEPT_CTYPE |
@@ -726,7 +728,7 @@ static PyObject *ffi_callback(FFIObject *self, PyObject *args, PyObject *kwds)
if (c_decl == NULL)
return NULL;
- args = Py_BuildValue("(OOO)", c_decl, python_callable, error);
+ args = Py_BuildValue("(OOOO)", c_decl, python_callable, error, onerror);
if (args == NULL)
return NULL;
diff --git a/c/test_c.py b/c/test_c.py
index 65da813..f3dceb6 100644
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -1181,6 +1181,12 @@ def test_callback_exception():
BShort = new_primitive_type("short")
BFunc = new_function_type((BShort,), BShort, False)
f = callback(BFunc, Zcb1, -42)
+ #
+ seen = []
+ def oops(*args):
+ seen.append(args)
+ ff = callback(BFunc, Zcb1, -42, oops)
+ #
orig_stderr = sys.stderr
orig_getline = linecache.getline
try:
@@ -1206,6 +1212,30 @@ From cffi callback <function$Zcb1 at 0x$>:
Trying to convert the result back to C:
OverflowError: integer 60000 does not fit 'short'
""")
+ sys.stderr = cStringIO.StringIO()
+ bigvalue = 20000
+ assert len(seen) == 0
+ assert ff(bigvalue) == -42
+ assert sys.stderr.getvalue() == ""
+ assert len(seen) == 1
+ exc, val, tb = seen[0]
+ assert exc is OverflowError
+ assert str(val) == "integer 60000 does not fit 'short'"
+ #
+ seen = "not a list" # this makes the oops() function crash
+ assert ff(bigvalue) == -42
+ assert matches(sys.stderr.getvalue(), """\
+From cffi callback <function$Zcb1 at 0x$>:
+Trying to convert the result back to C:
+OverflowError: integer 60000 does not fit 'short'
+
+During the call to 'onerror', another exception occurred:
+
+Traceback (most recent call last):
+ File "$", line $, in oops
+ seen.append(args)
+AttributeError: 'str' object has no attribute 'append'
+""")
finally:
sys.stderr = orig_stderr
linecache.getline = orig_getline
diff --git a/cffi/api.py b/cffi/api.py
index 4a63ac4..e141a11 100644
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -286,7 +286,7 @@ class FFI(object):
"""
return self._backend.from_buffer(self.BCharA, python_buffer)
- def callback(self, cdecl, python_callable=None, error=None):
+ def callback(self, cdecl, python_callable=None, error=None, onerror=None):
"""Return a callback object or a decorator making such a
callback object. 'cdecl' must name a C function pointer type.
The callback invokes the specified 'python_callable' (which may
@@ -298,7 +298,8 @@ class FFI(object):
if not callable(python_callable):
raise TypeError("the 'python_callable' argument "
"is not callable")
- return self._backend.callback(cdecl, python_callable, error)
+ return self._backend.callback(cdecl, python_callable,
+ error, onerror)
if isinstance(cdecl, basestring):
cdecl = self._typeof(cdecl, consider_function_as_funcptr=True)
if python_callable is None:
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
index 5cc0c2a..b061cda 100644
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -989,7 +989,8 @@ class CTypesBackend(object):
def cast(self, BType, source):
return BType._cast_from(source)
- def callback(self, BType, source, error):
+ def callback(self, BType, source, error, onerror):
+ assert onerror is None # XXX not implemented
return BType(source, error)
typeof = type
diff --git a/testing/cffi0/test_ffi_backend.py b/testing/cffi0/test_ffi_backend.py
index 72bc650..88ee451 100644
--- a/testing/cffi0/test_ffi_backend.py
+++ b/testing/cffi0/test_ffi_backend.py
@@ -38,6 +38,22 @@ class TestFFI(backend_tests.BackendTests,
assert ffi.from_handle(ffi.cast("char *", p)) is o
py.test.raises(RuntimeError, ffi.from_handle, ffi.NULL)
+ def test_callback_onerror(self):
+ ffi = FFI(backend=self.Backend())
+ seen = []
+ def oops(*args):
+ seen.append(args)
+ def cb(n):
+ raise LookupError
+ a = ffi.callback("int(*)(int)", cb, error=42, onerror=oops)
+ res = a(2)
+ assert res == 42
+ assert len(seen) == 1
+ exc, val, tb = seen[0]
+ assert exc is LookupError
+ assert isinstance(val, LookupError)
+ assert tb.tb_frame.f_code.co_name == 'cb'
+
class TestBitfield:
def check(self, source, expected_ofs_y, expected_align, expected_size):
diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py
index f303c23..1806e46 100644
--- a/testing/cffi1/test_ffi_obj.py
+++ b/testing/cffi1/test_ffi_obj.py
@@ -104,6 +104,35 @@ def test_ffi_callback_decorator():
assert deco(lambda x: x + "")(10) == -66
assert deco(lambda x: x + 42)(10) == 52
+def test_ffi_callback_onerror():
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ def oops(*args):
+ seen.append(args)
+
+ @ffi.callback("int(int)", onerror=oops)
+ def fn1(x):
+ return x + ""
+ assert fn1(10) == 0
+
+ @ffi.callback("int(int)", onerror=oops, error=-66)
+ def fn2(x):
+ return x + ""
+ assert fn2(10) == -66
+
+ assert len(seen) == 2
+ exc, val, tb = seen[0]
+ assert exc is TypeError
+ assert isinstance(val, TypeError)
+ assert tb.tb_frame.f_code.co_name == "fn1"
+ exc, val, tb = seen[1]
+ assert exc is TypeError
+ assert isinstance(val, TypeError)
+ assert tb.tb_frame.f_code.co_name == "fn2"
+ #
+ py.test.raises(TypeError, ffi.callback, "int(int)",
+ lambda x: x, onerror=42) # <- not callable
+
def test_ffi_getctype():
ffi = _cffi1_backend.FFI()
assert ffi.getctype("int") == "int"