Jeffrey Kintscher <websur...@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 <rep...@bugs.python.org>
<https://bugs.python.org/issue18060>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to