Commits

Armin Rigo committed 0d5efad

Workaround: allow out-of-bound array indexes if the array is 'type[0]'.

  • Participants
  • Parent commits ea4550a

Comments (0)

Files changed (5)

c/_cffi_backend.c

                             "negative index not supported");
             return NULL;
         }
-        if (i >= get_array_length(cd)) {
+        if (i >= get_array_length(cd) && cd->c_type->ct_length != 0) {
             PyErr_Format(PyExc_IndexError,
                          "index too large for cdata '%s' (expected %zd < %zd)",
                          cd->c_type->ct_name,
         if not py_py:
             assert repr(x).endswith("E+902>")
             assert float(x) == float("inf")
+
+def test_array_of_length_zero():
+    p = new_pointer_type(new_primitive_type("int"))
+    p0 = new_array_type(p, 0)
+    p3 = new_array_type(p, 3)
+    a1 = newp(p3, [61, 62, 63])
+    a2 = cast(p0, a1)
+    assert a2[0] == 61
+    assert a2[1] == 62
+    assert a2[2] == 63
+    a2[2] = 64
+    assert list(a1) == [61, 62, 64]
+    assert list(a2) == []

cffi/backend_ctypes.py

                 return len(self._blob)
 
             def __getitem__(self, index):
-                if not (0 <= index < len(self._blob)):
+                if 0 <= index < len(self._blob):
+                    x = self._blob[index]
+                elif len(self._blob) == 0:
+                    x = ctypes.cast(self._blob, CTypesPtr._ctype)[index]
+                else:
                     raise IndexError
-                return BItem._from_ctypes(self._blob[index])
+                return BItem._from_ctypes(x)
 
             def __setitem__(self, index, value):
-                if not (0 <= index < len(self._blob)):
+                x = BItem._to_ctypes(value)
+                if 0 <= index < len(self._blob):
+                    self._blob[index] = x
+                elif len(self._blob) == 0:
+                    ctypes.cast(self._blob, CTypesPtr._ctype)[index] = x
+                else:
                     raise IndexError
-                self._blob[index] = BItem._to_ctypes(value)
 
             if kind == 'char' or kind == 'byte':
                 def _to_string(self, maxlen):

doc/source/index.rst

 
 * 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)
+
 
 Reference: conversions
 ----------------------

testing/backend_tests.py

         assert repr(p) == "<cdata 'int[]' owning %d bytes>" % (2*SIZE_OF_INT)
         #
         p = ffi.new("int[]", 0)
-        py.test.raises(IndexError, "p[0]")
+        #py.test.raises(IndexError, "p[0]") ---
+        #   actually works, for test_struct_containing_array_varsize_workaround
         py.test.raises(ValueError, ffi.new, "int[]", -1)
         assert repr(p) == "<cdata 'int[]' owning 0 bytes>"
 
         f.close()
         os.unlink(filename)
 
+    def test_struct_containing_array_varsize_workaround(self):
+        ffi = FFI(backend=self.Backend())
+        ffi.cdef("struct foo_s { int len; short data[0]; };")
+        p = ffi.new("char[]", ffi.sizeof("struct foo_s") + 7 * SIZE_OF_SHORT)
+        q = ffi.cast("struct foo_s *", p)
+        assert q.len == 0
+        assert q.data[6] == 0
+        q.data[6] = 15
+        assert q.data[6] == 15
+
     def test_new_struct_containing_array_varsize(self):
         py.test.skip("later?")
         ffi = FFI(backend=self.Backend())