[pypy-commit] cffi default: Add "error=..." to ffi.callback().

arigo noreply at buildbot.pypy.org
Tue Jun 26 15:23:28 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r529:7cf76f8b03ba
Date: 2012-06-26 15:21 +0200
http://bitbucket.org/cffi/cffi/changeset/7cf76f8b03ba/

Log:	Add "error=..." to ffi.callback().

diff --git a/c/_ffi_backend.c b/c/_ffi_backend.c
--- a/c/_ffi_backend.c
+++ b/c/_ffi_backend.c
@@ -2921,6 +2921,11 @@
                           &ellipsis))
         return NULL;
 
+    if (fresult->ct_size < 0 && !(fresult->ct_flags & CT_VOID)) {
+        PyErr_SetString(PyExc_TypeError, "result type is of unknown size");
+        return NULL;
+    }
+
     fct = fb_prepare_ctype(&funcbuilder, fargs, fresult, ellipsis);
     if (fct == NULL)
         return NULL;
@@ -2973,6 +2978,7 @@
     PyObject *py_ob = PyTuple_GET_ITEM(cb_args, 1);
     PyObject *py_args = NULL;
     PyObject *py_res = NULL;
+    PyObject *py_rawerr;
     Py_ssize_t i, n;
 
 #define SIGNATURE(i)  ((CTypeDescrObject *)PyTuple_GET_ITEM(signature, i))
@@ -3007,8 +3013,10 @@
 
  error:
     PyErr_WriteUnraisable(py_ob);
-    if (SIGNATURE(0)->ct_size > 0)
-        memset(result, 0, SIGNATURE(0)->ct_size);
+    if (SIGNATURE(0)->ct_size > 0) {
+        py_rawerr = PyTuple_GET_ITEM(cb_args, 2);
+        memcpy(result, PyString_AS_STRING(py_rawerr), SIGNATURE(0)->ct_size);
+    }
     goto done;
     }
 
@@ -3017,13 +3025,16 @@
 
 static PyObject *b_callback(PyObject *self, PyObject *args)
 {
-    CTypeDescrObject *ct;
+    CTypeDescrObject *ct, *ctresult;
     CDataObject_own_base *cdb;
-    PyObject *ob;
+    PyObject *ob, *error_ob = Py_None;
+    PyObject *py_rawerr, *infotuple = NULL;
     cif_description_t *cif_descr;
     ffi_closure *closure;
-
-    if (!PyArg_ParseTuple(args, "O!O:callback", &CTypeDescr_Type, &ct, &ob))
+    Py_ssize_t size;
+
+    if (!PyArg_ParseTuple(args, "O!O|O:callback", &CTypeDescr_Type, &ct, &ob,
+                          &error_ob))
         return NULL;
 
     if (!(ct->ct_flags & CT_FUNCTIONPTR)) {
@@ -3038,6 +3049,26 @@
         return NULL;
     }
 
+    ctresult = (CTypeDescrObject *)PyTuple_GET_ITEM(ct->ct_stuff, 0);
+    size = ctresult->ct_size;
+    if (size < 0)
+        size = 0;
+    py_rawerr = PyString_FromStringAndSize(NULL, size);
+    if (py_rawerr == NULL)
+        return NULL;
+    memset(PyString_AS_STRING(py_rawerr), 0, size);
+    if (error_ob != Py_None) {
+        if (convert_from_object(PyString_AS_STRING(py_rawerr),
+                                ctresult, error_ob) < 0) {
+            Py_DECREF(py_rawerr);
+            return NULL;
+        }
+    }
+    infotuple = Py_BuildValue("OOO", ct, ob, py_rawerr);
+    Py_DECREF(py_rawerr);
+    if (infotuple == NULL)
+        return NULL;
+
     closure = cffi_closure_alloc();
 
     cdb = PyObject_New(CDataObject_own_base, &CDataOwning_Type);
@@ -3055,13 +3086,12 @@
         goto error;
     }
     if (ffi_prep_closure(closure, &cif_descr->cif,
-                         invoke_callback, args) != FFI_OK) {
+                         invoke_callback, infotuple) != FFI_OK) {
         PyErr_SetString(PyExc_SystemError,
                         "libffi failed to build this callback");
         goto error;
     }
-    assert(closure->user_data == args);
-    Py_INCREF(args);   /* capture the tuple (CTypeDescr, Python callable) */
+    assert(closure->user_data == infotuple);
     return (PyObject *)cdb;
 
  error:
@@ -3070,6 +3100,7 @@
         cffi_closure_free(closure);
     else
         Py_DECREF(cdb);
+    Py_XDECREF(infotuple);
     return NULL;
 }
 
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -821,17 +821,37 @@
         def cb(n):
             return n + 1
         BFunc = new_function_type((BInt,), BInt, False)
-        return callback(BFunc, cb)    # 'cb' and 'BFunc' go out of scope
+        return callback(BFunc, cb, 42)    # 'cb' and 'BFunc' go out of scope
     f = make_callback()
     assert f(-142) == -141
 
