[pypy-commit] cffi c99-array: Implementation: support a few extra ways to give initialization arguments to ffi.new()

arigo noreply at buildbot.pypy.org
Fri Nov 8 19:48:54 CET 2013


Author: Armin Rigo <arigo at tunes.org>
Branch: c99-array
Changeset: r1385:fb634a3e8dfc
Date: 2013-11-08 19:48 +0100
http://bitbucket.org/cffi/cffi/changeset/fb634a3e8dfc/

Log:	Implementation: support a few extra ways to give initialization
	arguments to ffi.new() for var-sized structs. See the test.

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) {
+                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,94 @@
     _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_version():
     # this test is here mostly for PyPy
     assert __version__ == "0.7"


More information about the pypy-commit mailing list