diff options
author | Armin Rigo <arigo@tunes.org> | 2015-07-04 21:52:34 +0200 |
---|---|---|
committer | Armin Rigo <arigo@tunes.org> | 2015-07-04 21:52:34 +0200 |
commit | da438dc658d9007d2d375116ce647a4b982aa227 (patch) | |
tree | b541af40bb402b6ee556cc99c06706c92737a7cd | |
parent | a108629085d42284fa84fcc0f563cff98dd78af7 (diff) | |
download | cffi-da438dc658d9007d2d375116ce647a4b982aa227.tar.gz |
New argument "onerror" on ffi.callback()
-rw-r--r-- | c/_cffi_backend.c | 51 | ||||
-rw-r--r-- | c/ffi_obj.c | 12 | ||||
-rw-r--r-- | c/test_c.py | 30 | ||||
-rw-r--r-- | cffi/api.py | 5 | ||||
-rw-r--r-- | cffi/backend_ctypes.py | 3 | ||||
-rw-r--r-- | testing/cffi0/test_ffi_backend.py | 16 | ||||
-rw-r--r-- | testing/cffi1/test_ffi_obj.py | 29 |
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" |