Author: Armin Rigo <[email protected]>
Branch:
Changeset: r678:e28f36080810
Date: 2012-07-25 19:30 +0200
http://bitbucket.org/cffi/cffi/changeset/e28f36080810/
Log: libffi has a strange rule that results of integer type will be
returned as a whole 'ffi_arg' if they are smaller. That matter
mostly on big-endian machines. Fix. (thanks tumbleweed)
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -1599,6 +1599,19 @@
return ct_int;
}
+static CTypeDescrObject *_get_ct_long(void)
+{
+ static CTypeDescrObject *ct_long = NULL;
+ if (ct_long == NULL) {
+ PyObject *args = Py_BuildValue("(s)", "long");
+ if (args == NULL)
+ return NULL;
+ ct_long = (CTypeDescrObject *)b_new_primitive_type(NULL, args);
+ Py_DECREF(args);
+ }
+ return ct_long;
+}
+
static PyObject*
cdata_call(CDataObject *cd, PyObject *args, PyObject *kwds)
{
@@ -1660,7 +1673,7 @@
if (CData_Check(obj)) {
ct = ((CDataObject *)obj)->c_type;
- if (ct->ct_flags & (CT_PRIMITIVE_CHAR|CT_PRIMITIVE_UNSIGNED|
+ if (ct->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_UNSIGNED |
CT_PRIMITIVE_SIGNED)) {
if (ct->ct_size < sizeof(int)) {
ct = _get_ct_int();
@@ -1740,7 +1753,18 @@
resultdata, buffer_array);
save_errno();
- if (fresult->ct_flags & CT_VOID) {
+ if (fresult->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_SIGNED |
+ CT_PRIMITIVE_UNSIGNED)) {
+#ifdef WORDS_BIGENDIAN
+ /* For results of precisely these types, libffi has a strange
+ rule that they will be returned as a whole 'ffi_arg' if they
+ are smaller. The difference only matters on big-endian. */
+ if (fresult->ct_size < sizeof(ffi_arg))
+ resultdata += (sizeof(ffi_arg) - fresult->ct_size);
+#endif
+ res = convert_to_object(resultdata, fresult);
+ }
+ else if (fresult->ct_flags & CT_VOID) {
res = Py_None;
Py_INCREF(res);
}
@@ -3351,6 +3375,47 @@
return NULL;
}
+static int convert_from_object_fficallback(char *result,
+ CTypeDescrObject *ctype,
+ PyObject *pyobj)
+{
+ /* work work work around a libffi irregularity: for integer return
+ types we have to fill at least a complete 'ffi_arg'-sized result
+ buffer. */
+ if (ctype->ct_size < sizeof(ffi_arg)) {
+ if ((ctype->ct_flags & (CT_PRIMITIVE_SIGNED | CT_IS_ENUM))
+ == CT_PRIMITIVE_SIGNED) {
+ /* It's probably fine to always zero-extend, but you never
+ know: maybe some code somewhere expects a negative
+ 'short' result to be returned into EAX as a 32-bit
+ negative number. Better safe than sorry. This code
+ is about that case. Let's ignore this for enums.
+ */
+ /* do a first conversion only to detect overflows. This
+ conversion produces stuff that is otherwise ignored. */
+ if (convert_from_object(result, ctype, pyobj) < 0)
+ return -1;
+ /* sign-extend the result to a whole 'ffi_arg' (which has the
+ size of a long). This ensures that we write it in the whole
+ '*result' buffer independently of endianness. */
+ ctype = _get_ct_long();
+ if (ctype == NULL)
+ return -1;
+ assert(ctype->ct_size == sizeof(ffi_arg));
+ }
+ else if (ctype->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_SIGNED |
+ CT_PRIMITIVE_UNSIGNED)) {
+ /* zero extension: fill the '*result' with zeros, and (on big-
+ endian machines) correct the 'result' pointer to write to */
+ memset(result, 0, sizeof(ffi_arg));
+#ifdef WORDS_BIGENDIAN
+ result += (sizeof(ffi_arg) - ctype->ct_size);
+#endif
+ }
+ }
+ return convert_from_object(result, ctype, pyobj);
+}
+
static void invoke_callback(ffi_cif *cif, void *result, void **args,
void *userdata)
{
@@ -3386,7 +3451,7 @@
goto error;
if (SIGNATURE(1)->ct_size > 0) {
- if (convert_from_object(result, SIGNATURE(1), py_res) < 0)
+ if (convert_from_object_fficallback(result, SIGNATURE(1), py_res) < 0)
goto error;
}
else if (py_res != Py_None) {
@@ -3405,7 +3470,8 @@
PyErr_WriteUnraisable(py_ob);
if (SIGNATURE(1)->ct_size > 0) {
py_rawerr = PyTuple_GET_ITEM(cb_args, 2);
- memcpy(result, PyString_AS_STRING(py_rawerr), SIGNATURE(1)->ct_size);
+ memcpy(result, PyString_AS_STRING(py_rawerr),
+ PyString_GET_SIZE(py_rawerr));
}
goto done;
}
@@ -3441,15 +3507,21 @@
ctresult = (CTypeDescrObject *)PyTuple_GET_ITEM(ct->ct_stuff, 1);
size = ctresult->ct_size;
- if (size < 0)
+ if (ctresult->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_SIGNED |
+ CT_PRIMITIVE_UNSIGNED)) {
+ if (size < sizeof(ffi_arg))
+ size = sizeof(ffi_arg);
+ }
+ else 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) {
+ if (convert_from_object_fficallback(
+ PyString_AS_STRING(py_rawerr), ctresult, error_ob) < 0) {
Py_DECREF(py_rawerr);
return NULL;
}
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -953,6 +953,42 @@
e = py.test.raises(TypeError, newp, BStructPtr, [None])
assert "must be a str or int, not NoneType" in str(e.value)
+def test_callback_returning_enum():
+ BInt = new_primitive_type("int")
+ BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
+ def cb(n):
+ return '#%d' % n
+ BFunc = new_function_type((BInt,), BEnum)
+ f = callback(BFunc, cb)
+ assert f(0) == 'def'
+ assert f(1) == 'c'
+ assert f(-20) == 'ab'
+ assert f(20) == '#20'
+
+def test_callback_returning_char():
+ BInt = new_primitive_type("int")
+ BChar = new_primitive_type("char")
+ def cb(n):
+ return chr(n)
+ BFunc = new_function_type((BInt,), BChar)
+ f = callback(BFunc, cb)
+ assert f(0) == '\x00'
+ assert f(255) == '\xFF'
+
+def test_callback_returning_wchar_t():
+ BInt = new_primitive_type("int")
+ BWChar = new_primitive_type("wchar_t")
+ def cb(n):
+ if n < 0:
+ return u'\U00012345'
+ return unichr(n)
+ BFunc = new_function_type((BInt,), BWChar)
+ f = callback(BFunc, cb)
+ assert f(0) == unichr(0)
+ assert f(255) == unichr(255)
+ assert f(0x1234) == u'\u1234'
+ assert f(-1) == u'\U00012345'
+
def test_struct_with_bitfields():
BLong = new_primitive_type("long")
BStruct = new_struct_type("foo")
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit