From 2592c6a005034ea10f3d1dfc5407f3ff7027adf7 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 15 Feb 2018 02:13:45 -0800 Subject: [PATCH 1/2] MAINT: Move dtype string functions to python Doing proper error checking of string concatenation in python super tedious. As a results, we're not doing it, making us prone to exceptions turning into segfaults. This moves all of the __str__ and __repr__ functions, where speed is irrelevant, into np.core._dtype. --- numpy/core/_dtype.py | 289 +++++++++++++++ numpy/core/src/multiarray/descriptor.c | 473 ++----------------------- numpy/core/src/multiarray/descriptor.h | 26 -- 3 files changed, 315 insertions(+), 473 deletions(-) create mode 100644 numpy/core/_dtype.py diff --git a/numpy/core/_dtype.py b/numpy/core/_dtype.py new file mode 100644 index 000000000000..24be89e08065 --- /dev/null +++ b/numpy/core/_dtype.py @@ -0,0 +1,289 @@ +""" +A place for code to be called from the implementation of np.dtype + +Some things are more easily handled in Python. + +""" +from __future__ import division, absolute_import, print_function + +import numpy as np + + +def dtype_construction_repr(dtype, include_align, short): + """ + Creates a string repr of the dtype, excluding the 'dtype()' part + surrounding the object. This object may be a string, a list, or + a dict depending on the nature of the dtype. This + is the object passed as the first parameter to the dtype + constructor, and if no additional constructor parameters are + given, will reproduce the exact memory layout. + + Parameters + ---------- + short : bool + If true, this creates a shorter repr using 'kind' and 'itemsize', instead + of the longer type name. + + include_align : bool + If true, this includes the 'align=True' parameter + inside the struct dtype construction dict when needed. Use this flag + if you want a proper repr string without the 'dtype()' part around it. + + If false, this does not preserve the + 'align=True' parameter or sticky NPY_ALIGNED_STRUCT flag for + struct arrays like the regular repr does, because the 'align' + flag is not part of first dtype constructor parameter. This + mode is intended for a full 'repr', where the 'align=True' is + provided as the second parameter. + """ + if dtype.fields: + return dtype_struct_str(dtype, include_align) + elif dtype.subdtype: + return dtype_subarray_str(dtype) + + + byteorder = dtype_byte_order_str(dtype) + + if dtype.type == np.bool_: + if short: + return "'?'" + else: + return "'bool'" + + elif dtype.type == np.object_: + # The object reference may be different sizes on different + # platforms, so it should never include the itemsize here. + return "'O'" + + elif dtype.type == np.string_: + if dtype.itemsize == 0: # TODO: PyDataType_ISUNSIZED + return "'S'" + else: + return "'S%d'" % dtype.itemsize + + elif dtype.type == np.unicode_: + if dtype.itemsize == 0: # TODO: PyDataType_ISUNSIZED + return "'%sU'" % byteorder + else: + return "'%sU%d'" % (byteorder, dtype.itemsize / 4) + + elif dtype.type == np.void: + if dtype.itemsize == 0: # TODO: PyDataType_ISUNSIZED + return "'V'" + else: + return "'V%d'" % dtype.itemsize + + elif dtype.type == np.datetime64: + return "'%sM8%s'" % (byteorder, get_datetimemeta(dtype)) + + elif dtype.type == np.timedelta64: + return "'%sm8%s'" % (byteorder, get_datetimemeta(dtype)) + + elif np.issubdtype(dtype, np.number): + # Short repr with endianness, like '' """ + # hack to obtain the native and swapped byte order characters + swapped = np.dtype(int).newbyteorder('s') + native = swapped.newbyteorder('s') + + byteorder = dtype.byteorder + if byteorder == '=': + return native.byteorder + if byteorder == 's': + return swapped.byteorder + elif byteorder == '|': + return '' + else: + return byteorder + + +def dtype_datetime_metadata_str(dtype): + # This is a hack since the data is not exposed to python in any other way + return dtype.name[dtype.name.rfind('['):] + + +def unpack_field(dtype, offset, title=None): + return dtype, offset, title + + +def dtype_struct_dict_str(dtype, includealignedflag): + # unpack the fields dictionary into ls + names = dtype.names + fld_dtypes = [] + offsets = [] + titles = [] + for name in names: + fld_dtype, offset, title = unpack_field(*dtype.fields[name]) + fld_dtypes.append(fld_dtype) + offsets.append(offset) + titles.append(title) + + # Build up a string to make the dictionary + + # First, the names + ret = "{'names':[" + ret += ",".join(repr(name) for name in names) + + # Second, the formats + ret += "], 'formats':[" + ret += ",".join( + dtype_construction_repr(fld_dtype, 0, 1) for fld_dtype in fld_dtypes) + + # Third, the offsets + ret += "], 'offsets':[" + ret += ",".join("%d" % offset for offset in offsets) + + # Fourth, the titles + if any(title is not None for title in titles): + ret += "], 'titles':[" + ret += ",".join(repr(title) for title in titles) + + # Fifth, the itemsize + ret += "], 'itemsize':%d}" % dtype.itemsize + + if (includealignedflag and dtype.isalignedstruct): + # Finally, the aligned flag + ret += ", 'aligned':True}" % dtype.itemsize + else: + ret += "}" + + return ret + + +def is_dtype_struct_simple_unaligned_layout(dtype): + """ + Checks whether the structured data type in 'dtype' + has a simple layout, where all the fields are in order, + and follow each other with no alignment padding. + + When this returns true, the dtype can be reconstructed + from a list of the field names and dtypes with no additional + dtype parameters. + """ + total_offset = 0 + for name in dtype.names: + fld_dtype, fld_offset, title = unpack_field(*dtype.fields[name]) + if fld_offset != total_offset: + return False + total_offset += fld_dtype.itemsize + if total_offset != dtype.itemsize: + return False + return True + +# +# Returns a string representation of a structured array, +# in a list format. +# + +def dtype_struct_list_str(dtype): + # Build up a string to make the list + + items = [] + for name in dtype.names: + fld_dtype, fld_offset, title = unpack_field(*dtype.fields[name]) + + item = "(" + if title is not None: + item += "({!r}, {!r}), ".format(title, name) + else: + item += "{!r}, ".format(name) + # Special case subarray handling here + if fld_dtype.subdtype is not None: + base, shape = fld_dtype.subdtype + item += "{}, {}".format( + dtype_construction_repr(fld_dtype.subarray.base, 0, 1), + shape + ) + else: + item += dtype_construction_repr(fld_dtype, 0, 1) + + item += ")" + items.append(item) + + return "[" + ", ".join(items) + "]" + + +def dtype_struct_str(dtype, include_align): + # The list str representation can't include the 'align=' flag, + # so if it is requested and the struct has the aligned flag set, + # we must use the dict str instead. + if not (include_align and dtype.isalignedstruct) and \ + is_dtype_struct_simple_unaligned_layout(dtype): + sub = dtype_struct_list_str(dtype) + + else: + sub = dtype_struct_dict_str(dtype, include_align) + + + # If the data type isn't the default, void, show it + if dtype.type != np.void: + # Note: We cannot get the type name from dtype.typeobj.tp_name + # because its value depends on whether the type is dynamically or + # statically allocated. Instead use __name__ and __module__. + # See https://docs.python.org/2/c-api/typeobj.html. + return "({t.__module__}.{t.__name__}, {f})".format(t=dtype.type, f=sub) + else: + return sub + + +def dtype_subarray_str(dtype): + base, shape = dtype.subdtype + return "({}, {})".format( + dtype_construction_repr(base, 0, 1), + shape + ) + +def dtype_str(dtype): + if dtype.fields: + return dtype_struct_str(dtype, 1) + elif dtype.subdtype: + return dtype_subarray_str(dtype) + elif not dtype.isnative: + return dtype_protocol_typestr_get(dtype) + else: + return dtype_typename_get(dtype) + + +def dtype_struct_repr(dtype): + s = "dtype(" + s += dtype_struct_str(dtype, 0) + if dtype.isalignedstruct: + s += ", align=True" + s += ")" + return s + + +def dtype_repr(dtype): + if dtype.fields: + return dtype_struct_repr(dtype) + else: + return "dtype({})".format(dtype_construction_repr(dtype, 1, 0)) diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index 91cf2ad9d51f..7180bc125569 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -3167,462 +3167,36 @@ is_dtype_struct_simple_unaligned_layout(PyArray_Descr *dtype) } /* - * Returns a string representation of a structured array, - * in a list format. + * The general dtype repr function. */ static PyObject * -arraydescr_struct_list_str(PyArray_Descr *dtype) +arraydescr_repr(PyArray_Descr *dtype) { - PyObject *names, *key, *fields, *ret, *tmp, *tup, *title; - Py_ssize_t i, names_size; - PyArray_Descr *fld_dtype; - int fld_offset; - - names = dtype->names; - names_size = PyTuple_GET_SIZE(names); - fields = dtype->fields; - - /* Build up a string to make the list */ - - /* Go through all the names */ - ret = PyUString_FromString("["); - for (i = 0; i < names_size; ++i) { - key = PyTuple_GET_ITEM(names, i); - tup = PyDict_GetItem(fields, key); - if (tup == NULL) { - return 0; - } - title = NULL; - if (!PyArg_ParseTuple(tup, "Oi|O", &fld_dtype, &fld_offset, &title)) { - PyErr_Clear(); - return 0; - } - PyUString_ConcatAndDel(&ret, PyUString_FromString("(")); - /* Check for whether to do titles as well */ - if (title != NULL && title != Py_None) { - PyUString_ConcatAndDel(&ret, PyUString_FromString("(")); - PyUString_ConcatAndDel(&ret, PyObject_Repr(title)); - PyUString_ConcatAndDel(&ret, PyUString_FromString(", ")); - PyUString_ConcatAndDel(&ret, PyObject_Repr(key)); - PyUString_ConcatAndDel(&ret, PyUString_FromString("), ")); - } - else { - PyUString_ConcatAndDel(&ret, PyObject_Repr(key)); - PyUString_ConcatAndDel(&ret, PyUString_FromString(", ")); - } - /* Special case subarray handling here */ - if (PyDataType_HASSUBARRAY(fld_dtype)) { - tmp = arraydescr_construction_repr( - fld_dtype->subarray->base, 0, 1); - PyUString_ConcatAndDel(&ret, tmp); - PyUString_ConcatAndDel(&ret, PyUString_FromString(", ")); - PyUString_ConcatAndDel(&ret, - PyObject_Str(fld_dtype->subarray->shape)); - } - else { - tmp = arraydescr_construction_repr(fld_dtype, 0, 1); - PyUString_ConcatAndDel(&ret, tmp); - } - PyUString_ConcatAndDel(&ret, PyUString_FromString(")")); - if (i != names_size - 1) { - PyUString_ConcatAndDel(&ret, PyUString_FromString(", ")); - } + PyObject *_numpy_dtype; + PyObject *res; + _numpy_dtype = PyImport_ImportModule("numpy.core._dtype"); + if (_numpy_dtype == NULL) { + return NULL; } - PyUString_ConcatAndDel(&ret, PyUString_FromString("]")); - - return ret; + res = PyObject_CallMethod(_numpy_dtype, "dtype_repr", "O", dtype); + Py_DECREF(_numpy_dtype); + return res; } - /* - * Returns a string representation of a structured array, - * in a dict format. + * The general dtype str function. */ -static PyObject * -arraydescr_struct_dict_str(PyArray_Descr *dtype, int includealignedflag) -{ - PyObject *names, *key, *fields, *ret, *tmp, *tup, *title; - Py_ssize_t i, names_size; - PyArray_Descr *fld_dtype; - int fld_offset, has_titles; - - names = dtype->names; - names_size = PyTuple_GET_SIZE(names); - fields = dtype->fields; - has_titles = 0; - - /* Build up a string to make the dictionary */ - - /* First, the names */ - ret = PyUString_FromString("{'names':["); - for (i = 0; i < names_size; ++i) { - key = PyTuple_GET_ITEM(names, i); - PyUString_ConcatAndDel(&ret, PyObject_Repr(key)); - if (i != names_size - 1) { - PyUString_ConcatAndDel(&ret, PyUString_FromString(",")); - } - } - /* Second, the formats */ - PyUString_ConcatAndDel(&ret, PyUString_FromString("], 'formats':[")); - for (i = 0; i < names_size; ++i) { - key = PyTuple_GET_ITEM(names, i); - tup = PyDict_GetItem(fields, key); - if (tup == NULL) { - return 0; - } - title = NULL; - if (!PyArg_ParseTuple(tup, "Oi|O", &fld_dtype, &fld_offset, &title)) { - PyErr_Clear(); - return 0; - } - /* Check for whether to do titles as well */ - if (title != NULL && title != Py_None) { - has_titles = 1; - } - tmp = arraydescr_construction_repr(fld_dtype, 0, 1); - PyUString_ConcatAndDel(&ret, tmp); - if (i != names_size - 1) { - PyUString_ConcatAndDel(&ret, PyUString_FromString(",")); - } - } - /* Third, the offsets */ - PyUString_ConcatAndDel(&ret, PyUString_FromString("], 'offsets':[")); - for (i = 0; i < names_size; ++i) { - key = PyTuple_GET_ITEM(names, i); - tup = PyDict_GetItem(fields, key); - if (tup == NULL) { - return 0; - } - if (!PyArg_ParseTuple(tup, "Oi|O", &fld_dtype, &fld_offset, &title)) { - PyErr_Clear(); - return 0; - } - PyUString_ConcatAndDel(&ret, PyUString_FromFormat("%d", fld_offset)); - if (i != names_size - 1) { - PyUString_ConcatAndDel(&ret, PyUString_FromString(",")); - } - } - /* Fourth, the titles */ - if (has_titles) { - PyUString_ConcatAndDel(&ret, PyUString_FromString("], 'titles':[")); - for (i = 0; i < names_size; ++i) { - key = PyTuple_GET_ITEM(names, i); - tup = PyDict_GetItem(fields, key); - if (tup == NULL) { - return 0; - } - title = Py_None; - if (!PyArg_ParseTuple(tup, "Oi|O", &fld_dtype, - &fld_offset, &title)) { - PyErr_Clear(); - return 0; - } - PyUString_ConcatAndDel(&ret, PyObject_Repr(title)); - if (i != names_size - 1) { - PyUString_ConcatAndDel(&ret, PyUString_FromString(",")); - } - } - } - if (includealignedflag && (dtype->flags&NPY_ALIGNED_STRUCT)) { - /* Finally, the itemsize/itemsize and aligned flag */ - PyUString_ConcatAndDel(&ret, - PyUString_FromFormat("], 'itemsize':%d, 'aligned':True}", - (int)dtype->elsize)); - } - else { - /* Finally, the itemsize/itemsize*/ - PyUString_ConcatAndDel(&ret, - PyUString_FromFormat("], 'itemsize':%d}", (int)dtype->elsize)); - } - - return ret; -} - -/* Produces a string representation for a structured dtype */ -static PyObject * -arraydescr_struct_str(PyArray_Descr *dtype, int includealignflag) -{ - PyObject *sub; - - /* - * The list str representation can't include the 'align=' flag, - * so if it is requested and the struct has the aligned flag set, - * we must use the dict str instead. - */ - if (!(includealignflag && (dtype->flags&NPY_ALIGNED_STRUCT)) && - is_dtype_struct_simple_unaligned_layout(dtype)) { - sub = arraydescr_struct_list_str(dtype); - } - else { - sub = arraydescr_struct_dict_str(dtype, includealignflag); - } - - /* If the data type isn't the default, void, show it */ - if (dtype->typeobj != &PyVoidArrType_Type) { - /* - * Note: We cannot get the type name from dtype->typeobj->tp_name - * because its value depends on whether the type is dynamically or - * statically allocated. Instead use __name__ and __module__. - * See https://docs.python.org/2/c-api/typeobj.html. - */ - - PyObject *str_name, *namestr, *str_module, *modulestr, *ret; - - str_name = PyUString_FromString("__name__"); - namestr = PyObject_GetAttr((PyObject*)(dtype->typeobj), str_name); - Py_DECREF(str_name); - - if (namestr == NULL) { - /* this should never happen since types always have __name__ */ - PyErr_Format(PyExc_RuntimeError, - "dtype does not have a __name__ attribute"); - return NULL; - } - - str_module = PyUString_FromString("__module__"); - modulestr = PyObject_GetAttr((PyObject*)(dtype->typeobj), str_module); - Py_DECREF(str_module); - - ret = PyUString_FromString("("); - if (modulestr != NULL) { - /* Note: if modulestr == NULL, the type is unpicklable */ - PyUString_ConcatAndDel(&ret, modulestr); - PyUString_ConcatAndDel(&ret, PyUString_FromString(".")); - } - PyUString_ConcatAndDel(&ret, namestr); - PyUString_ConcatAndDel(&ret, PyUString_FromString(", ")); - PyUString_ConcatAndDel(&ret, sub); - PyUString_ConcatAndDel(&ret, PyUString_FromString(")")); - return ret; - } - else { - return sub; - } -} - -/* Produces a string representation for a subarray dtype */ -static PyObject * -arraydescr_subarray_str(PyArray_Descr *dtype) -{ - PyObject *p, *ret; - - ret = PyUString_FromString("("); - p = arraydescr_construction_repr(dtype->subarray->base, 0, 1); - PyUString_ConcatAndDel(&ret, p); - PyUString_ConcatAndDel(&ret, PyUString_FromString(", ")); - PyUString_ConcatAndDel(&ret, PyObject_Str(dtype->subarray->shape)); - PyUString_ConcatAndDel(&ret, PyUString_FromString(")")); - - return ret; -} - static PyObject * arraydescr_str(PyArray_Descr *dtype) { - PyObject *sub; - - if (PyDataType_HASFIELDS(dtype)) { - sub = arraydescr_struct_str(dtype, 1); - } - else if (PyDataType_HASSUBARRAY(dtype)) { - sub = arraydescr_subarray_str(dtype); - } - else if (PyDataType_ISFLEXIBLE(dtype) || !PyArray_ISNBO(dtype->byteorder)) { - sub = arraydescr_protocol_typestr_get(dtype); - } - else { - sub = arraydescr_typename_get(dtype); - } - return sub; -} - -/* - * The dtype repr function specifically for structured arrays. - */ -static PyObject * -arraydescr_struct_repr(PyArray_Descr *dtype) -{ - PyObject *sub, *s; - - s = PyUString_FromString("dtype("); - sub = arraydescr_struct_str(dtype, 0); - if (sub == NULL) { + PyObject *_numpy_dtype; + PyObject *res; + _numpy_dtype = PyImport_ImportModule("numpy.core._dtype"); + if (_numpy_dtype == NULL) { return NULL; } - - PyUString_ConcatAndDel(&s, sub); - - /* If it's an aligned structure, add the align=True parameter */ - if (dtype->flags&NPY_ALIGNED_STRUCT) { - PyUString_ConcatAndDel(&s, PyUString_FromString(", align=True")); - } - - PyUString_ConcatAndDel(&s, PyUString_FromString(")")); - return s; -} - -/* See descriptor.h for documentation */ -NPY_NO_EXPORT PyObject * -arraydescr_construction_repr(PyArray_Descr *dtype, int includealignflag, - int shortrepr) -{ - PyObject *ret; - PyArray_DatetimeMetaData *meta; - char byteorder[2]; - - if (PyDataType_HASFIELDS(dtype)) { - return arraydescr_struct_str(dtype, includealignflag); - } - else if (PyDataType_HASSUBARRAY(dtype)) { - return arraydescr_subarray_str(dtype); - } - - /* Normalize byteorder to '<' or '>' */ - switch (dtype->byteorder) { - case NPY_NATIVE: - byteorder[0] = NPY_NATBYTE; - break; - case NPY_SWAP: - byteorder[0] = NPY_OPPBYTE; - break; - case NPY_IGNORE: - byteorder[0] = '\0'; - break; - default: - byteorder[0] = dtype->byteorder; - break; - } - byteorder[1] = '\0'; - - /* Handle booleans, numbers, and custom dtypes */ - if (dtype->type_num == NPY_BOOL) { - if (shortrepr) { - return PyUString_FromString("'?'"); - } - else { - return PyUString_FromString("'bool'"); - } - } - else if (PyTypeNum_ISNUMBER(dtype->type_num)) { - /* Short repr with endianness, like 'byteorder != NPY_NATIVE && - dtype->byteorder != NPY_IGNORE)) { - return PyUString_FromFormat("'%s%c%d'", byteorder, - (int)dtype->kind, dtype->elsize); - } - /* Longer repr, like 'float64' */ - else { - char *kindstr; - switch (dtype->kind) { - case 'u': - kindstr = "uint"; - break; - case 'i': - kindstr = "int"; - break; - case 'f': - kindstr = "float"; - break; - case 'c': - kindstr = "complex"; - break; - default: - PyErr_Format(PyExc_RuntimeError, - "internal dtype repr error, unknown kind '%c'", - (int)dtype->kind); - return NULL; - } - return PyUString_FromFormat("'%s%d'", kindstr, 8*dtype->elsize); - } - } - else if (PyTypeNum_ISUSERDEF(dtype->type_num)) { - char *s = strrchr(dtype->typeobj->tp_name, '.'); - if (s == NULL) { - return PyUString_FromString(dtype->typeobj->tp_name); - } - else { - return PyUString_FromStringAndSize(s + 1, strlen(s) - 1); - } - } - - /* All the rest which don't fit in the same pattern */ - switch (dtype->type_num) { - /* - * The object reference may be different sizes on different - * platforms, so it should never include the itemsize here. - */ - case NPY_OBJECT: - return PyUString_FromString("'O'"); - - case NPY_STRING: - if (PyDataType_ISUNSIZED(dtype)) { - return PyUString_FromString("'S'"); - } - else { - return PyUString_FromFormat("'S%d'", (int)dtype->elsize); - } - - case NPY_UNICODE: - if (PyDataType_ISUNSIZED(dtype)) { - return PyUString_FromFormat("'%sU'", byteorder); - } - else { - return PyUString_FromFormat("'%sU%d'", byteorder, - (int)dtype->elsize / 4); - } - - case NPY_VOID: - if (PyDataType_ISUNSIZED(dtype)) { - return PyUString_FromString("'V'"); - } - else { - return PyUString_FromFormat("'V%d'", (int)dtype->elsize); - } - - case NPY_DATETIME: - meta = get_datetime_metadata_from_dtype(dtype); - if (meta == NULL) { - return NULL; - } - ret = PyUString_FromFormat("'%sM8", byteorder); - ret = append_metastr_to_string(meta, 0, ret); - PyUString_ConcatAndDel(&ret, PyUString_FromString("'")); - return ret; - - case NPY_TIMEDELTA: - meta = get_datetime_metadata_from_dtype(dtype); - if (meta == NULL) { - return NULL; - } - ret = PyUString_FromFormat("'%sm8", byteorder); - ret = append_metastr_to_string(meta, 0, ret); - PyUString_ConcatAndDel(&ret, PyUString_FromString("'")); - return ret; - - default: - PyErr_SetString(PyExc_RuntimeError, "Internal error: NumPy dtype " - "unrecognized type number"); - return NULL; - } -} - -/* - * The general dtype repr function. - */ -static PyObject * -arraydescr_repr(PyArray_Descr *dtype) -{ - PyObject *ret; - - if (PyDataType_HASFIELDS(dtype)) { - return arraydescr_struct_repr(dtype); - } - else { - ret = PyUString_FromString("dtype("); - PyUString_ConcatAndDel(&ret, - arraydescr_construction_repr(dtype, 1, 0)); - PyUString_ConcatAndDel(&ret, PyUString_FromString(")")); - return ret; - } + res = PyObject_CallMethod(_numpy_dtype, "dtype_str", "O", dtype); + Py_DECREF(_numpy_dtype); + return res; } static PyObject * @@ -3760,10 +3334,15 @@ _check_has_fields(PyArray_Descr *self) { if (!PyDataType_HASFIELDS(self)) { PyObject *astr = arraydescr_str(self); + if (astr == NULL) { + return -1; + } #if defined(NPY_PY3K) - PyObject *bstr = PyUnicode_AsUnicodeEscapeString(astr); - Py_DECREF(astr); - astr = bstr; + { + PyObject *bstr = PyUnicode_AsUnicodeEscapeString(astr); + Py_DECREF(astr); + astr = bstr; + } #endif PyErr_Format(PyExc_KeyError, "There are no fields in dtype %s.", PyBytes_AsString(astr)); diff --git a/numpy/core/src/multiarray/descriptor.h b/numpy/core/src/multiarray/descriptor.h index f950411955b7..5a3e4b15f232 100644 --- a/numpy/core/src/multiarray/descriptor.h +++ b/numpy/core/src/multiarray/descriptor.h @@ -14,32 +14,6 @@ _arraydescr_fromobj(PyObject *obj); NPY_NO_EXPORT int is_dtype_struct_simple_unaligned_layout(PyArray_Descr *dtype); -/* - * Creates a string repr of the dtype, excluding the 'dtype()' part - * surrounding the object. This object may be a string, a list, or - * a dict depending on the nature of the dtype. This - * is the object passed as the first parameter to the dtype - * constructor, and if no additional constructor parameters are - * given, will reproduce the exact memory layout. - * - * If 'shortrepr' is non-zero, this creates a shorter repr using - * 'kind' and 'itemsize', instead of the longer type name. - * - * If 'includealignflag' is true, this includes the 'align=True' parameter - * inside the struct dtype construction dict when needed. Use this flag - * if you want a proper repr string without the 'dtype()' part around it. - * - * If 'includealignflag' is false, this does not preserve the - * 'align=True' parameter or sticky NPY_ALIGNED_STRUCT flag for - * struct arrays like the regular repr does, because the 'align' - * flag is not part of first dtype constructor parameter. This - * mode is intended for a full 'repr', where the 'align=True' is - * provided as the second parameter. - */ -NPY_NO_EXPORT PyObject * -arraydescr_construction_repr(PyArray_Descr *dtype, int includealignflag, - int shortrepr); - extern NPY_NO_EXPORT char *_datetime_strings[]; #endif From 17bac32ff7acb8dd30866f83144db6bf2048bd92 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 15 Feb 2018 09:41:18 -0800 Subject: [PATCH 2/2] BUG: Add missing error checking to exception string creation. If `repr` throws this would previously segfault --- numpy/core/src/umath/ufunc_type_resolution.c | 53 ++++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/numpy/core/src/umath/ufunc_type_resolution.c b/numpy/core/src/umath/ufunc_type_resolution.c index 1766ba5648c8..4deb5176b376 100644 --- a/numpy/core/src/umath/ufunc_type_resolution.c +++ b/numpy/core/src/umath/ufunc_type_resolution.c @@ -63,17 +63,21 @@ PyUFunc_ValidateCasting(PyUFuncObject *ufunc, if (i < nin) { if (!PyArray_CanCastArrayTo(operands[i], dtypes[i], casting)) { PyObject *errmsg; - errmsg = PyUString_FromFormat("Cannot cast ufunc %s " - "input from ", ufunc_name); - PyUString_ConcatAndDel(&errmsg, - PyObject_Repr((PyObject *)PyArray_DESCR(operands[i]))); - PyUString_ConcatAndDel(&errmsg, - PyUString_FromString(" to ")); - PyUString_ConcatAndDel(&errmsg, - PyObject_Repr((PyObject *)dtypes[i])); - PyUString_ConcatAndDel(&errmsg, - PyUString_FromFormat(" with casting rule %s", - npy_casting_to_string(casting))); + PyObject *fmt = PyUString_FromFormat( + "Cannot cast ufunc %s input from {} to {} with casting " + "rule %s", + ufunc_name, npy_casting_to_string(casting) + ); + if (fmt == NULL) { + return -1; + } + errmsg = PyObject_CallMethod(fmt, "format", "OO", + (PyObject *)PyArray_DESCR(operands[i]), + (PyObject *)dtypes[i]); + Py_DECREF(fmt); + if (errmsg == NULL){ + return -1; + } PyErr_SetObject(PyExc_TypeError, errmsg); Py_DECREF(errmsg); return -1; @@ -82,17 +86,22 @@ PyUFunc_ValidateCasting(PyUFuncObject *ufunc, if (!PyArray_CanCastTypeTo(dtypes[i], PyArray_DESCR(operands[i]), casting)) { PyObject *errmsg; - errmsg = PyUString_FromFormat("Cannot cast ufunc %s " - "output from ", ufunc_name); - PyUString_ConcatAndDel(&errmsg, - PyObject_Repr((PyObject *)dtypes[i])); - PyUString_ConcatAndDel(&errmsg, - PyUString_FromString(" to ")); - PyUString_ConcatAndDel(&errmsg, - PyObject_Repr((PyObject *)PyArray_DESCR(operands[i]))); - PyUString_ConcatAndDel(&errmsg, - PyUString_FromFormat(" with casting rule %s", - npy_casting_to_string(casting))); + PyObject *fmt = PyUString_FromFormat( + "Cannot cast ufunc %s output from {} to {} with casting " + "rule %s", + ufunc_name, npy_casting_to_string(casting) + ); + if (fmt == NULL) { + return -1; + } + errmsg = PyObject_CallMethod(fmt, "format", "OO", + (PyObject *)dtypes[i], + (PyObject *)PyArray_DESCR(operands[i]) + ); + Py_DECREF(fmt); + if (errmsg == NULL){ + return -1; + } PyErr_SetObject(PyExc_TypeError, errmsg); Py_DECREF(errmsg); return -1;