diff options
Diffstat (limited to 'numpy/core')
-rw-r--r-- | numpy/core/_internal.py | 13 | ||||
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 96 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 23 |
3 files changed, 122 insertions, 10 deletions
diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py index 8c6596d13..9990bacf0 100644 --- a/numpy/core/_internal.py +++ b/numpy/core/_internal.py @@ -756,3 +756,16 @@ def _ufunc_doc_signature_formatter(ufunc): out_args=out_args, kwargs=kwargs ) + + +def _is_from_ctypes(obj): + # determine if an object comes from ctypes, in order to work around + # a bug in the buffer protocol for those objects, bpo-10746 + try: + # ctypes class are new-style, so have an __mro__. This probably fails + # for ctypes classes with multiple inheritance. + ctype_base = type(obj).__mro__[-2] + # right now, they're part of the _ctypes module + return 'ctypes' in ctype_base.__module__ + except Exception: + return False diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index de9eb6355..7367902cc 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -11,6 +11,7 @@ #include "npy_config.h" +#include "npy_import.h" #include "npy_pycompat.h" #include "multiarraymodule.h" @@ -1312,16 +1313,6 @@ _dtype_from_buffer_3118(PyObject *memoryview) if (descr == NULL) { return NULL; } - - /* Sanity check */ - if (descr->elsize != view->itemsize) { - PyErr_SetString( - PyExc_RuntimeError, - "Item size computed from the PEP 3118 buffer format " - "string does not match the actual item size."); - Py_DECREF(descr); - return NULL; - } } else { /* If no format is specified, just assume a byte array @@ -1335,6 +1326,28 @@ _dtype_from_buffer_3118(PyObject *memoryview) } +/* + * Call the python _is_from_ctypes + */ +NPY_NO_EXPORT int +_is_from_ctypes(PyObject *obj) { + PyObject *ret_obj; + static PyObject *py_func = NULL; + + npy_cache_import("numpy.core._internal", "_is_from_ctypes", &py_func); + + if (py_func == NULL) { + return -1; + } + ret_obj = PyObject_CallFunctionObjArgs(py_func, obj, NULL); + if (ret_obj == NULL) { + return -1; + } + + return PyObject_IsTrue(ret_obj); +} + + NPY_NO_EXPORT PyObject * _array_from_buffer_3118(PyObject *memoryview) { @@ -1354,6 +1367,68 @@ _array_from_buffer_3118(PyObject *memoryview) return NULL; } + /* Sanity check */ + if (descr->elsize != view->itemsize) { + /* Ctypes has bugs in its PEP3118 implementation, which we need to + * work around. + * + * bpo-10746 + * bpo-32780 + * bpo-32782 + * + * Note that even if the above are fixed in master, we have to drop the + * early patch versions of python to actually make use of the fixes. + */ + + int is_ctypes = _is_from_ctypes(view->obj); + if (is_ctypes < 0) { + /* This error is not useful */ + PyErr_WriteUnraisable(view->obj); + is_ctypes = 0; + } + + if (!is_ctypes) { + /* This object has no excuse for a broken PEP3118 buffer */ + PyErr_SetString( + PyExc_RuntimeError, + "Item size computed from the PEP 3118 buffer format " + "string does not match the actual item size."); + Py_DECREF(descr); + return NULL; + } + + if (PyErr_Warn( + PyExc_RuntimeWarning, + "A builtin ctypes object gave a PEP3118 format " + "string that does not match its itemsize, so a " + "best-guess will be made of the data type. " + "Newer versions of python may behave correctly.") < 0) { + Py_DECREF(descr); + return NULL; + } + + /* Thankfully, np.dtype(ctypes_type) works in most cases. + * For an array input, this produces a dtype containing all the + * dimensions, so the array is now 0d. + */ + nd = 0; + descr = (PyArray_Descr *)PyObject_CallFunctionObjArgs( + (PyObject *)&PyArrayDescr_Type, Py_TYPE(view->obj), NULL); + if (descr == NULL) { + return NULL; + } + if (descr->elsize != view->len) { + PyErr_SetString( + PyExc_RuntimeError, + "For the given ctypes object, neither the item size " + "computed from the PEP 3118 buffer format nor from " + "converting the type to a np.dtype matched the actual " + "size. This is a bug both in python and numpy"); + Py_DECREF(descr); + return NULL; + } + } + if (view->shape != NULL) { int k; if (nd > NPY_MAXDIMS || nd < 0) { @@ -1399,6 +1474,7 @@ _array_from_buffer_3118(PyObject *memoryview) flags, NULL, memoryview); return r; + fail: Py_XDECREF(r); Py_XDECREF(descr); diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 1bca46bf7..05dd2efd1 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -6562,6 +6562,29 @@ class TestNewBufferProtocol(object): ValueError, "format string", np.array, m) + def test_ctypes_integer_via_memoryview(self): + # gh-11150, due to bpo-10746 + for c_integer in {ctypes.c_int, ctypes.c_long, ctypes.c_longlong}: + value = c_integer(42) + with warnings.catch_warnings(record=True) as w: + warnings.filterwarnings('always', r'.*\bctypes\b', RuntimeWarning) + np.asarray(value) + + def test_ctypes_struct_via_memoryview(self): + # gh-10528 + class foo(ctypes.Structure): + _fields_ = [('a', ctypes.c_uint8), ('b', ctypes.c_uint32)] + f = foo(a=1, b=2) + + with warnings.catch_warnings(record=True) as w: + warnings.filterwarnings('always', r'.*\bctypes\b', RuntimeWarning) + arr = np.asarray(f) + + assert_equal(arr['a'], 1) + assert_equal(arr['b'], 2) + f.a = 3 + assert_equal(arr['a'], 3) + class TestArrayAttributeDeletion(object): |