From b30f1891760f06f056925641d20f2f968c15ce9d Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Tue, 12 Oct 2021 10:19:40 -0600 Subject: [PATCH] BUG: core: result_type(0, np.timedelta64(4)) would seg. fault. Backport of #20088. --- numpy/core/src/multiarray/convert_datatype.c | 61 +++++++++++++------- numpy/core/tests/test_dtype.py | 8 +++ 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index cd0e21098bcd..49e4550804b6 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -1536,6 +1536,40 @@ should_use_min_scalar(npy_intp narrs, PyArrayObject **arr, } +/* + * Utility function used only in PyArray_ResultType for value-based logic. + * See that function for the meaning and contents of the parameters. + */ +static PyArray_Descr * +get_descr_from_cast_or_value( + npy_intp i, + PyArrayObject *arrs[], + npy_intp ndtypes, + PyArray_Descr *descriptor, + PyArray_DTypeMeta *common_dtype) +{ + PyArray_Descr *curr; + if (NPY_LIKELY(i < ndtypes || + !(PyArray_FLAGS(arrs[i-ndtypes]) & _NPY_ARRAY_WAS_PYSCALAR))) { + curr = PyArray_CastDescrToDType(descriptor, common_dtype); + } + else { + /* + * Unlike `PyArray_CastToDTypeAndPromoteDescriptors`, deal with + * plain Python values "graciously". This recovers the original + * value the long route, but it should almost never happen... + */ + PyObject *tmp = PyArray_GETITEM(arrs[i-ndtypes], + PyArray_BYTES(arrs[i-ndtypes])); + if (tmp == NULL) { + return NULL; + } + curr = common_dtype->discover_descr_from_pyobject(common_dtype, tmp); + Py_DECREF(tmp); + } + return curr; +} + /*NUMPY_API * * Produces the result type of a bunch of inputs, using the same rules @@ -1672,28 +1706,15 @@ PyArray_ResultType( result = common_dtype->default_descr(common_dtype); } else { - result = PyArray_CastDescrToDType(all_descriptors[0], common_dtype); + result = get_descr_from_cast_or_value( + 0, arrs, ndtypes, all_descriptors[0], common_dtype); + if (result == NULL) { + goto error; + } for (npy_intp i = 1; i < ndtypes+narrs; i++) { - PyArray_Descr *curr; - if (NPY_LIKELY(i < ndtypes || - !(PyArray_FLAGS(arrs[i-ndtypes]) & _NPY_ARRAY_WAS_PYSCALAR))) { - curr = PyArray_CastDescrToDType(all_descriptors[i], common_dtype); - } - else { - /* - * Unlike `PyArray_CastToDTypeAndPromoteDescriptors` deal with - * plain Python values "graciously". This recovers the original - * value the long route, but it should almost never happen... - */ - PyObject *tmp = PyArray_GETITEM( - arrs[i-ndtypes], PyArray_BYTES(arrs[i-ndtypes])); - if (tmp == NULL) { - goto error; - } - curr = common_dtype->discover_descr_from_pyobject(common_dtype, tmp); - Py_DECREF(tmp); - } + PyArray_Descr *curr = get_descr_from_cast_or_value( + i, arrs, ndtypes, all_descriptors[i], common_dtype); if (curr == NULL) { goto error; } diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py index 3d15009ea765..1dcf1fb8ebd8 100644 --- a/numpy/core/tests/test_dtype.py +++ b/numpy/core/tests/test_dtype.py @@ -1539,3 +1539,11 @@ class mytype: # Tests that a dtype must have its type field set up to np.dtype # or in this case a builtin instance. create_custom_field_dtype(blueprint, mytype, 2) + + +def test_result_type_integers_and_unitless_timedelta64(): + # Regression test for gh-20077. The following call of `result_type` + # would cause a seg. fault. + td = np.timedelta64(4) + result = np.result_type(0, td) + assert_dtype_equal(result, td.dtype)