+def test_callback_return_type():
+    for rettype in ["signed char", "short", "int", "long", "long long",
+                    "unsigned char", "unsigned short", "unsigned int",
+                    "unsigned long", "unsigned long long"]:
+        BRet = new_primitive_type(rettype)
+        def cb(n):
+            return n + 1
+        BFunc = new_function_type((BRet,), BRet)
+        f = callback(BFunc, cb, 42)
+        assert f(41) == 42
+        if rettype.startswith("unsigned "):
+            min = 0
+            max = (1 << (8*sizeof(BRet))) - 1
+        else:
+            min = -(1 << (8*sizeof(BRet)-1))
+            max = (1 << (8*sizeof(BRet)-1)) - 1
+        assert f(min) == min + 1
+        assert f(max - 1) == max
+        assert f(max) == 42
+
 def test_a_lot_of_callbacks():
     BInt = new_primitive_type("int")
     def make_callback(m):
         def cb(n):
             return n + m
         BFunc = new_function_type((BInt,), BInt, False)
-        return callback(BFunc, cb)    # 'cb' and 'BFunc' go out of scope
+        return callback(BFunc, cb, 42)    # 'cb' and 'BFunc' go out of scope
     #
     flist = [make_callback(i) for i in range(10000)]
     for i, f in enumerate(flist):
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -170,7 +170,7 @@
         """
         return self._backend.buffer(cdata, size)
 
-    def callback(self, cdecl, python_callable):
+    def callback(self, cdecl, python_callable, error=None):
         """Return a callback object.  'cdecl' must name a C function pointer
         type.  The callback invokes the specified 'python_callable'.
         Important: the callback object must be manually kept alive for as
@@ -179,7 +179,7 @@
         if not callable(python_callable):
             raise TypeError("the 'python_callable' argument is not callable")
         BFunc = self.typeof(cdecl)
-        return self._backend.callback(BFunc, python_callable)
+        return self._backend.callback(BFunc, python_callable, error)
 
     def getctype(self, cdecl, replace_with=''):
         """Return a string giving the C type 'cdecl', which may be itself
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -164,7 +164,10 @@
         return self
 
     def _get_own_repr(self):
-        return self._addr_repr(self._address)
+        try:
+            return self._addr_repr(self._address)
+        except AttributeError:
+            return '???'
 
     def _cast_to_integer(self):
         return self._address
@@ -706,21 +709,33 @@
                                       use_errno=True)
             _reftypename = BResult._get_c_name('(* &)(%s)' % (nameargs,))
 
-            def __init__(self, init):
+            def __init__(self, init, error=None):
                 # create a callback to the Python callable init()
+                import traceback
                 assert not has_varargs, "varargs not supported for callbacks"
+                if getattr(BResult, '_ctype', None) is not None:
+                    error = BResult._from_ctypes(
+                        BResult._create_ctype_obj(error))
+                else:
+                    error = None
                 def callback(*args):
                     args2 = []
                     for arg, BArg in zip(args, BArgs):
                         args2.append(BArg._from_ctypes(arg))
-                    res2 = init(*args2)
-                    res2 = BResult._to_ctypes(res2)
+                    try:
+                        res2 = init(*args2)
+                    except:
+                        traceback.print_exc()
+                        res2 = error
+                    else:
+                        res2 = BResult._to_ctypes(res2)
                     if issubclass(BResult, CTypesGenericPtr):
                         if res2:
                             res2 = ctypes.cast(res2, ctypes.c_void_p).value
                                 # .value: http://bugs.python.org/issue1574593
                         else:
                             res2 = None
+                    print repr(res2)
                     return res2
                 if issubclass(BResult, CTypesGenericPtr):
                     # The only pointers callbacks can return are void*s:
@@ -869,8 +884,8 @@
     def cast(self, BType, source):
         return BType._cast_from(source)
 
-    def callback(self, BType, source):
-        return BType(source)
+    def callback(self, BType, source, error):
+        return BType(source, error)
 
     typeof = type
 
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -536,8 +536,16 @@
 Be careful when writing the Python callback function: if it returns an
 object of the wrong type, or more generally raises an exception, then
 the exception cannot be propagated.  Instead, it is printed to stderr
-and the C-level callback is made to return a null value (this is a
-random choice).
+and the C-level callback is made to return a default value.
+
+.. versionadded:: 0.2
+   The returned value in case of errors is null by default, but can be
+   specified with the ``error`` keyword argument to ``ffi.callback()``::
+
+       >>> ffi.callback("int(*)(int, int)", myfunc, error=42)
+
+   In all cases the exception is printed to stderr, so this should be
+   used only as a last-resort solution.
 
 
 Miscellaneous
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -662,7 +662,6 @@
         assert hash(a) == hash(b)
 
     def test_callback_crash(self):
-        py.test.skip("in-progress")
         ffi = FFI(backend=self.Backend())
         def cb(n):
             raise Exception


More information about the pypy-commit mailing list