diff options
-rw-r--r-- | numpy/core/src/multiarray/arrayobject.c | 3 | ||||
-rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 23 | ||||
-rw-r--r-- | numpy/core/src/umath/loops.c.src | 16 | ||||
-rw-r--r-- | numpy/core/tests/test_casting_unittests.py | 4 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 15 |
6 files changed, 56 insertions, 7 deletions
diff --git a/numpy/core/src/multiarray/arrayobject.c b/numpy/core/src/multiarray/arrayobject.c index d18fe1b10..b1302738d 100644 --- a/numpy/core/src/multiarray/arrayobject.c +++ b/numpy/core/src/multiarray/arrayobject.c @@ -1261,7 +1261,8 @@ array_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) descr = NULL; goto fail; } - if (PyDataType_FLAGCHK(descr, NPY_ITEM_HASOBJECT)) { + /* Logic shared by `empty`, `empty_like`, and `ndarray.__new__` */ + if (PyDataType_REFCHK(PyArray_DESCR(ret))) { /* place Py_None in object positions */ PyArray_FillObjectArray(ret, Py_None); if (PyErr_Occurred()) { diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index a9f8dfdd2..2539fdb57 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -716,6 +716,7 @@ OBJECT_getitem(void *ip, void *NPY_UNUSED(ap)) PyObject *obj; memcpy(&obj, ip, sizeof(obj)); if (obj == NULL) { + /* We support NULL, but still try to guarantee this never happens! */ Py_RETURN_NONE; } else { @@ -733,6 +734,7 @@ OBJECT_setitem(PyObject *op, void *ov, void *NPY_UNUSED(ap)) memcpy(&obj, ov, sizeof(obj)); Py_INCREF(op); + /* A newly created array/buffer may only be NULLed, so XDECREF */ Py_XDECREF(obj); memcpy(ov, &op, sizeof(op)); diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index c3d66dd6b..ebd990724 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -1068,6 +1068,18 @@ PyArray_NewLikeArrayWithShape(PyArrayObject *prototype, NPY_ORDER order, 0, subok ? (PyObject *)prototype : NULL); } + if (ret == NULL) { + return NULL; + } + + /* Logic shared by `empty`, `empty_like`, and `ndarray.__new__` */ + if (PyDataType_REFCHK(PyArray_DESCR((PyArrayObject *)ret))) { + PyArray_FillObjectArray((PyArrayObject *)ret, Py_None); + if (PyErr_Occurred()) { + Py_DECREF(ret); + return NULL; + } + } return ret; } @@ -2979,22 +2991,23 @@ PyArray_Empty(int nd, npy_intp const *dims, PyArray_Descr *type, int is_f_order) * PyArray_NewFromDescr steals a ref, * but we need to look at type later. * */ - Py_INCREF(type); - ret = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, type, nd, dims, NULL, NULL, is_f_order, NULL); - if (ret != NULL && PyDataType_REFCHK(type)) { + if (ret == NULL) { + return NULL; + } + + /* Logic shared by `empty`, `empty_like`, and `ndarray.__new__` */ + if (PyDataType_REFCHK(PyArray_DESCR(ret))) { PyArray_FillObjectArray(ret, Py_None); if (PyErr_Occurred()) { Py_DECREF(ret); - Py_DECREF(type); return NULL; } } - Py_DECREF(type); return (PyObject *)ret; } diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 9ae686399..e5104db81 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -214,6 +214,8 @@ PyUFunc_O_O(char **args, npy_intp const *dimensions, npy_intp const *steps, void UNARY_LOOP { PyObject *in1 = *(PyObject **)ip1; PyObject **out = (PyObject **)op1; + /* We allow NULL, but try to guarantee non-NULL to downstream */ + assert(in1 != NULL); PyObject *ret = f(in1 ? in1 : Py_None); if (ret == NULL) { return; @@ -231,6 +233,8 @@ PyUFunc_O_O_method(char **args, npy_intp const *dimensions, npy_intp const *step UNARY_LOOP { PyObject *in1 = *(PyObject **)ip1; PyObject **out = (PyObject **)op1; + /* We allow NULL, but try to guarantee non-NULL to downstream */ + assert(in1 != NULL); PyObject *ret, *func; func = PyObject_GetAttrString(in1 ? in1 : Py_None, meth); if (func != NULL && !PyCallable_Check(func)) { @@ -268,6 +272,9 @@ PyUFunc_OO_O(char **args, npy_intp const *dimensions, npy_intp const *steps, voi PyObject *in1 = *(PyObject **)ip1; PyObject *in2 = *(PyObject **)ip2; PyObject **out = (PyObject **)op1; + /* We allow NULL, but try to guarantee non-NULL to downstream */ + assert(in1 != NULL); + assert(in2 != NULL); PyObject *ret = f(in1 ? in1 : Py_None, in2 ? in2 : Py_None); if (ret == NULL) { return; @@ -286,6 +293,10 @@ PyUFunc_OOO_O(char **args, npy_intp const *dimensions, npy_intp const *steps, vo PyObject *in2 = *(PyObject **)ip2; PyObject *in3 = *(PyObject **)ip3; PyObject **out = (PyObject **)op1; + /* We allow NULL, but try to guarantee non-NULL to downstream */ + assert(in1 != NULL); + assert(in2 != NULL); + assert(in3 != NULL); PyObject *ret = f( in1 ? in1 : Py_None, in2 ? in2 : Py_None, @@ -308,6 +319,9 @@ PyUFunc_OO_O_method(char **args, npy_intp const *dimensions, npy_intp const *ste PyObject *in1 = *(PyObject **)ip1; PyObject *in2 = *(PyObject **)ip2; PyObject **out = (PyObject **)op1; + /* We allow NULL, but try to guarantee non-NULL to downstream */ + assert(in1 != NULL); + assert(in2 != NULL); PyObject *ret = PyObject_CallMethod(in1 ? in1 : Py_None, meth, "(O)", in2); if (ret == NULL) { @@ -349,6 +363,8 @@ PyUFunc_On_Om(char **args, npy_intp const *dimensions, npy_intp const *steps, vo } for(j = 0; j < nin; j++) { in = *((PyObject **)ptrs[j]); + /* We allow NULL, but try to guarantee non-NULL to downstream */ + assert(in != NULL); if (in == NULL) { in = Py_None; } diff --git a/numpy/core/tests/test_casting_unittests.py b/numpy/core/tests/test_casting_unittests.py index 5c5ff55b4..16ecb1943 100644 --- a/numpy/core/tests/test_casting_unittests.py +++ b/numpy/core/tests/test_casting_unittests.py @@ -10,6 +10,7 @@ import pytest import textwrap import enum import random +import ctypes import numpy as np from numpy.lib.stride_tricks import as_strided @@ -786,7 +787,8 @@ class TestCasting: # None to <other> casts may succeed or fail, but a NULL'ed array must # behave the same as one filled with None's. arr_normal = np.array([None] * 5) - arr_NULLs = np.empty_like([None] * 5) + arr_NULLs = np.empty_like(arr_normal) + ctypes.memset(arr_NULLs.ctypes.data, 0, arr_NULLs.nbytes) # If the check fails (maybe it should) the test would lose its purpose: assert arr_NULLs.tobytes() == b"\x00" * arr_NULLs.nbytes diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 84fdf545f..ef2fc7d2e 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -1172,6 +1172,21 @@ class TestCreation: a = np.array([1, Decimal(1)]) a = np.array([[1], [Decimal(1)]]) + @pytest.mark.parametrize("dtype", [object, "O,O", "O,(3)O", "(2,3)O"]) + @pytest.mark.parametrize("function", [ + np.ndarray, np.empty, + lambda shape, dtype: np.empty_like(np.empty(shape, dtype=dtype))]) + def test_object_initialized_to_None(self, function, dtype): + # NumPy has support for object fields to be NULL (meaning None) + # but generally, we should always fill with the proper None, and + # downstream may rely on that. (For fully initialized arrays!) + arr = function(3, dtype=dtype) + # We expect a fill value of None, which is not NULL: + expected = np.array(None).tobytes() + expected = expected * (arr.nbytes // len(expected)) + assert arr.tobytes() == expected + + class TestStructured: def test_subarray_field_access(self): a = np.zeros((3, 5), dtype=[('a', ('i4', (2, 2)))]) |