[pypy-commit] cffi default: Detect packed structs. Improve error messages and test them.

arigo pypy.commits at gmail.com
Thu Dec 22 11:53:54 EST 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r2839:b88f27223be8
Date: 2016-12-22 15:04 +0100
http://bitbucket.org/cffi/cffi/changeset/b88f27223be8/

Log:	Detect packed structs. Improve error messages and test them.

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -141,6 +141,7 @@
 #define CT_WITH_VAR_ARRAY     1048576
 #define CT_IS_UNSIZED_CHAR_A  2097152
 #define CT_LAZY_FIELD_LIST    4194304
+#define CT_WITH_PACKED_CHANGE 8388608
 #define CT_PRIMITIVE_ANY  (CT_PRIMITIVE_SIGNED |        \
                            CT_PRIMITIVE_UNSIGNED |      \
                            CT_PRIMITIVE_CHAR |          \
@@ -4280,7 +4281,7 @@
     CTypeDescrObject *ct;
     PyObject *fields, *interned_fields, *ignored;
     int is_union, alignment;
-    Py_ssize_t boffset, i, nb_fields, boffsetmax, alignedsize;
+    Py_ssize_t boffset, i, nb_fields, boffsetmax, alignedsize, boffsetorg;
     Py_ssize_t totalsize = -1;
     int totalalignment = -1;
     CFieldObject **previous;
@@ -4308,7 +4309,7 @@
                   "first arg must be a non-initialized struct or union ctype");
         return NULL;
     }
-    ct->ct_flags &= ~CT_CUSTOM_FIELD_POS;
+    ct->ct_flags &= ~(CT_CUSTOM_FIELD_POS | CT_WITH_PACKED_CHANGE);
 
     alignment = 1;
     boffset = 0;         /* this number is in *bits*, not bytes! */
@@ -4325,7 +4326,7 @@
     for (i=0; i<nb_fields; i++) {
         PyObject *fname;
         CTypeDescrObject *ftype;
-        int fbitsize = -1, falign, do_align;
+        int fbitsize = -1, falign, falignorg, do_align;
         Py_ssize_t foffset = -1;
 
         if (!PyArg_ParseTuple(PyList_GET_ITEM(fields, i), "O!O!|in:list item",
@@ -4353,7 +4354,8 @@
 
         /* update the total alignment requirement, but skip it if the
            field is an anonymous bitfield or if SF_PACKED */
-        falign = (sflags & SF_PACKED) ? 1 : get_alignment(ftype);
+        falignorg = get_alignment(ftype);
+        falign = (sflags & SF_PACKED) ? 1 : falignorg;
         if (falign < 0)
             goto error;
 
@@ -4383,7 +4385,11 @@
                 bs_flag = BS_REGULAR;
 
             /* align this field to its own 'falign' by inserting padding */
+            boffsetorg = (boffset + falignorg*8-1) & ~(falignorg*8-1); /*bits!*/
             boffset = (boffset + falign*8-1) & ~(falign*8-1); /* bits! */
+            if (boffsetorg != boffset) {
+                ct->ct_flags |= CT_WITH_PACKED_CHANGE;
+            }
 
             if (foffset >= 0) {
                 /* a forced field position: ignore the offset just computed,
@@ -4398,6 +4404,7 @@
             if (PyText_GetSize(fname) == 0 &&
                     ftype->ct_flags & (CT_STRUCT|CT_UNION)) {
                 /* a nested anonymous struct or union */
+                /* note: it seems we only get here with ffi.verify() */
                 CFieldObject *cfsrc = (CFieldObject *)ftype->ct_extra;
                 for (; cfsrc != NULL; cfsrc = cfsrc->cf_next) {
                     /* broken complexity in the call to get_field_name(),
@@ -4684,7 +4691,8 @@
            Another reason for CT_CUSTOM_FIELD_POS would be anonymous
            nested structures: we lost the information about having it
            here, so better safe (and forbid it) than sorry (and maybe
-           crash).
+           crash).  Note: it seems we only get in this case with
+           ffi.verify().
         */
         if (force_lazy_struct(ct) < 0)
             return NULL;
@@ -4692,9 +4700,16 @@
             /* these NotImplementedErrors may be caught and ignored until
                a real call is made to a function of this type */
             return fb_unsupported(ct, place,
-                "It can be a struct declared with \"...;\": then the C "
-                "calling convention may depend on the missing fields.  "
-                "Or, it can be a struct with nested anonymous structs/unions");
+                "It is a struct declared with \"...;\", but the C "
+                "calling convention may depend on the missing fields; "
+                "or, it contains anonymous struct/unions");
+        }
+        /* Another reason: __attribute__((packed)) is not supported by libffi.
+        */
+        if (ct->ct_flags & CT_WITH_PACKED_CHANGE) {
+            return fb_unsupported(ct, place,
+                "It is a 'packed' structure, with a different layout than "
+                "expected by libffi");
         }
 
         n = PyDict_Size(ct->ct_stuff);
@@ -4709,7 +4724,8 @@
             assert(cf != NULL);
             if (cf->cf_bitshift >= 0) {
                 return fb_unsupported(ct, place,
-                                      "It is a struct with bit fields");
+                    "It is a struct with bit fields, which libffi does not "
+                    "support");
             }
             flat = 1;
             ct1 = cf->cf_type;
@@ -4719,7 +4735,8 @@
             }
             if (flat <= 0) {
                 return fb_unsupported(ct, place,
-                    "It is a struct with a zero-length array");
+                    "It is a struct with a zero-length array, which libffi "
+                    "does not support");
             }
             nflat += flat;
             cf = cf->cf_next;
