Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r1631:6acfa48521dd
Date: 2015-01-11 22:23 +0100
http://bitbucket.org/cffi/cffi/changeset/6acfa48521dd/

Log:    Change again ffi.offsetof() and ffi.addressof(), generalizing them.

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -4836,27 +4836,19 @@
     CTypeDescrObject *ct;
     CFieldObject *cf;
     Py_ssize_t offset;
-
-    if (!PyArg_ParseTuple(args, "O!O:typeoffsetof",
-                          &CTypeDescr_Type, &ct, &fieldname))
+    int following = 0;
+
+    if (!PyArg_ParseTuple(args, "O!O|i:typeoffsetof",
+                          &CTypeDescr_Type, &ct, &fieldname, &following))
         return NULL;
 
-    if (fieldname == Py_None) {
-        if (!(ct->ct_flags & (CT_STRUCT|CT_UNION))) {
-            PyErr_SetString(PyExc_TypeError,
-                            "expected a struct or union ctype");
-            return NULL;
-        }
-        res = (PyObject *)ct;
-        offset = 0;
-    }
-    else {
-        if (ct->ct_flags & CT_POINTER)
+    if (PyTextAny_Check(fieldname)) {
+        if (!following && (ct->ct_flags & CT_POINTER))
             ct = ct->ct_itemdescr;
         if (!(ct->ct_flags & (CT_STRUCT|CT_UNION)) || ct->ct_stuff == NULL) {
             PyErr_SetString(PyExc_TypeError,
-                            "expected an initialized struct or union ctype, "
-                            "or a pointer to one");
+                            "with a field name argument, expected an "
+                            "initialized struct or union ctype");
             return NULL;
         }
         cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, fieldname);
@@ -4871,6 +4863,29 @@
         res = (PyObject *)cf->cf_type;
         offset = cf->cf_offset;
     }
+    else {
+        ssize_t index = PyInt_AsSsize_t(fieldname);
+        if (index < 0 && PyErr_Occurred()) {
+            PyErr_SetString(PyExc_TypeError,
+                            "field name or array index expected");
+            return NULL;
+        }
+
+        if (!(ct->ct_flags & (CT_ARRAY|CT_POINTER)) ||
+                ct->ct_itemdescr->ct_size < 0) {
+            PyErr_SetString(PyExc_TypeError, "with an integer argument, "
+                                             "expected an array ctype or a "
+                                             "pointer to non-opaque");
+            return NULL;
+        }
+        res = (PyObject *)ct->ct_itemdescr;
+        offset = index * ct->ct_itemdescr->ct_size;
+        if ((offset / ct->ct_itemdescr->ct_size) != index) {
+            PyErr_SetString(PyExc_OverflowError,
+                            "array offset would overflow a Py_ssize_t");
+            return NULL;
+        }
+    }
     return Py_BuildValue("(On)", res, offset);
 }
 
