Author: Armin Rigo <ar...@tunes.org> Branch: Changeset: r2204:2b30856ad741 Date: 2015-07-04 21:52 +0200 http://bitbucket.org/cffi/cffi/changeset/2b30856ad741/
Log: New argument "onerror" on ffi.callback() diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -4725,9 +4725,11 @@ #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 @@ 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 @@ 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 @@ { 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 @@ 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 @@ 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 --- a/c/ffi_obj.c +++ b/c/ffi_obj.c @@ -714,11 +714,13 @@ 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 @@ 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 --- a/c/test_c.py +++ b/c/test_c.py @@ -1181,6 +1181,12 @@ 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 @@ 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 --- a/cffi/api.py +++ b/cffi/api.py @@ -286,7 +286,7 @@ """ 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 @@ 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 --- a/cffi/backend_ctypes.py +++ b/cffi/backend_ctypes.py @@ -989,7 +989,8 @@ 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 --- a/testing/cffi0/test_ffi_backend.py +++ b/testing/cffi0/test_ffi_backend.py @@ -38,6 +38,22 @@ 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 --- a/testing/cffi1/test_ffi_obj.py +++ b/testing/cffi1/test_ffi_obj.py @@ -104,6 +104,35 @@ 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" _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit