Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r1393:3a53adb16e28
Date: 2013-11-08 21:29 +0100
http://bitbucket.org/cffi/cffi/changeset/3a53adb16e28/

Log:    hg merge c99-array

        Adding more direct support for C99-style arrays inside structs. Non-
        fully-backward-compatible changes documented.

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -86,6 +86,7 @@
 #define CT_IS_BOOL             131072
 #define CT_IS_FILE             262144
 #define CT_IS_VOID_PTR         524288
+#define CT_WITH_VAR_ARRAY     1048576
 #define CT_PRIMITIVE_ANY  (CT_PRIMITIVE_SIGNED |        \
                            CT_PRIMITIVE_UNSIGNED |      \
                            CT_PRIMITIVE_CHAR |          \
@@ -1007,9 +1008,39 @@
 static int    /* forward */
 convert_from_object_bitfield(char *data, CFieldObject *cf, PyObject *init);
 
+static Py_ssize_t
+get_new_array_length(PyObject **pvalue)
+{
+    PyObject *value = *pvalue;
+
+    if (PyList_Check(value) || PyTuple_Check(value)) {
+        return PySequence_Fast_GET_SIZE(value);
+    }
+    else if (PyBytes_Check(value)) {
+        /* from a string, we add the null terminator */
+        return PyBytes_GET_SIZE(value) + 1;
+    }
+    else if (PyUnicode_Check(value)) {
+        /* from a unicode, we add the null terminator */
+        return _my_PyUnicode_SizeAsWideChar(value) + 1;
+    }
+    else {
+        Py_ssize_t explicitlength;
+        explicitlength = PyNumber_AsSsize_t(value, PyExc_OverflowError);
+        if (explicitlength < 0) {
+            if (!PyErr_Occurred())
+                PyErr_SetString(PyExc_ValueError, "negative array length");
+            return -1;
+        }
+        *pvalue = Py_None;
+        return explicitlength;
+    }
+}
+
 static int
 convert_field_from_object(char *data, CFieldObject *cf, PyObject *value)
 {
+    data += cf->cf_offset;
     if (cf->cf_bitshift >= 0)
         return convert_from_object_bitfield(data, cf, value);
     else
@@ -1017,6 +1048,45 @@
 }
 
 static int
