Commit e3510cf7 authored by Armin Rigo's avatar Armin Rigo
Browse files

New argument "onerror" on ffi.callback()

parent e12558a3ce6b
......@@ -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;
......
......@@ -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;
......
......@@ -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:
......@@ -1205,6 +1211,30 @@ ValueError: 42
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
......
......@@ -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:
......
......@@ -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
......
......@@ -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):
......
......@@ -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"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment