[issue26628] Undefined behavior calling C functions with ctypes.Union arguments

Eryk Sun report at bugs.python.org
Thu Mar 24 23:04:40 EDT 2016


Eryk Sun added the comment:

> I would strongly argue to generally prohibit this 
> with an exception

I agree. A warning in the tutorial isn't sufficient. ctypes should raise an error when setting a union or bitfield struct type in argtypes or when passing one by value.

Here's some background to flesh out this issue. Both struct and union types are defined as the libffi type FFI_TYPE_STRUCT (13), since FFI_TYPE_UNION doesn't exist. 

    class MyUnion(ctypes.Union):
        _fields_ = [("x%s" % i, ctypes.c_double) for i in range(11)]

    >>> stgdict(MyUnion).length
    11
    >>> stgdict(MyUnion).ffi_type_pointer
    size: 8
    alignment: 8
    type: 13
    elements: 17918992

    >>> (ctypes.c_void_p * 12).from_address(17918992)[:]
    [140354450953784, 140354450953784, 140354450953784, 140354450953784,
     140354450953784, 140354450953784, 140354450953784, 140354450953784,
     140354450953784, 140354450953784, 140354450953784, None]

    >>> ffi_type.from_address(140354450953784)
    size: 8
    alignment: 8
    type: 3
    elements: LP_LP_ffi_type(<NULL>)

Type 3 is FFI_TYPE_DOUBLE.

    class MyStruct(ctypes.Structure):
        _fields_ = [("x%s" % i, ctypes.c_double) for i in range(11)]

    >>> stgdict(MyStruct).length
    11
    >>> stgdict(MyStruct).ffi_type_pointer
    size: 88
    alignment: 8
    type: 13
    elements: 17868096
    >>> (ctypes.c_void_p * 12).from_address(17868096)[:]
    [140354450953784, 140354450953784, 140354450953784, 140354450953784,
     140354450953784, 140354450953784, 140354450953784, 140354450953784,
     140354450953784, 140354450953784, 140354450953784, None]

As shown above, FFI_TYPE_STRUCT uses the `elements` array in its ffi_type. Each element is a pointer to an ffi_type for the corresponding field in the struct or union. This array is terminated by a NULL pointer. 

The C data size of a MyUnion instance is 8 bytes. This is less than 32 bytes (i.e. four 8-byte words), so the code in classify_argument rightfully assumes the argument won't be passed on the stack. Thus it classifies the register type(s) to use, presuming it's dealing with a struct. But since it's a union the total size of the elements is unrelated to the union's size. libffi would need to implement an FFI_TYPE_UNION type to support this as a separate case. Otherwise ctypes needs to actively forbid passing a union by value.

----------
nosy: +eryksun
versions: +Python 3.6

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue26628>
_______________________________________


More information about the Python-bugs-list mailing list