+convert_vfield_from_object(char *data, CFieldObject *cf, PyObject *value,
+                           Py_ssize_t *optvarsize)
+{
+    /* a special case for var-sized C99 arrays */
+    if ((cf->cf_type->ct_flags & CT_ARRAY) && cf->cf_type->ct_size < 0) {
+        Py_ssize_t varsizelength = get_new_array_length(&value);
+        if (varsizelength < 0)
+            return -1;
+        if (optvarsize != NULL) {
+            /* in this mode, the only purpose of this function is to compute
+               the real size of the structure from a var-sized C99 array */
+            Py_ssize_t size, itemsize;
+            assert(data == NULL);
+            itemsize = cf->cf_type->ct_itemdescr->ct_size;
+            size = cf->cf_offset + itemsize * varsizelength;
+            if (size < 0 ||
+                ((size - cf->cf_offset) / itemsize) != varsizelength) {
+                PyErr_SetString(PyExc_OverflowError,
+                                "array size would overflow a Py_ssize_t");
+                return -1;
+            }
+            if (size > *optvarsize)
+                *optvarsize = size;
+            return 0;
+        }
+        /* if 'value' was only an integer, get_new_array_length() returns
+           it and convert 'value' to be None.  Detect if this was the case,
+           and if so, stop here, leaving the content uninitialized
+           (it should be zero-initialized from somewhere else). */
+        if (value == Py_None)
+            return 0;
+    }
+    if (optvarsize == NULL)
+        return convert_field_from_object(data, cf, value);
+    else
+        return 0;
+}
+
+static int
 convert_array_from_object(char *data, CTypeDescrObject *ct, PyObject *init)
 {
     /* used by convert_from_object(), and also to decode lists/tuples/unicodes
@@ -1097,6 +1167,63 @@
 }
 
 static int
+convert_struct_from_object(char *data, CTypeDescrObject *ct, PyObject *init,
+                           Py_ssize_t *optvarsize)
+{
+    const char *expected;
+
+    if (ct->ct_flags & CT_UNION) {
+        Py_ssize_t n = PyObject_Size(init);
+        if (n < 0)
+            return -1;
+        if (n > 1) {
+            PyErr_Format(PyExc_ValueError,
+                         "initializer for '%s': %zd items given, but "
+                         "only one supported (use a dict if needed)",
+                         ct->ct_name, n);
+            return -1;
+        }
+    }
+    if (PyList_Check(init) || PyTuple_Check(init)) {
+        PyObject **items = PySequence_Fast_ITEMS(init);
+        Py_ssize_t i, n = PySequence_Fast_GET_SIZE(init);
+        CFieldObject *cf = (CFieldObject *)ct->ct_extra;
+
+        for (i=0; i<n; i++) {
+            if (cf == NULL) {
+                PyErr_Format(PyExc_ValueError,
+                             "too many initializers for '%s' (got %zd)",
+                             ct->ct_name, n);
+                return -1;
+            }
+            if (convert_vfield_from_object(data, cf, items[i], optvarsize) < 0)
+                return -1;
+            cf = cf->cf_next;
+        }
+        return 0;
+    }
+    if (PyDict_Check(init)) {
+        PyObject *d_key, *d_value;
+        Py_ssize_t i = 0;
+        CFieldObject *cf;
+
+        while (PyDict_Next(init, &i, &d_key, &d_value)) {
+            cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, d_key);
+            if (cf == NULL) {
+                PyErr_SetObject(PyExc_KeyError, d_key);
+                return -1;
+            }
+            if (convert_vfield_from_object(data, cf, d_value, optvarsize) < 0)
+                return -1;
+        }
+        return 0;
+    }
+    expected = optvarsize == NULL ? "list or tuple or dict or struct-cdata"
+                                  : "list or tuple or dict";
+    return _convert_error(init, ct->ct_name, expected);
+}
+
+static int
 convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init)
 {
     const char *expected;
@@ -1209,56 +1336,7 @@
                 return 0;
             }
         }
-        if (ct->ct_flags & CT_UNION) {
-            Py_ssize_t n = PyObject_Size(init);
-            if (n < 0)
-                return -1;
-            if (n > 1) {
-                PyErr_Format(PyExc_ValueError,
-                             "initializer for '%s': %zd items given, but "
-                             "only one supported (use a dict if needed)",
-                             ct->ct_name, n);
-                return -1;
-            }
-        }
-        if (PyList_Check(init) || PyTuple_Check(init)) {
-            PyObject **items = PySequence_Fast_ITEMS(init);
-            Py_ssize_t i, n = PySequence_Fast_GET_SIZE(init);
-            CFieldObject *cf = (CFieldObject *)ct->ct_extra;
-
-            for (i=0; i<n; i++) {
-                if (cf == NULL) {
-                    PyErr_Format(PyExc_ValueError,
-                                 "too many initializers for '%s' (got %zd)",
-                                 ct->ct_name, n);
-                    return -1;
-                }
-                if (convert_field_from_object(data + cf->cf_offset,
-                                              cf, items[i]) < 0)
-                    return -1;
-                cf = cf->cf_next;
-            }
-            return 0;
-        }
-        if (PyDict_Check(init)) {
-            PyObject *d_key, *d_value;
-            Py_ssize_t i = 0;
-            CFieldObject *cf;
-
-            while (PyDict_Next(init, &i, &d_key, &d_value)) {
-                cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, d_key);
-                if (cf == NULL) {
-                    PyErr_SetObject(PyExc_KeyError, d_key);
-                    return -1;
-                }
-                if (convert_field_from_object(data + cf->cf_offset,
-                                              cf, d_value) < 0)
-                    return -1;
-            }
-            return 0;
-        }
-        expected = "list or tuple or dict or struct-cdata";
-        goto cannot_convert;
+        return convert_struct_from_object(data, ct, init, NULL);
     }
     PyErr_Format(PyExc_SystemError,
                  "convert_from_object: '%s'", ct->ct_name);
@@ -2068,9 +2146,8 @@
         cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, attr);
         if (cf != NULL) {
             /* write the field 'cf' */
-            char *data = cd->c_data + cf->cf_offset;
             if (value != NULL) {
-                return convert_field_from_object(data, cf, value);
+                return convert_field_from_object(cd->c_data, cf, value);
             }
             else {
                 PyErr_SetString(PyExc_AttributeError,
@@ -2642,32 +2719,21 @@
         }
         if (ctitem->ct_flags & CT_PRIMITIVE_CHAR)
             datasize *= 2;   /* forcefully add another character: a null */
+
+        if ((ctitem->ct_flags & CT_WITH_VAR_ARRAY) && init != Py_None) {
+            Py_ssize_t optvarsize = datasize;
+            if (convert_struct_from_object(NULL,ctitem, init, &optvarsize) < 0)
+                return NULL;
+            datasize = optvarsize;
+        }
     }
     else if (ct->ct_flags & CT_ARRAY) {
         dataoffset = offsetof(CDataObject_own_nolength, alignment);
         datasize = ct->ct_size;
         if (datasize < 0) {
-            if (PyList_Check(init) || PyTuple_Check(init)) {
-                explicitlength = PySequence_Fast_GET_SIZE(init);
-            }
-            else if (PyBytes_Check(init)) {
-                /* from a string, we add the null terminator */
-                explicitlength = PyBytes_GET_SIZE(init) + 1;
-            }
-            else if (PyUnicode_Check(init)) {
-                /* from a unicode, we add the null terminator */
-                explicitlength = _my_PyUnicode_SizeAsWideChar(init) + 1;
-            }
-            else {
-                explicitlength = PyNumber_AsSsize_t(init, PyExc_OverflowError);
-                if (explicitlength < 0) {
-                    if (!PyErr_Occurred())
-                        PyErr_SetString(PyExc_ValueError,
-                                        "negative array length");
-                    return NULL;
-                }
-                init = Py_None;
-            }
+            explicitlength = get_new_array_length(&init);
+            if (explicitlength < 0)
+                return NULL;
             ctitem = ct->ct_itemdescr;
             dataoffset = offsetof(CDataObject_own_length, alignment);
             datasize = explicitlength * ctitem->ct_size;
@@ -3554,11 +3620,17 @@
             goto error;
 
         if (ftype->ct_size < 0) {
-            PyErr_Format(PyExc_TypeError,
-                         "field '%s.%s' has ctype '%s' of unknown size",
-                         ct->ct_name, PyText_AS_UTF8(fname),
-                         ftype->ct_name);
-            goto error;
+            if ((ftype->ct_flags & CT_ARRAY) && fbitsize < 0
+                    && (i == nb_fields - 1 || foffset != -1)) {
+                ct->ct_flags |= CT_WITH_VAR_ARRAY;
+            }
+            else {
+                PyErr_Format(PyExc_TypeError,
+                             "field '%s.%s' has ctype '%s' of unknown size",
+                             ct->ct_name, PyText_AS_UTF8(fname),
+                             ftype->ct_name);
+                goto error;
+            }
         }
 
         if (is_union)
@@ -3632,7 +3704,8 @@
                     goto error;
                 previous = &(*previous)->cf_next;
             }
-            boffset += ftype->ct_size * 8;
+            if (ftype->ct_size >= 0)
+                boffset += ftype->ct_size * 8;
             prev_bitfield_size = 0;
         }
         else {
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -2963,6 +2963,133 @@
     _test_bitfield_details(flag=4)
 
 
+def test_struct_array_no_length():
+    BInt = new_primitive_type("int")
+    BIntP = new_pointer_type(BInt)
+    BArray = new_array_type(BIntP, None)
+    BStruct = new_struct_type("foo")
+    py.test.raises(TypeError, complete_struct_or_union,
+                   BStruct, [('x', BArray),
+                             ('y', BInt)])
+    #
+    BStruct = new_struct_type("foo")
+    complete_struct_or_union(BStruct, [('x', BInt),
+                                       ('y', BArray)])
+    assert sizeof(BStruct) == size_of_int()
+    d = BStruct.fields
+    assert len(d) == 2
+    assert d[0][0] == 'x'
+    assert d[0][1].type is BInt
+    assert d[0][1].offset == 0
+    assert d[0][1].bitshift == -1
+    assert d[0][1].bitsize == -1
+    assert d[1][0] == 'y'
+    assert d[1][1].type is BArray
+    assert d[1][1].offset == size_of_int()
+    assert d[1][1].bitshift == -1
+    assert d[1][1].bitsize == -1
+    #
+    p = newp(new_pointer_type(BStruct))
+    p.x = 42
+    assert p.x == 42
+    assert typeof(p.y) is BIntP
+    assert p.y == cast(BIntP, p) + 1
+    #
+    p = newp(new_pointer_type(BStruct), [100])
+    assert p.x == 100
+    #
+    # Tests for
+    #    ffi.new("struct_with_var_array *", [field.., [the_array_items..]])
+    #    ffi.new("struct_with_var_array *", [field.., array_size])
+    plist = []
+    for i in range(20):
+        if i % 2 == 0:
+            p = newp(new_pointer_type(BStruct), [100, [200, i, 400]])
+        else:
+            p = newp(new_pointer_type(BStruct), [100, 3])
+            p.y[1] = i
+            p.y[0] = 200
+            assert p.y[2] == 0
+            p.y[2] = 400
+        plist.append(p)
+    for i in range(20):
+        p = plist[i]
+        assert p.x == 100
+        assert p.y[0] == 200
+        assert p.y[1] == i
+        assert p.y[2] == 400
+        assert list(p.y[0:3]) == [200, i, 400]
+    #
+    # the following assignment works, as it normally would, for any array field
+    p.y = [500, 600]
+    assert list(p.y[0:3]) == [500, 600, 400]
+    #
+    # error cases
+    py.test.raises(TypeError, "p.y = cast(BIntP, 0)")
+    py.test.raises(TypeError, "p.y = 15")
+    py.test.raises(TypeError, "p.y = None")
+    #
+    # accepting this may be specified by the C99 standard,
+    # or a GCC strangeness...
+    BStruct2 = new_struct_type("bar")
+    complete_struct_or_union(BStruct2, [('f', BStruct),
+                                        ('n', BInt)])
+    p = newp(new_pointer_type(BStruct2), {'n': 42})
+    assert p.n == 42
+    #
+    # more error cases
+    py.test.raises(TypeError, newp, new_pointer_type(BStruct), [100, None])
+    BArray4 = new_array_type(BIntP, 4)
+    BStruct4 = new_struct_type("test4")
+    complete_struct_or_union(BStruct4, [('a', BArray4)])   # not varsized
+    py.test.raises(TypeError, newp, new_pointer_type(BStruct4), [None])
+    py.test.raises(TypeError, newp, new_pointer_type(BStruct4), [4])
+    p = newp(new_pointer_type(BStruct4), [[10, 20, 30]])
+    assert p.a[0] == 10
+    assert p.a[1] == 20
+    assert p.a[2] == 30
+    assert p.a[3] == 0
+
+def test_struct_array_no_length_explicit_position():
+    BInt = new_primitive_type("int")
+    BIntP = new_pointer_type(BInt)
+    BArray = new_array_type(BIntP, None)
+    BStruct = new_struct_type("foo")
+    complete_struct_or_union(BStruct, [('x', BArray, -1, 0), # actually 3 items
+                                       ('y', BInt, -1, 12)])
+    p = newp(new_pointer_type(BStruct), [[10, 20], 30])
+    assert p.x[0] == 10
+    assert p.x[1] == 20
+    assert p.x[2] == 0
+    assert p.y == 30
+    p = newp(new_pointer_type(BStruct), {'x': [40], 'y': 50})
+    assert p.x[0] == 40
+    assert p.x[1] == 0
+    assert p.x[2] == 0
+    assert p.y == 50
+    p = newp(new_pointer_type(BStruct), {'y': 60})
+    assert p.x[0] == 0
+    assert p.x[1] == 0
+    assert p.x[2] == 0
+    assert p.y == 60
+    #
+    # This "should" work too, allocating a larger structure
+    # (a bit strange in this case, but useful in general)
+    plist = []
+    for i in range(20):
+        p = newp(new_pointer_type(BStruct), [[10, 20, 30, 40, 50, 60, 70]])
+        plist.append(p)
+    for i in range(20):
+        p = plist[i]
+        assert p.x[0] == 10
+        assert p.x[1] == 20
+        assert p.x[2] == 30
+        assert p.x[3] == 40 == p.y
+        assert p.x[4] == 50
+        assert p.x[5] == 60
+        assert p.x[6] == 70
+
+
 def test_version():
     # this test is here mostly for PyPy
     assert __version__ == "0.7"
diff --git a/cffi/cparser.py b/cffi/cparser.py
--- a/cffi/cparser.py
+++ b/cffi/cparser.py
@@ -508,7 +508,7 @@
             if (isinstance(exprnode, pycparser.c_ast.ID) and
                     exprnode.name == '__dotdotdotarray__'):
                 self._partial_length = True
-                return None
+                return '...'
         #
         raise api.FFIError("unsupported expression: expected a "
                            "simple numeric constant")
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -212,10 +212,10 @@
         self.item = item
         self.length = length
         #
-        if self.length is None:
+        if length is None or length == '...':
             brackets = '&[]'
         else:
-            brackets = '&[%d]' % self.length
+            brackets = '&[%d]' % length
         self.c_name_with_marker = (
             self.item.c_name_with_marker.replace('&', brackets))
 
@@ -223,6 +223,10 @@
         return ArrayType(self.item, newlength)
 
     def build_backend_type(self, ffi, finishlist):
+        if self.length == '...':
+            from . import api
+            raise api.CDefError("cannot render the type %r: unknown length" %
+                                (self,))
         self.item.get_cached_btype(ffi, finishlist)   # force the item BType
         BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist)
         return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length)
