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