[issue18060] Updating _fields_ of a derived struct type yields a bad cif

Jeffrey Kintscher report at bugs.python.org
Thu May 16 18:56:46 EDT 2019


Jeffrey Kintscher <websurfer at surf2c.net> added the comment:

The t1.py test case calls both PyCStructUnionType_update_stgdict() and PyCStgDict_clone(), both of which are broken. The test case in t2.py is simpler than t1.py in that it only calls PyCStructUnionType_update_stgdict().

PyCStructUnionType_update_stgdict() gets called whenever _fields_ gets assigned a new list of element tuples. The function is supposed to copy any inherited element pointers in parent classes and append the new elements. The element pointers array in each child class is supposed to be cumulative (i.e. parent class element pointers plus child class element pointers).

_fields_ is represented by a StgDictObject structure, and the relevant member variables are 'ffi_type_pointer.elements', 'len', and 'size'. 'ffi_type_pointer.elements' is an array of ffi_type pointers padded with a NULL pointer at the end. 'size' is the number of bytes in the array excluding the padding. 'len' is the number of elements in the current class (i.e. excluding the elements in parent classes).

PyCStructUnionType_update_stgdict() allocates a new 'ffi_type_pointer.elements' array by adding 1 to the sum of 'len' and the 'len' member of the parent class, then multiplying by sizeof(ffi_type *). This works just fine when there is a single parent class, but breaks when there are multiple levels of inheritance. For example:

   class Base(Structure):
      _fields_ = [('y', c_double),
                  ('x', c_double)]

    class Mid(Base):
        _fields_ = []

    class Vector(Mid):
        _fields_ = []

PyCStructUnionType_update_stgdict() gets called for each of the three _fileds_ assignments. Assuming a pointer size of 8 bytes, Base has these values:

    ffi_type_pointer.elements = array of 3 pointers (x, y, and NULL padding).
    len = 2
    size = 16 (i.e. 2 * sizeof(ffi_type *))

Mid has these values:

    ffi_type_pointer.elements = array of 3 pointers (x, y, and NULL padding).
    len = 0
    size = 16 (i.e. 2 * sizeof(ffi_type *))

Vector has these values:

    ffi_type_pointer.elements = array of 1 pointer (x)
    len = 0
    size = 16 (i.e. 2 * sizeof(ffi_type *))

Vector's 'len' and 'size' are correct, but 'ffi_type_pointer.elements' contains one element instead of three. Vector should have:

    ffi_type_pointer.elements = array of 3 pointers (x, y, and NULL padding).
    len = 0
    size = 16 (i.e. 2 * sizeof(ffi_type *))

'ffi_type_pointer.elements' got truncated because PyCStructUnionType_update_stgdict() uses the parent class's 'len' field to determine the size of the new array to allocate. As can be seen, Mid's 'len' is zero, so a new array with one element gets allocated and copied (0 element pointers plus a trailing NULL pointer for padding). Notice that Vector's 'size' is correct because the value is calculated as Mid's 'size' plus zero (for zero elements being added in the child class).

Similarly, PyCStgDict_clone() has the same problem because it also uses the similar calculations based on 'len' to determine the new 'ffi_type_pointer.elements' array size that gets allocated and copied.

The solution proposed by lauri.alanko effectively redefines the 'len' member variable to be the total number of elements defined in the inheritance chain for _fields_. While this does fix the allocation/copying issue, it breaks other code that expects the 'len' variables in the parent and child classes to be distinct values instead of cumulative. For example (from StructureTestCase.test_positional_args() in Lib/ctypes/test/test_structures.py),

    class W(Structure):
        _fields_ = [("a", c_int), ("b", c_int)]
    class X(W):
        _fields_ = [("c", c_int)]
    class Y(X):
        pass
    class Z(Y):
        _fields_ = [("d", c_int), ("e", c_int), ("f", c_int)]

    z = Z(1, 2, 3, 4, 5, 6)

will throw an exception because Z's 'len' will be 6 instead of the expected 3.

A better solution is to use 'size' to allocate and copy 'ffi_type_pointer.elements' since its value is already properly calculated and propagated through inheritance.

----------
Added file: https://bugs.python.org/file48332/t2.py

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue18060>
_______________________________________


More information about the Python-bugs-list mailing list