@@ -300,20 +304,21 @@
             return    # not completing it: it's an opaque struct
         #
         self.completed = 1
-        fldtypes = tuple(tp.get_cached_btype(ffi, finishlist)
-                         for tp in self.fldtypes)
         #
         if self.fixedlayout is None:
+            fldtypes = [tp.get_cached_btype(ffi, finishlist)
+                        for tp in self.fldtypes]
             lst = list(zip(self.fldnames, fldtypes, self.fldbitsize))
             ffi._backend.complete_struct_or_union(BType, lst, self)
             #
         else:
+            fldtypes = []
             fieldofs, fieldsize, totalsize, totalalignment = self.fixedlayout
             for i in range(len(self.fldnames)):
                 fsize = fieldsize[i]
                 ftype = self.fldtypes[i]
                 #
-                if isinstance(ftype, ArrayType) and ftype.length is None:
+                if isinstance(ftype, ArrayType) and ftype.length == '...':
                     # fix the length to match the total size
                     BItemType = ftype.item.get_cached_btype(ffi, finishlist)
                     nlen, nrest = divmod(fsize, ffi.sizeof(BItemType))
@@ -324,18 +329,20 @@
                     ftype = ftype.resolve_length(nlen)
                     self.fldtypes = (self.fldtypes[:i] + (ftype,) +
                                      self.fldtypes[i+1:])