@@ -4760,7 +4777,7 @@
     }
     else if (ct->ct_flags & CT_UNION) {
         PyErr_Format(PyExc_NotImplementedError,
-                     "ctype '%s' not supported as %s.  "
+                     "ctype '%s' not supported as %s by libffi.  "
                      "Unions" SUPPORTED_IN_API_MODE,
                      ct->ct_name, place, place);
         return NULL;
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -1095,9 +1095,13 @@
     BFunc = new_function_type((BStruct,), BDouble)   # internally not callable
     dummy_func = cast(BFunc, 42)
     e = py.test.raises(NotImplementedError, dummy_func, "?")
-    msg = ("ctype \'struct foo\' not supported as argument (it is a struct "
-           'declared with "...;", but the C calling convention may depend on '
-           'the missing fields)')
+    msg = ("ctype 'struct foo' not supported as argument.  It is a struct "
+           'declared with "...;", but the C calling convention may depend '
+           "on the missing fields; or, it contains anonymous struct/unions.  "
+           "Such structs are only supported as argument if the function is "
+           "'API mode' and non-variadic (i.e. declared inside ffibuilder."
+           "cdef()+ffibuilder.set_source() and not taking a final '...' "
+           "argument)")
     assert str(e.value) == msg
 
 def test_new_charp():
diff --git a/testing/cffi0/test_verify.py b/testing/cffi0/test_verify.py
--- a/testing/cffi0/test_verify.py
+++ b/testing/cffi0/test_verify.py
@@ -1075,9 +1075,13 @@
         int (*foo)(struct foo_s s) = &foo1;
     """)
     e = py.test.raises(NotImplementedError, lib.foo, "?")
-    msg = ("ctype 'struct foo_s' not supported as argument (it is a struct "
-           'declared with "...;", but the C calling convention may depend '
-           'on the missing fields)')
+    msg = ("ctype 'struct foo_s' not supported as argument.  It is a struct "
+           'declared with "...;", but the C calling convention may depend on '
+           "the missing fields; or, it contains anonymous struct/unions.  "
+           "Such structs are only supported as argument "
+           "if the function is 'API mode' and non-variadic (i.e. declared "
+           "inside ffibuilder.cdef()+ffibuilder.set_source() and not taking "
+           "a final '...' argument)")
     assert str(e.value) == msg
 
 def test_func_returns_struct():
@@ -2146,14 +2150,23 @@
     # assert did not crash so far
     e = py.test.raises(NotImplementedError, fooptr, ffi.new("Data *"))
     assert str(e.value) == (
-        "ctype 'Data' (size 4) not supported as argument")
+        "ctype 'Data' not supported as argument by libffi.  Unions are only "
+        "supported as argument if the function is 'API mode' and "
+        "non-variadic (i.e. declared inside ffibuilder.cdef()+"
+        "ffibuilder.set_source() and not taking a final '...' argument)")
     e = py.test.raises(NotImplementedError, bazptr)
     assert str(e.value) == (
-        "ctype 'Data' (size 4) not supported as return value")
+        "ctype 'Data' not supported as return value by libffi.  Unions are "
+        "only supported as return value if the function is 'API mode' and "
+        "non-variadic (i.e. declared inside ffibuilder.cdef()+"
+        "ffibuilder.set_source() and not taking a final '...' argument)")
     e = py.test.raises(NotImplementedError, barptr)
     assert str(e.value) == (
-        "ctype 'MyStr' not supported as return value "
-        "(it is a struct with bit fields)")
+        "ctype 'MyStr' not supported as return value.  It is a struct with "
+        "bit fields, which libffi does not support.  Such structs are only "
+        "supported as return value if the function is 'API mode' and non-"
+        "variadic (i.e. declared inside ffibuilder.cdef()+ffibuilder."
+        "set_source() and not taking a final '...' argument)")
 
 def test_verify_extra_arguments():
     ffi = FFI()
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -2030,7 +2030,43 @@
     """)
     assert lib.f().x == 200
     e = py.test.raises(NotImplementedError, lib.g, 0)