@@ -4878,15 +4893,17 @@
 {
     CTypeDescrObject *ct;
     CDataObject *cd;
-    Py_ssize_t offset = 0;
-
-    if (!PyArg_ParseTuple(args, "O!O!|n:rawaddressof",
+    Py_ssize_t offset;
+    int accepted_flags;
+
+    if (!PyArg_ParseTuple(args, "O!O!n:rawaddressof",
                           &CTypeDescr_Type, &ct,
                           &CData_Type, &cd,
                           &offset))
         return NULL;
 
-    if ((cd->c_type->ct_flags & (CT_STRUCT|CT_UNION|CT_IS_PTR_TO_OWNED)) == 0) 
{
+    accepted_flags = CT_STRUCT | CT_UNION | CT_ARRAY | CT_POINTER;
+    if ((cd->c_type->ct_flags & accepted_flags) == 0) {
         PyErr_SetString(PyExc_TypeError,
                         "expected a 'cdata struct-or-union' object");
         return NULL;
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -2538,13 +2538,31 @@
                                        ('a2', BChar, -1),
                                        ('a3', BChar, -1)])
     py.test.raises(TypeError, typeoffsetof, BStructPtr, None)
-    assert typeoffsetof(BStruct, None) == (BStruct, 0)
+    py.test.raises(TypeError, typeoffsetof, BStruct, None)
     assert typeoffsetof(BStructPtr, 'a1') == (BChar, 0)
     assert typeoffsetof(BStruct, 'a1') == (BChar, 0)
     assert typeoffsetof(BStructPtr, 'a2') == (BChar, 1)
     assert typeoffsetof(BStruct, 'a3') == (BChar, 2)
+    assert typeoffsetof(BStructPtr, 'a2', 0) == (BChar, 1)
+    py.test.raises(TypeError, typeoffsetof, BStructPtr, 'a2', 1)
     py.test.raises(KeyError, typeoffsetof, BStructPtr, 'a4')
     py.test.raises(KeyError, typeoffsetof, BStruct, 'a5')
+    py.test.raises(TypeError, typeoffsetof, BStruct, 42)
+    py.test.raises(TypeError, typeoffsetof, BChar, 'a1')
+
+def test_typeoffsetof_array():
+    BInt = new_primitive_type("int")
+    BIntP = new_pointer_type(BInt)
+    BArray = new_array_type(BIntP, None)
+    py.test.raises(TypeError, typeoffsetof, BArray, None)
+    py.test.raises(TypeError, typeoffsetof, BArray, 'a1')
+    assert typeoffsetof(BArray, 51) == (BInt, 51 * size_of_int())
+    assert typeoffsetof(BIntP, 51) == (BInt, 51 * size_of_int())
+    assert typeoffsetof(BArray, -51) == (BInt, -51 * size_of_int())
+    MAX = sys.maxsize // size_of_int()
+    assert typeoffsetof(BArray, MAX) == (BInt, MAX * size_of_int())
+    assert typeoffsetof(BIntP, MAX) == (BInt, MAX * size_of_int())
+    py.test.raises(OverflowError, typeoffsetof, BArray, MAX + 1)
 
 def test_typeoffsetof_no_bitfield():
     BInt = new_primitive_type("int")
@@ -2564,14 +2582,14 @@
     assert repr(p) == "<cdata 'struct foo *' owning 3 bytes>"
     s = p[0]
     assert repr(s) == "<cdata 'struct foo' owning 3 bytes>"
-    a = rawaddressof(BStructPtr, s)
+    a = rawaddressof(BStructPtr, s, 0)
     assert repr(a).startswith("<cdata 'struct foo *' 0x")
-    py.test.raises(TypeError, rawaddressof, BStruct, s)
-    b = rawaddressof(BCharP, s)
+    py.test.raises(TypeError, rawaddressof, BStruct, s, 0)
+    b = rawaddressof(BCharP, s, 0)
     assert b == cast(BCharP, p)
-    c = rawaddressof(BStructPtr, a)
+    c = rawaddressof(BStructPtr, a, 0)
     assert c == a
-    py.test.raises(TypeError, rawaddressof, BStructPtr, cast(BChar, '?'))
+    py.test.raises(TypeError, rawaddressof, BStructPtr, cast(BChar, '?'), 0)
     #
     d = rawaddressof(BCharP, s, 1)
     assert d == cast(BCharP, p) + 1
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -191,14 +191,16 @@
             cdecl = self._typeof(cdecl)
         return self._backend.alignof(cdecl)
 
-    def offsetof(self, cdecl, fieldname):
+    def offsetof(self, cdecl, *fields_or_indexes):
         """Return the offset of the named field inside the given
-        structure, which must be given as a C type name.  The field
-        may be 'x.y.z' in case of nested structures.
+        structure or array, which must be given as a C type name.  
+        You can give several field names in case of nested structures.
+        You can also give numeric values which correspond to array
+        items, in case of an array type.
         """
         if isinstance(cdecl, basestring):
             cdecl = self._typeof(cdecl)
-        return self._typeoffsetof(cdecl, fieldname)[1]
+        return self._typeoffsetof(cdecl, *fields_or_indexes)[1]
 
     def new(self, cdecl, init=None):
         """Allocate an instance according to the specified C type and
@@ -383,24 +385,28 @@
         with self._lock:
             return model.pointer_cache(self, ctype)
 
-    def addressof(self, cdata, field=None):
+    def addressof(self, cdata, *fields_or_indexes):
         """Return the address of a <cdata 'struct-or-union'>.
-        If 'field' is specified, return the address of this field.
-        The field may be 'x.y.z' in case of nested structures.
+        If 'fields_or_indexes' are given, returns the address of that
+        field or array item in the structure or array, recursively in
+        case of nested structures.
         """
         ctype = self._backend.typeof(cdata)
-        ctype, offset = self._typeoffsetof(ctype, field)
+        if fields_or_indexes:
+            ctype, offset = self._typeoffsetof(ctype, *fields_or_indexes)
+        else:
+            if ctype.kind == "pointer":
+                raise TypeError("addressof(pointer)")
+            offset = 0
         ctypeptr = self._pointer_to(ctype)
         return self._backend.rawaddressof(ctypeptr, cdata, offset)
 
-    def _typeoffsetof(self, ctype, field):
-        if field is not None and '.' in field:
-            offset = 0
-            for field1 in field.split('.'):
-                ctype, offset1 = self._backend.typeoffsetof(ctype, field1)
-                offset += offset1
-            return ctype, offset
-        return self._backend.typeoffsetof(ctype, field)
+    def _typeoffsetof(self, ctype, field_or_index, *fields_or_indexes):
+        ctype, offset = self._backend.typeoffsetof(ctype, field_or_index)
+        for field1 in fields_or_indexes:
+            ctype, offset1 = self._backend.typeoffsetof(ctype, field1, 1)
+            offset += offset1
+        return ctype, offset
 
     def include(self, ffi_to_include):
         """Includes the typedefs, structs, unions and enums defined
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -169,6 +169,7 @@
 class CTypesGenericPtr(CTypesData):
     __slots__ = ['_address', '_as_ctype_ptr']
     _automatic_casts = False
+    kind = "pointer"
 
     @classmethod
     def _newp(cls, init):
@@ -370,10 +371,12 @@
                                 (CTypesPrimitive, type(source).__name__))
             return source
         #
+        kind1 = kind
         class CTypesPrimitive(CTypesGenericPrimitive):
             __slots__ = ['_value']
             _ctype = ctype
             _reftypename = '%s &' % name
+            kind = kind1
 
             def __init__(self, value):
                 self._value = value
@@ -703,12 +706,13 @@
         class struct_or_union(base_ctypes_class):
             pass
         struct_or_union.__name__ = '%s_%s' % (kind, name)
+        kind1 = kind
         #
         class CTypesStructOrUnion(CTypesBaseStructOrUnion):
             __slots__ = ['_blob']
             _ctype = struct_or_union
             _reftypename = '%s &' % (name,)
-            _kind = kind
+            _kind = kind = kind1
         #
         CTypesStructOrUnion._fix_class()
         return CTypesStructOrUnion
@@ -994,27 +998,42 @@
     def getcname(self, BType, replace_with):
         return BType._get_c_name(replace_with)
 
-    def typeoffsetof(self, BType, fieldname):
-        if fieldname is not None and issubclass(BType, CTypesGenericPtr):
-            BType = BType._BItem
-        if not issubclass(BType, CTypesBaseStructOrUnion):
-            raise TypeError("expected a struct or union ctype")
-        if fieldname is None:
-            return (BType, 0)
-        else:
+    def typeoffsetof(self, BType, fieldname, num=0):
+        if isinstance(fieldname, str):
+            if num == 0 and issubclass(BType, CTypesGenericPtr):
+                BType = BType._BItem
+            if not issubclass(BType, CTypesBaseStructOrUnion):
+                raise TypeError("expected a struct or union ctype")
             BField = BType._bfield_types[fieldname]
             if BField is Ellipsis:
                 raise TypeError("not supported for bitfields")
             return (BField, BType._offsetof(fieldname))
+        elif isinstance(fieldname, (int, long)):
+            if issubclass(BType, CTypesGenericArray):
+                BType = BType._CTPtr
+            if not issubclass(BType, CTypesGenericPtr):
+                raise TypeError("expected an array or ptr ctype")
+            BItem = BType._BItem
+            offset = BItem._get_size() * fieldname
+            if offset > sys.maxsize:
+                raise OverflowError
+            return (BItem, offset)
+        else:
+            raise TypeError(type(fieldname))
 
-    def rawaddressof(self, BTypePtr, cdata, offset):
+    def rawaddressof(self, BTypePtr, cdata, offset=None):
         if isinstance(cdata, CTypesBaseStructOrUnion):
             ptr = ctypes.pointer(type(cdata)._to_ctypes(cdata))
         elif isinstance(cdata, CTypesGenericPtr):
+            if offset is None or not issubclass(type(cdata)._BItem,
+                                                CTypesBaseStructOrUnion):
+                raise TypeError("unexpected cdata type")
+            ptr = type(cdata)._to_ctypes(cdata)
+        elif isinstance(cdata, CTypesGenericArray):
             ptr = type(cdata)._to_ctypes(cdata)
         else:
             raise TypeError("expected a <cdata 'struct-or-union'>")
-        if offset != 0:
+        if offset:
             ptr = ctypes.cast(
                 ctypes.c_void_p(
                     ctypes.cast(ptr, ctypes.c_void_p).value + offset),
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1301,11 +1301,15 @@
 **ffi.alignof("C type")**: return the alignment of the C type.
 Corresponds to the ``__alignof__`` operator in GCC.
 
-**ffi.offsetof("C struct type", "fieldname")**: return the offset within
-the struct of the given field.  Corresponds to ``offsetof()`` in C.
+**ffi.offsetof("C struct or array type", *fields_or_indexes)**: return the
+offset within the struct of the given field.  Corresponds to ``offsetof()``
+in C.
 
 .. versionchanged:: 0.9
-   ``"fieldname"`` can be ``"x.y"`` in case of nested structures.
+   You can give several field names in case of nested structures.  You
+   can also give numeric values which correspond to array items, in case
+   of a pointer or array type.  For example, ``ffi.offsetof("int[5]", 2)``
+   is equal to the size of two integers, as is ``ffi.offsetof("int *", 2)``.
 
 **ffi.getctype("C type" or <ctype>, extra="")**: return the string
 representation of the given C type.  If non-empty, the "extra" string is
@@ -1358,21 +1362,26 @@
 
 .. "versionadded:: 0.7" --- inlined in the previous paragraph
 
-**ffi.addressof(cdata, field=None)**: from a cdata whose type is
-``struct foo_s``, return its "address", as a cdata whose type is
-``struct foo_s *``.  Also works on unions, but not on any other type.
-(It would be difficult because only structs and unions are internally
-stored as an indirect pointer to the data.  If you need a C int whose
-address can be taken, use ``ffi.new("int[1]")`` in the first place;
-similarly, if it's a C pointer, use ``ffi.new("foo_t *[1]")``.)
-If ``field`` is given,
-returns the address of that field in the structure.  The returned
-pointer is only valid as long as the original ``cdata`` object is; be
-sure to keep it alive if it was obtained directly from ``ffi.new()``.
-*New in version 0.4.*
+**ffi.addressof(cdata, *fields_or_indexes)**: equivalent to the C
+expression ``&cdata`` or ``&cdata.field`` or ``&cdata->field`` or
+``&cdata[index]`` (or any combination of fields and indexes).  Works
+with the same ctypes where one of the above expressions would work in
+C, with one exception: if no ``fields_or_indexes`` is specified, it
+cannot be used to take the address of a primitive or pointer (it would
+be difficult to implement because only structs and unions and arrays
+are internally stored as an indirect pointer to the data.  If you need
+a C int whose address can be taken, use ``ffi.new("int[1]")`` in the
+first place; similarly, for a pointer, use ``ffi.new("foo_t *[1]")``.)
+
+The returned pointer is only valid as long as the original ``cdata``
+object is; be sure to keep it alive if it was obtained directly from
+``ffi.new()``.  *New in version 0.4.*
 
 .. versionchanged:: 0.9
-   ``"field"`` can be ``"x.y"`` in case of nested structures.
+   You can give several field names in case of nested structures, and
+   you can give numeric values for array items.  Note that
+   ``&cdata[index]`` can also be expressed as simply ``cdata + index``,
+   both in C and in CFFI.
 
 .. "versionadded:: 0.4" --- inlined in the previous paragraph
 
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -964,9 +964,19 @@
         ffi.cdef("struct foo { int a, b, c; };"
                  "struct bar { struct foo d, e; };")
         assert ffi.offsetof("struct bar", "e") == 12
-        assert ffi.offsetof("struct bar", "e.a") == 12
-        assert ffi.offsetof("struct bar", "e.b") == 16
-        assert ffi.offsetof("struct bar", "e.c") == 20
+        py.test.raises(KeyError, ffi.offsetof, "struct bar", "e.a")
+        assert ffi.offsetof("struct bar", "e", "a") == 12
+        assert ffi.offsetof("struct bar", "e", "b") == 16
+        assert ffi.offsetof("struct bar", "e", "c") == 20
+
+    def test_offsetof_array(self):
+        ffi = FFI(backend=self.Backend())
+        assert ffi.offsetof("int[]", 51) == 51 * ffi.sizeof("int")
+        assert ffi.offsetof("int *", 51) == 51 * ffi.sizeof("int")
+        ffi.cdef("struct bar { int a, b; int c[99]; };")
+        assert ffi.offsetof("struct bar", "c") == 2 * ffi.sizeof("int")
+        assert ffi.offsetof("struct bar", "c", 0) == 2 * ffi.sizeof("int")
+        assert ffi.offsetof("struct bar", "c", 51) == 53 * ffi.sizeof("int")
 
     def test_alignof(self):
         ffi = FFI(backend=self.Backend())
@@ -1500,8 +1510,10 @@
         p = ffi.new("struct foo_s *")
         a = ffi.addressof(p[0])
         assert repr(a).startswith("<cdata 'struct foo_s *' 0x")
+        assert a == p
         py.test.raises(TypeError, ffi.addressof, p)
         py.test.raises((AttributeError, TypeError), ffi.addressof, 5)
+        py.test.raises(TypeError, ffi.addressof, ffi.cast("int", 5))
 
     def test_addressof_field(self):
         ffi = FFI(backend=self.Backend())
@@ -1519,7 +1531,8 @@
         ffi.cdef("struct foo_s { int x, y; };"
                  "struct bar_s { struct foo_s a, b; };")
         p = ffi.new("struct bar_s *")
-        a = ffi.addressof(p[0], 'b.y')
+        py.test.raises(KeyError, ffi.addressof, p[0], 'b.y')
+        a = ffi.addressof(p[0], 'b', 'y')
         assert int(ffi.cast("uintptr_t", a)) == (
             int(ffi.cast("uintptr_t", p)) +
             ffi.sizeof("struct foo_s") + ffi.sizeof("int"))
@@ -1531,6 +1544,49 @@
         a = ffi.addressof(p[0])
         assert a == p
 
+    def test_addressof_array(self):
+        ffi = FFI()
+        p = ffi.new("int[52]")
+        p0 = ffi.addressof(p)
+        assert p0 == p
+        assert ffi.typeof(p0) is ffi.typeof("int(*)[52]")
+        py.test.raises(TypeError, ffi.addressof, p0)
+        #
+        p1 = ffi.addressof(p, 25)
+        assert ffi.typeof(p1) is ffi.typeof("int *")
+        assert (p1 - p) == 25
+        assert ffi.addressof(p, 0) == p
+
+    def test_addressof_pointer(self):
+        ffi = FFI()
+        array = ffi.new("int[50]")
+        p = ffi.cast("int *", array)
+        py.test.raises(TypeError, ffi.addressof, p)
+        assert ffi.addressof(p, 0) == p
+        assert ffi.addressof(p, 25) == p + 25
+        assert ffi.typeof(ffi.addressof(p, 25)) == ffi.typeof(p)
+        #
+        ffi.cdef("struct foo { int a, b; };")
+        array = ffi.new("struct foo[50]")
+        p = ffi.cast("int *", array)
+        py.test.raises(TypeError, ffi.addressof, p)
+        assert ffi.addressof(p, 0) == p
+        assert ffi.addressof(p, 25) == p + 25
+        assert ffi.typeof(ffi.addressof(p, 25)) == ffi.typeof(p)
+
+    def test_addressof_array_in_struct(self):
+        ffi = FFI()
+        ffi.cdef("struct foo { int a, b; int c[50]; };")
+        p = ffi.new("struct foo *")
+        p1 = ffi.addressof(p, "c", 25)
+        assert ffi.typeof(p1) is ffi.typeof("int *")
+        assert p1 == ffi.cast("int *", p) + 27
+        assert ffi.addressof(p, "c") == ffi.cast("int *", p) + 2
+        assert ffi.addressof(p, "c", 0) == ffi.cast("int *", p) + 2
+        p2 = ffi.addressof(p, 1)
+        assert ffi.typeof(p2) is ffi.typeof("struct foo *")
+        assert p2 == p + 1
+
     def test_multiple_independent_structs(self):
         ffi1 = FFI(); ffi1.cdef("struct foo { int x; };")
         ffi2 = FFI(); ffi2.cdef("struct foo { int y, z; };")
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to