-                    BArrayType = ftype.get_cached_btype(ffi, finishlist)
-                    fldtypes = (fldtypes[:i] + (BArrayType,) +
-                                fldtypes[i+1:])
-                    continue
                 #
-                bitemsize = ffi.sizeof(fldtypes[i])
-                if bitemsize != fsize:
-                    self._verification_error(
-                        "field '%s.%s' is declared as %d bytes, but is "
-                        "really %d bytes" % (self.name,
-                                             self.fldnames[i] or '{}',
-                                             bitemsize, fsize))
+                BFieldType = ftype.get_cached_btype(ffi, finishlist)
+                if isinstance(ftype, ArrayType) and ftype.length is None:
+                    assert fsize == 0
+                else:
+                    bitemsize = ffi.sizeof(BFieldType)
+                    if bitemsize != fsize:
+                        self._verification_error(
+                            "field '%s.%s' is declared as %d bytes, but is "
+                            "really %d bytes" % (self.name,
+                                                 self.fldnames[i] or '{}',
+                                                 bitemsize, fsize))
+                fldtypes.append(BFieldType)
+            #
             lst = list(zip(self.fldnames, fldtypes, self.fldbitsize, fieldofs))
             ffi._backend.complete_struct_or_union(BType, lst, self,
                                                   totalsize, totalalignment)
diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py
--- a/cffi/vengine_cpy.py
+++ b/cffi/vengine_cpy.py
@@ -280,8 +280,8 @@
             return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % (
                 var, self._gettypenum(tp))
         elif isinstance(tp, model.ArrayType):
-            return '_cffi_from_c_deref((char *)%s, _cffi_type(%d))' % (
-                var, self._gettypenum(tp))
+            return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % (
+                var, self._gettypenum(model.PointerType(tp.item)))
         elif isinstance(tp, model.StructType):
             if tp.fldnames is None:
                 raise TypeError("'%s' is used as %s, but is opaque" % (
@@ -464,11 +464,14 @@
         prnt('  static Py_ssize_t nums[] = {')
         prnt('    sizeof(%s),' % cname)
         prnt('    offsetof(struct _cffi_aligncheck, y),')
-        for fname, _, fbitsize in tp.enumfields():
+        for fname, ftype, fbitsize in tp.enumfields():
             if fbitsize >= 0:
                 continue      # xxx ignore fbitsize for now
             prnt('    offsetof(%s, %s),' % (cname, fname))
-            prnt('    sizeof(((%s *)0)->%s),' % (cname, fname))
+            if isinstance(ftype, model.ArrayType) and ftype.length is None:
+                prnt('    0,  /* %s */' % ftype._get_c_name())
+            else:
+                prnt('    sizeof(((%s *)0)->%s),' % (cname, fname))
         prnt('    -1')
         prnt('  };')
         prnt('  return _cffi_get_struct_layout(nums);')
@@ -528,9 +531,10 @@
                     continue        # xxx ignore fbitsize for now
                 check(layout[i], ffi.offsetof(BStruct, fname),
                       "wrong offset for field %r" % (fname,))
-                BField = ffi._get_cached_btype(ftype)
-                check(layout[i+1], ffi.sizeof(BField),
-                      "wrong size for field %r" % (fname,))
+                if layout[i+1] != 0:
+                    BField = ffi._get_cached_btype(ftype)
+                    check(layout[i+1], ffi.sizeof(BField),
+                          "wrong size for field %r" % (fname,))
                 i += 2
             assert i == len(layout)
 
@@ -566,7 +570,7 @@
     # constants, likely declared with '#define'
 
     def _generate_cpy_const(self, is_int, name, tp=None, category='const',
-                            vartp=None, delayed=True):
+                            vartp=None, delayed=True, size_too=False):
         prnt = self._prnt
         funcname = '_cffi_%s_%s' % (category, name)
         prnt('static int %s(PyObject *lib)' % funcname)
@@ -597,6 +601,15 @@
                  '(unsigned long long)(%s));' % (name,))
         prnt('  if (o == NULL)')
         prnt('    return -1;')
