[pypy-commit] cffi default: New argument "onerror" on ffi.callback()

arigo noreply at buildbot.pypy.org
Sat Jul 4 21:51:56 CEST 2015


Author: Armin Rigo <arigo at 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"


More information about the pypy-commit mailing list