-    print str(e.value)
+    assert str(e.value) == (
+        'ctype \'struct foo\' not supported as return value.  It is a '
+        'struct declared with "...;", but the C calling convention may '
+        'depend on the missing fields; or, it contains anonymous '
+        'struct/unions.  Such structs are only supported '
+        'as return value if the function is \'API mode\' and non-variadic '
+        '(i.e. declared inside ffibuilder.cdef()+ffibuilder.set_source() '
+        'and not taking a final \'...\' argument)')
+
+def test_call_with_nested_anonymous_struct():
+    if sys.platform == 'win32':
+        py.test.skip("needs a GCC extension")
+    ffi = FFI()
+    ffi.cdef("""
+        struct foo { int a; union { int b, c; }; };
+        struct foo f(void);
+        struct foo g(int, ...);
+    """)
+    lib = verify(ffi, "test_call_with_nested_anonymous_struct", """
+        struct foo { int a; union { int b, c; }; };
+        struct foo f(void) {
+            struct foo s = { 40 };
+            s.b = 200;
+            return s;
+        }
+        struct foo g(int a, ...) { }
+    """)
+    assert lib.f().b == 200
+    e = py.test.raises(NotImplementedError, lib.g, 0)
+    assert str(e.value) == (
+        'ctype \'struct foo\' not supported as return value.  It is a '
+        'struct declared with "...;", but the C calling convention may '
+        'depend on the missing fields; or, it contains anonymous '
+        'struct/unions.  Such structs are only supported '
+        'as return value if the function is \'API mode\' and non-variadic '
+        '(i.e. declared inside ffibuilder.cdef()+ffibuilder.set_source() '
+        'and not taking a final \'...\' argument)')
 
 def test_call_with_bitfield():
     ffi = FFI()
@@ -2049,7 +2085,12 @@
     """)
     assert lib.f().x == 11
     e = py.test.raises(NotImplementedError, lib.g, 0)
-    print str(e.value)
+    assert str(e.value) == (
+        "ctype 'struct foo' not supported as return value.  It is a struct "
+        "with bit fields, which libffi does not support.  Such structs are "
+        "only supported as return value if the function is 'API mode' and "
+        "non-variadic (i.e. declared inside ffibuilder.cdef()+ffibuilder."
+        "set_source() and not taking a final '...' argument)")
 
 def test_call_with_zero_length_field():
     ffi = FFI()
@@ -2068,7 +2109,12 @@
     """)
     assert lib.f().a == 42
     e = py.test.raises(NotImplementedError, lib.g, 0)
-    print str(e.value)
+    assert str(e.value) == (
+        "ctype 'struct foo' not supported as return value.  It is a "
+        "struct with a zero-length array, which libffi does not support."
+        "  Such structs are only supported as return value if the function is "
+        "'API mode' and non-variadic (i.e. declared inside ffibuilder.cdef()"
+        "+ffibuilder.set_source() and not taking a final '...' argument)")
 
 def test_call_with_union():
     ffi = FFI()