+        if size_too:
+            prnt('  {')
+            prnt('    PyObject *o1 = o;')
+            prnt('    o = Py_BuildValue("On", o1, (Py_ssize_t)sizeof(%s));'
+                 % (name,))
+            prnt('    Py_DECREF(o1);')
+            prnt('    if (o == NULL)')
+            prnt('      return -1;')
+            prnt('  }')
         prnt('  res = PyObject_SetAttrString(lib, "%s", o);' % name)
         prnt('  Py_DECREF(o);')
         prnt('  if (res < 0)')
@@ -677,15 +690,16 @@
 
     def _generate_cpy_variable_collecttype(self, tp, name):
         if isinstance(tp, model.ArrayType):
-            self._do_collect_type(tp)
+            tp_ptr = model.PointerType(tp.item)
         else:
             tp_ptr = model.PointerType(tp)
-            self._do_collect_type(tp_ptr)
+        self._do_collect_type(tp_ptr)
 
     def _generate_cpy_variable_decl(self, tp, name):
         if isinstance(tp, model.ArrayType):
             tp_ptr = model.PointerType(tp.item)
-            self._generate_cpy_const(False, name, tp, vartp=tp_ptr)
+            self._generate_cpy_const(False, name, tp, vartp=tp_ptr,
+                                     size_too = (tp.length == '...'))
         else:
             tp_ptr = model.PointerType(tp)
             self._generate_cpy_const(False, name, tp_ptr, category='var')
@@ -694,11 +708,29 @@
     _loading_cpy_variable = _loaded_noop
 
     def _loaded_cpy_variable(self, tp, name, module, library):
+        value = getattr(library, name)
         if isinstance(tp, model.ArrayType):   # int a[5] is "constant" in the
-            return                            # sense that "a=..." is forbidden
+                                              # sense that "a=..." is forbidden
+            if tp.length == '...':
+                assert isinstance(value, tuple)
+                (value, size) = value
+                BItemType = self.ffi._get_cached_btype(tp.item)
+                length, rest = divmod(size, self.ffi.sizeof(BItemType))
+                if rest != 0:
+                    raise ffiplatform.VerificationError(
+                        "bad size: %r does not seem to be an array of %s" %
+                        (name, tp.item))
+                tp = tp.resolve_length(length)
+            # 'value' is a <cdata 'type *'> which we have to replace with
+            # a <cdata 'type[N]'> if the N is actually known
+            if tp.length is not None:
+                BArray = self.ffi._get_cached_btype(tp)
+                value = self.ffi.cast(BArray, value)
+                setattr(library, name, value)
+            return
         # remove ptr=<cdata 'int *'> from the library instance, and replace
         # it by a property on the class, which reads/writes into ptr[0].
-        ptr = getattr(library, name)
+        ptr = value
         delattr(library, name)
         def getter(library):
             return ptr[0]
diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py
--- a/cffi/vengine_gen.py
+++ b/cffi/vengine_gen.py
@@ -255,11 +255,14 @@
         prnt('  static ssize_t nums[] = {')
         prnt('    sizeof(%s),' % cname)
         prnt('    offsetof(struct _cffi_aligncheck, y),')
-        for fname, _, fbitsize in tp.enumfields():
+        for fname, ftype, fbitsize in tp.enumfields():
             if fbitsize >= 0:
                 continue      # xxx ignore fbitsize for now
             prnt('    offsetof(%s, %s),' % (cname, fname))
-            prnt('    sizeof(((%s *)0)->%s),' % (cname, fname))
+            if isinstance(ftype, model.ArrayType) and ftype.length is None:
+                prnt('    0,  /* %s */' % ftype._get_c_name())
+            else:
+                prnt('    sizeof(((%s *)0)->%s),' % (cname, fname))
         prnt('    -1')
         prnt('  };')
         prnt('  return nums[i];')
@@ -319,9 +322,10 @@
                     continue        # xxx ignore fbitsize for now
                 check(layout[i], ffi.offsetof(BStruct, fname),
                       "wrong offset for field %r" % (fname,))
-                BField = ffi._get_cached_btype(ftype)
-                check(layout[i+1], ffi.sizeof(BField),
-                      "wrong size for field %r" % (fname,))
+                if layout[i+1] != 0:
+                    BField = ffi._get_cached_btype(ftype)
+                    check(layout[i+1], ffi.sizeof(BField),
+                          "wrong size for field %r" % (fname,))
                 i += 2
             assert i == len(layout)
 
@@ -468,6 +472,14 @@
 
     def _generate_gen_variable_decl(self, tp, name):
         if isinstance(tp, model.ArrayType):
