Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Fix subarray to object cast ownership details #21925

Merged
merged 2 commits into from Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/release/upcoming_changes/21925.compatibility.rst
@@ -0,0 +1,12 @@
Subarray to object cast now copies
----------------------------------
Casting a dtype that includes a subarray to an object will now ensure
a copy of the subarray. Previously an unsafe view was returned::

arr = np.ones(3, dtype=[("f", "i", 3)])
subarray_fields = arr.astype(object)[0]
subarray = subarray_fields[0] # "f" field

np.may_share_memory(subarray, arr)

Is now always false. While previously it was true for the specific cast.
11 changes: 10 additions & 1 deletion numpy/core/src/multiarray/arraytypes.c.src
Expand Up @@ -804,14 +804,23 @@ VOID_getitem(void *input, void *vap)
* could have special handling.
*/
PyObject *base = (PyObject *)ap;
while (Py_TYPE(base) == NULL) {
while (base != NULL && Py_TYPE(base) == NULL) {
base = PyArray_BASE((PyArrayObject *)base);
}
ret = (PyArrayObject *)PyArray_NewFromDescrAndBase(
&PyArray_Type, descr->subarray->base,
shape.len, shape.ptr, NULL, ip,
PyArray_FLAGS(ap) & ~NPY_ARRAY_F_CONTIGUOUS,
NULL, base);
if (base == NULL) {
/*
* Need to create a copy, or we may point to wrong data. This path
* is taken when no "valid" array is passed. This happens for
* casts.
*/
PyObject *copy = PyArray_FromArray(ret, NULL, NPY_ARRAY_ENSURECOPY);
Py_SETREF(ret, (PyArrayObject *)copy);
}
npy_free_cache_dim_obj(shape);
return (PyObject *)ret;
}
Expand Down
1 change: 1 addition & 0 deletions numpy/core/src/multiarray/dtype_transfer.c
Expand Up @@ -258,6 +258,7 @@ any_to_object_get_loop(
data->base.free = &_any_to_object_auxdata_free;
data->base.clone = &_any_to_object_auxdata_clone;
data->arr_fields.base = NULL;
Py_SET_TYPE(&data->arr_fields, NULL);
data->arr_fields.descr = context->descriptors[0];
Py_INCREF(data->arr_fields.descr);
data->arr_fields.flags = aligned ? NPY_ARRAY_ALIGNED : 0;
Expand Down
23 changes: 23 additions & 0 deletions numpy/core/tests/test_dtype.py
Expand Up @@ -650,6 +650,29 @@ def test_aligned_empty(self):
dt = np.dtype({"names": [], "formats": [], "itemsize": 0}, align=True)
assert dt == np.dtype([])

def test_subarray_base_item(self):
arr = np.ones(3, dtype=[("f", "i", 3)])
# Extracting the field "absorbs" the subarray into a view:
assert arr["f"].base is arr
# Extract the structured item, and then check the tuple component:
item = arr.item(0)
assert type(item) is tuple and len(item) == 1
assert item[0].base is arr

def test_subarray_cast_copies(self):
# Older versions of NumPy did NOT copy, but they got the ownership
# wrong (not actually knowing the correct base!). Versions since 1.21
# (I think) crashed fairly reliable. This defines the correct behavior
# as a copy. Keeping the ownership would be possible (but harder)
arr = np.ones(3, dtype=[("f", "i", 3)])
cast = arr.astype(object)
for fields in cast:
assert type(fields) == tuple and len(fields) == 1
subarr = fields[0]
assert subarr.base is None
assert subarr.flags.owndata


def iter_struct_object_dtypes():
"""
Iterates over a few complex dtypes and object pattern which
Expand Down