@@ -2087,7 +2133,11 @@
     """)
     assert lib.f().a == 42
     e = py.test.raises(NotImplementedError, lib.g, 0)
-    print str(e.value)
+    assert str(e.value) == (
+        "ctype 'union foo' not supported as return value by libffi.  "
+        "Unions are only supported as return value if the function is "
+        "'API mode' and non-variadic (i.e. declared inside ffibuilder.cdef()"
+        "+ffibuilder.set_source() and not taking a final '...' argument)")
 
 def test_call_with_packed_struct():
     if sys.platform == 'win32':
@@ -2104,9 +2154,17 @@
             struct foo s = { 40, 200 };
             return s;
         }
-        struct foo g(int a, ...) { }
+        struct foo g(int a, ...) {
+            struct foo s = { 41, 201 };
+            return s;
+        }
     """)
     assert lib.f().y == chr(40)
     assert lib.f().x == 200
     e = py.test.raises(NotImplementedError, lib.g, 0)
-    print str(e.value)
+    assert str(e.value) == (
+        "ctype 'struct foo' not supported as return value.  It is a "
+        "'packed' structure, with a different layout than expected by libffi."
+        "  Such structs are only supported as return value if the function is "
+        "'API mode' and non-variadic (i.e. declared inside ffibuilder.cdef()"
+        "+ffibuilder.set_source() and not taking a final '...' argument)")
diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py
--- a/testing/cffi1/test_verify1.py
+++ b/testing/cffi1/test_verify1.py
@@ -1042,9 +1042,13 @@
         int (*foo)(struct foo_s s) = &foo1;
     """)
     e = py.test.raises(NotImplementedError, lib.foo, "?")
-    msg = ("ctype 'struct foo_s' not supported as argument (it is a struct "
-           'declared with "...;", but the C calling convention may depend '
-           'on the missing fields)')
+    msg = ("ctype 'struct foo_s' not supported as argument.  It is a struct "
+           'declared with "...;", but the C calling convention may depend on '
+           "the missing fields; or, it contains anonymous struct/unions.  "
+           "Such structs are only supported as argument "
+           "if the function is 'API mode' and non-variadic (i.e. declared "
+           "inside ffibuilder.cdef()+ffibuilder.set_source() and not taking "
+           "a final '...' argument)")
     assert str(e.value) == msg
 
 def test_func_returns_struct():
@@ -2114,14 +2118,23 @@
     # assert did not crash so far
     e = py.test.raises(NotImplementedError, fooptr, ffi.new("Data *"))
     assert str(e.value) == (
-        "ctype 'Data' (size 4) not supported as argument")
+        "ctype 'Data' not supported as argument by libffi.  Unions are only "
+        "supported as argument if the function is 'API mode' and "
+        "non-variadic (i.e. declared inside ffibuilder.cdef()+"
+        "ffibuilder.set_source() and not taking a final '...' argument)")
     e = py.test.raises(NotImplementedError, bazptr)
     assert str(e.value) == (
-        "ctype 'Data' (size 4) not supported as return value")
+        "ctype 'Data' not supported as return value by libffi.  Unions are "
+        "only supported as return value if the function is 'API mode' and "
+        "non-variadic (i.e. declared inside ffibuilder.cdef()+"
+        "ffibuilder.set_source() and not taking a final '...' argument)")
     e = py.test.raises(NotImplementedError, barptr)
     assert str(e.value) == (
-        "ctype 'MyStr' not supported as return value "
-        "(it is a struct with bit fields)")
+        "ctype 'MyStr' not supported as return value.  It is a struct with "
+        "bit fields, which libffi does not support.  Such structs are only "
+        "supported as return value if the function is 'API mode' and non-"
+        "variadic (i.e. declared inside ffibuilder.cdef()+ffibuilder."
+        "set_source() and not taking a final '...' argument)")
 
 def test_verify_extra_arguments():
     ffi = FFI()


More information about the pypy-commit mailing list