+            if tp.length == '...':
+                prnt = self._prnt
+                funcname = '_cffi_sizeof_%s' % (name,)
+                self.export_symbols.append(funcname)
+                prnt("size_t %s(void)" % funcname)
+                prnt("{")
+                prnt("  return sizeof(%s);" % (name,))
+                prnt("}")
             tp_ptr = model.PointerType(tp.item)
             self._generate_gen_const(False, name, tp_ptr)
         else:
@@ -479,6 +491,18 @@
     def _loaded_gen_variable(self, tp, name, module, library):
         if isinstance(tp, model.ArrayType):   # int a[5] is "constant" in the
                                               # sense that "a=..." is forbidden
+            if tp.length == '...':
+                funcname = '_cffi_sizeof_%s' % (name,)
+                BFunc = self.ffi.typeof('size_t(*)(void)')
+                function = module.load_function(BFunc, funcname)
+                size = function()
+                BItemType = self.ffi._get_cached_btype(tp.item)
+                length, rest = divmod(size, self.ffi.sizeof(BItemType))
+                if rest != 0:
+                    raise ffiplatform.VerificationError(
+                        "bad size: %r does not seem to be an array of %s" %
+                        (name, tp.item))
+                tp = tp.resolve_length(length)
             tp_ptr = model.PointerType(tp.item)
             value = self._load_constant(False, tp_ptr, name, module)
             # 'value' is a <cdata 'type *'> which we have to replace with
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -573,9 +573,18 @@
    ``foo_t`` is not opaque, but you just don't know any field in it; then
    you would use "``typedef struct { ...; } foo_t;``".
 
-*  array lengths: when used as structure fields, arrays can have an
-   unspecified length, as in "``int n[];``" or "``int n[...];``".
-   The length is completed by the C compiler.
+*  array lengths: when used as structure fields or in global variables,
+   arrays can have an unspecified length, as in "``int n[...];``".  The
+   length is completed by the C compiler.  (Only the outermost array
+   may have an unknown length, in case of array-of-array.)
+   You can also use the syntax "``int n[];``".
+
+.. versionchanged:: 0.8
+   "``int n[];``" asks for an array of unknown length whose length must
+   *not* be completed by the C compiler.  See `variable-length array`_
+   below.  If the structure does not contain the syntax ``...`` anywhere,
+   it will be not be considered to have a partial layout to complete by
+   the compiler.
 
 *  enums: if you don't know the exact order (or values) of the declared
    constants, then use this syntax: "``enum foo { A, B, C, ... };``"
@@ -1278,11 +1287,6 @@
 
 * Thread-local variables (access them via getter/setter functions)
 
-* Variable-length structures, i.e. whose last field is a variable-length
-  array (work around like in C, e.g. by declaring it as an array of
-  length 0, allocating a ``char[]`` of the correct size, and casting
-  it to a struct pointer)
-
 .. versionadded:: 0.4
    Now supported: the common GCC extension of anonymous nested
    structs/unions inside structs/unions.
@@ -1297,6 +1301,20 @@
    this by naming the largest value.  A similar but less important problem
    involves negative values.*
 
+.. _`variable-length array`:
+
+.. versionadded:: 0.8
+   Now supported: variable-length structures, i.e. whose last field is
+   a variable-length array.
+
+Note that since version 0.8, declarations like ``int field[];`` in
+structures are interpreted as variable-length structures.  When used for
+structures that are not, in fact, variable-length, it works too; in this
+case, the difference with using ``int field[...];`` is that, as CFFI
+believes it cannot ask the C compiler for the length of the array, you
+get reduced safety checks: for example, you risk overwriting the
+following fields by passing too many array items in the constructor.
+
 
 Debugging dlopen'ed C libraries
 -------------------------------
diff --git a/testing/test_ffi_backend.py b/testing/test_ffi_backend.py
--- a/testing/test_ffi_backend.py
+++ b/testing/test_ffi_backend.py
@@ -184,3 +184,13 @@
         ffi.cdef("typedef struct { float x; } foo_t;")
         p = ffi.new("foo_t *", [5.2])
         assert repr(p).startswith("<cdata 'foo_t *' ")
+
+    def test_struct_array_no_length(self):
+        ffi = FFI()
+        ffi.cdef("struct foo_s { int x; int a[]; };")
+        p = ffi.new("struct foo_s *", [100, [200, 300, 400]])
+        assert p.x == 100
+        assert ffi.typeof(p.a) is ffi.typeof("int *")   # no length available
+        assert p.a[0] == 200
+        assert p.a[1] == 300
+        assert p.a[2] == 400
diff --git a/testing/test_verify.py b/testing/test_verify.py
--- a/testing/test_verify.py
+++ b/testing/test_verify.py
@@ -479,32 +479,71 @@
     s = ffi.new("struct foo_s *")
     assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int')
 
-def test_struct_array_guess_length():
+def test_struct_array_no_length():
     ffi = FFI()
