Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
bpo-47197: Fix void return type handling in ctypes
_ctypes_get_ffi_type never returns ffi_type_void. If the
return type is specified as None, we need set the libffi
return type to void, but just taking the output from
_ctypes_get_ffi_type will make the return type be sint.

This fixes two spots where ctypes accidentally converts
None return type to sint rather than void, causing crashes
on Emscripten targets.
  • Loading branch information
hoodmane committed Apr 2, 2022
commit f48101008c4b2735ef51c557d3095d07e33aa6e5
2 changes: 1 addition & 1 deletion Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ CThunkObject *_ctypes_alloc_callback(PyObject *callable,
#endif
result = ffi_prep_cif(&p->cif, cc,
Py_SAFE_DOWNCAST(nargs, Py_ssize_t, int),
_ctypes_get_ffi_type(restype),
&p->restype,
&p->atypes[0]);
if (result != FFI_OK) {
PyErr_Format(PyExc_RuntimeError,
Expand Down
7 changes: 6 additions & 1 deletion Modules/_ctypes/callproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,12 @@ PyObject *_ctypes_callproc(PPROC pProc,
}
}

rtype = _ctypes_get_ffi_type(restype);
if (restype == Py_None) {
rtype = &ffi_type_void;
} else {
rtype = _ctypes_get_ffi_type(restype);
}
Comment on lines +1212 to +1216

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with ctypes. Would it make sense to check for None in _ctypes_get_ffi_type() rather than here?

ffi_type *_ctypes_get_ffi_type(PyObject *obj)
{
    StgDictObject *dict;
    if (obj == NULL)
        return &ffi_type_sint;
    if (obj == Py_None)
        return &ffi_type_void;
    dict = PyType_stgdict(obj);
    if (dict == NULL)
        return &ffi_type_sint;
...

@hoodmane hoodmane Apr 3, 2022 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is better? I ran into a similar issue when writing the libffi port: _ctypes_get_ffi_type is used for the arguments. Return types need a little bit of special handling, like for instance it doesn't make sense to have an argument of type ffi_type_void. I would argue that it would be safest to do something like:

if (obj == Py_None)
	// Set an error, arguments can't have type None!

but I'm not that familiar with the ctypes interface -- are we supposed to use None if we are not sure about the argument's type? Similarly,

    if (obj == NULL)
        return &ffi_type_sint;

looks kind of scary to me. Why did we pass in NULL to this function? If this function set exceptions in these weird cases and they were handled as appropriate at the call site, the bug I'm fixing here probably wouldn't have happened in the first place.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NULL value is used to indicate not-set and is quite pervasive in the ctypes codebase. Function prototypes are not required to supply argtypes or restype. It seems _ctypes_get_ffi_type() is designed for arguments so the additional checking for Py_None outside of it would be correct.


resbuf = alloca(max(rtype->size, sizeof(ffi_arg)));

#ifdef _Py_MEMORY_SANITIZER
Expand Down