-    ffi.cdef("struct foo_s { int a[]; ...; };")    # <= no declared length
-    ffi.verify("struct foo_s { int x; int a[17]; int y; };")
-    assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int')
-    s = ffi.new("struct foo_s *")
-    assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int')
-
-def test_struct_array_guess_length_2():
-    ffi = FFI()
-    ffi.cdef("struct foo_s { int a[]; ...; };\n"    # <= no declared length
+    ffi.cdef("struct foo_s { int a[]; int y; ...; };\n"
              "int bar(struct foo_s *);\n")
     lib = ffi.verify("struct foo_s { int x; int a[17]; int y; };\n"
                      "int bar(struct foo_s *f) { return f->a[14]; }\n")
     assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int')
     s = ffi.new("struct foo_s *")
+    assert ffi.typeof(s.a) is ffi.typeof('int *')   # because no length
     s.a[14] = 4242
     assert lib.bar(s) == 4242
+    # with no declared length, out-of-bound accesses are not detected
+    s.a[17] = -521
+    assert s.y == s.a[17] == -521
+    #
+    s = ffi.new("struct foo_s *", {'a': list(range(17))})
+    assert s.a[16] == 16
+    # overflows at construction time not detected either
+    s = ffi.new("struct foo_s *", {'a': list(range(18))})
+    assert s.y == s.a[17] == 17
 
-def test_struct_array_guess_length_3():
+def test_struct_array_guess_length():
     ffi = FFI()
     ffi.cdef("struct foo_s { int a[...]; };")
     ffi.verify("struct foo_s { int x; int a[17]; int y; };")
     assert ffi.sizeof('struct foo_s') == 19 * ffi.sizeof('int')
     s = ffi.new("struct foo_s *")
     assert ffi.sizeof(s.a) == 17 * ffi.sizeof('int')
+    py.test.raises(IndexError, 's.a[17]')
+
+def test_struct_array_c99_1():
+    if sys.platform == 'win32':
+        py.test.skip("requires C99")
+    ffi = FFI()
+    ffi.cdef("struct foo_s { int x; int a[]; };")
+    ffi.verify("struct foo_s { int x; int a[]; };")
+    assert ffi.sizeof('struct foo_s') == 1 * ffi.sizeof('int')
+    s = ffi.new("struct foo_s *", [424242, 4])
+    assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int')   # the same in C
+    assert s.a[3] == 0
+    s = ffi.new("struct foo_s *", [424242, [-40, -30, -20, -10]])
+    assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int')
+    assert s.a[3] == -10
+    s = ffi.new("struct foo_s *")
+    assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int')
+    s = ffi.new("struct foo_s *", [424242])
+    assert ffi.sizeof(s[0]) == 1 * ffi.sizeof('int')
+
+def test_struct_array_c99_2():
+    if sys.platform == 'win32':
+        py.test.skip("requires C99")
+    ffi = FFI()
+    ffi.cdef("struct foo_s { int x; int a[]; ...; };")
+    ffi.verify("struct foo_s { int x, y; int a[]; };")
+    assert ffi.sizeof('struct foo_s') == 2 * ffi.sizeof('int')
+    s = ffi.new("struct foo_s *", [424242, 4])
+    assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int')
+    assert s.a[3] == 0
+    s = ffi.new("struct foo_s *", [424242, [-40, -30, -20, -10]])
+    assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int')
+    assert s.a[3] == -10
+    s = ffi.new("struct foo_s *")
+    assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int')
+    s = ffi.new("struct foo_s *", [424242])
+    assert ffi.sizeof(s[0]) == 2 * ffi.sizeof('int')
 
 def test_struct_ptr_to_array_field():
     ffi = FFI()
@@ -1470,7 +1509,12 @@
     ffi = FFI()
     ffi.cdef("int fooarray[...];")
     lib = ffi.verify("int fooarray[50];")
-    assert repr(lib.fooarray).startswith("<cdata 'int *'")
+    assert repr(lib.fooarray).startswith("<cdata 'int[50]'")
+
+def test_bad_global_array_with_dotdotdot_length():
+    ffi = FFI()
+    ffi.cdef("int fooarray[...];")
+    py.test.raises(VerificationError, ffi.verify, "char fooarray[23];")
 
 def test_struct_containing_struct():
     ffi = FFI()
@@ -1697,7 +1741,7 @@
     ffi = FFI()
     ffi.cdef("""const char *a[...];""")
     lib = ffi.verify("""const char *a[5];""")
-    assert repr(ffi.typeof(lib.a)) == "<ctype 'char * *'>"
+    assert repr(ffi.typeof(lib.a)) == "<ctype 'char *[5]'>"
 
 def test_bug_const_char_ptr_array_2():
     from cffi import FFI     # ignore warnings
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to