From 67fb488b737a39f01dadde8b5e307b9df476f7b9 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 31 Aug 2026 14:37:59 -0700 Subject: [PATCH 1/8] Check PyMem_Malloc for allocation failure --- mkl_fft/_pydfti.pyx | 82 +++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/mkl_fft/_pydfti.pyx b/mkl_fft/_pydfti.pyx index f6b90e28..8a89bcc9 100644 --- a/mkl_fft/_pydfti.pyx +++ b/mkl_fft/_pydfti.pyx @@ -68,19 +68,27 @@ cdef void _capsule_destructor(object caps) noexcept: def _tls_dfti_cache_capsule(): - cdef DftiCache *_cache_struct + cdef DftiCache *_cache_struct = NULL - init = getattr(_tls, "initialized", None) - if (init is None): + capsule = getattr(_tls, "capsule", None) + if (capsule is None): _cache_struct = PyMem_Malloc(sizeof(DftiCache)) - # important to initialized + if (_cache_struct is NULL): + raise MemoryError( + "Failed to allocate memory for DFTI descriptor cache" + ) + # important to initialize _cache_struct.initialized = 0 _cache_struct.hand = NULL - _tls.initialized = True - _tls.capsule = cpython.pycapsule.PyCapsule_New( - _cache_struct, capsule_name, &_capsule_destructor - ) - capsule = getattr(_tls, "capsule", None) + try: + capsule = cpython.pycapsule.PyCapsule_New( + _cache_struct, capsule_name, &_capsule_destructor + ) + except: + PyMem_Free(_cache_struct) + raise + # only publish to TLS once the capsule owns the allocation + _tls.capsule = capsule if (not cpython.pycapsule.PyCapsule_IsValid(capsule, capsule_name)): raise ValueError("Internal Error: invalid capsule stored in TLS") return capsule @@ -199,15 +207,24 @@ cdef cnp.ndarray _pad_array( b_ndim = cnp.PyArray_NDIM(x_arr) b_shape = PyMem_Malloc(b_ndim * sizeof(cnp.npy_intp)) - memcpy(b_shape, cnp.PyArray_DIMS(x_arr), b_ndim * sizeof(cnp.npy_intp)) - b_shape[axis] = n + if (b_shape is NULL): + raise MemoryError("Failed to allocate memory for the array shape") + + try: + memcpy( + b_shape, cnp.PyArray_DIMS(x_arr), b_ndim * sizeof(cnp.npy_intp) + ) + b_shape[axis] = n - # allocating temporary buffer - x_arr_is_fortran = cnp.PyArray_CHKFLAGS(x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS) - b_arr = cnp.PyArray_EMPTY( - b_ndim, b_shape, b_type, x_arr_is_fortran - ) # 0 for C-contiguous - PyMem_Free(b_shape) + # allocating temporary buffer + x_arr_is_fortran = cnp.PyArray_CHKFLAGS( + x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS + ) + b_arr = cnp.PyArray_EMPTY( + b_ndim, b_shape, b_type, x_arr_is_fortran + ) # 0 for C-contiguous + finally: + PyMem_Free(b_shape) ind = [slice(0, None, None), ] * b_ndim ind[axis] = slice(0, cnp.PyArray_DIM(x_arr, axis), None) @@ -307,17 +324,26 @@ cdef cnp.ndarray _allocate_result( f_ndim = cnp.PyArray_NDIM(x_arr) f_shape = PyMem_Malloc(f_ndim * sizeof(cnp.npy_intp)) - memcpy(f_shape, cnp.PyArray_DIMS(x_arr), f_ndim * sizeof(cnp.npy_intp)) - # if dimension is negative, do not alter the dimension - if n_ > 0: - f_shape[axis_] = n_ - - # allocating output buffer - x_arr_is_fortran = cnp.PyArray_CHKFLAGS(x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS) - f_arr = cnp.PyArray_EMPTY( - f_ndim, f_shape, f_type, x_arr_is_fortran - ) # 0 for C-contiguous - PyMem_Free(f_shape) + if (f_shape is NULL): + raise MemoryError("Failed to allocate memory for the array shape") + + try: + memcpy( + f_shape, cnp.PyArray_DIMS(x_arr), f_ndim * sizeof(cnp.npy_intp) + ) + # if dimension is negative, do not alter the dimension + if n_ > 0: + f_shape[axis_] = n_ + + # allocating output buffer + x_arr_is_fortran = cnp.PyArray_CHKFLAGS( + x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS + ) + f_arr = cnp.PyArray_EMPTY( + f_ndim, f_shape, f_type, x_arr_is_fortran + ) # 0 for C-contiguous + finally: + PyMem_Free(f_shape) return f_arr From 7650af8f2a3736f01fe72b71bdc07fdbedd67a4f Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 1 Sep 2026 16:45:08 -0700 Subject: [PATCH 2/8] Make multi-iterator constructor return status to check for allocation failure Also fixes a typo and makes _direct_fftnd check status --- mkl_fft/_pydfti.pyx | 46 ++++++++--------- mkl_fft/src/mklfft.c.src | 105 +++++++++++++++++++++++++++++++++------ mkl_fft/src/multi_iter.h | 82 +++++++++++++++++++----------- 3 files changed, 167 insertions(+), 66 deletions(-) diff --git a/mkl_fft/_pydfti.pyx b/mkl_fft/_pydfti.pyx index 8a89bcc9..04dda953 100644 --- a/mkl_fft/_pydfti.pyx +++ b/mkl_fft/_pydfti.pyx @@ -165,6 +165,18 @@ cdef extern from "src/mklfft.h": int float_cfloat_mkl_ifftnd_out(cnp.ndarray, cnp.ndarray, double) int double_cdouble_mkl_ifftnd_out(cnp.ndarray, cnp.ndarray, double) char * mkl_dfti_error(int) + int DFTI_MEMORY_ERROR + + +cdef _raise_dfti_error(int status): + """Raise the exception described by a non-zero MKL DFTI status""" + cdef char * c_error_msg = mkl_dfti_error(status) + cdef bytes py_error_msg = c_error_msg + + message = f"Internal error occurred: {str(py_error_msg)}" + if status == DFTI_MEMORY_ERROR: + raise MemoryError(message) + raise ValueError(message) # Initialize numpy @@ -406,8 +418,6 @@ def _c2c_fft1d_impl(x, n=None, axis=-1, direction=+1, double fsc=1.0, out=None): cdef long n_, axis_ cdef int x_type, f_type, status = 0 cdef int ALL_HARMONICS = 1 - cdef char * c_error_msg = NULL - cdef bytes py_error_msg cdef DftiCache *_cache if direction not in [-1, +1]: @@ -470,9 +480,7 @@ def _c2c_fft1d_impl(x, n=None, axis=-1, direction=+1, double fsc=1.0, out=None): status = 1 if status: - c_error_msg = mkl_dfti_error(status) - py_error_msg = c_error_msg - raise ValueError("Internal error occurred: {}".format(py_error_msg)) + _raise_dfti_error(status) n_max = cnp.PyArray_DIM(x_arr, axis_) if (n_ < n_max): @@ -569,9 +577,7 @@ def _c2c_fft1d_impl(x, n=None, axis=-1, direction=+1, double fsc=1.0, out=None): ) if (status): - c_error_msg = mkl_dfti_error(status) - py_error_msg = c_error_msg - raise ValueError("Internal error occurred: {}".format(py_error_msg)) + _raise_dfti_error(status) if out is not None and f_arr is not out: out[...] = f_arr @@ -595,8 +601,6 @@ def _r2c_fft1d_impl( cdef long n_, axis_ cdef int x_type, f_type, status, requirement cdef int HALF_HARMONICS = 0 # give only positive index harmonics - cdef char * c_error_msg = NULL - cdef bytes py_error_msg cdef DftiCache *_cache x_arr = _process_arguments(x, n, axis, &axis_, &n_, &in_place, &xnd, 1) @@ -665,11 +669,7 @@ def _r2c_fft1d_impl( ) if (status): - c_error_msg = mkl_dfti_error(status) - py_error_msg = c_error_msg - raise ValueError( - "Internal error occurred: {}".format(str(py_error_msg)) - ) + _raise_dfti_error(status) if out is not None and f_arr is not out: out[...] = f_arr @@ -692,8 +692,6 @@ def _c2r_fft1d_impl( cdef int xnd, in_place, int_n cdef long n_, axis_ cdef int x_type, f_type, status - cdef char * c_error_msg = NULL - cdef bytes py_error_msg cdef DftiCache *_cache int_n = _is_integral(n) @@ -768,11 +766,7 @@ def _c2r_fft1d_impl( ) if (status): - c_error_msg = mkl_dfti_error(status) - py_error_msg = c_error_msg - raise ValueError( - "Internal error occurred: {}".format(str(py_error_msg)) - ) + _raise_dfti_error(status) if out is not None and f_arr is not out: out[...] = f_arr @@ -785,7 +779,7 @@ def _direct_fftnd( x, direction=+1, double fsc=1.0, out=None ): """Perform n-dimensional FFT over all axes""" - cdef int err + cdef int err = 0 cdef cnp.ndarray x_arr "xxnd_arrayObject" cdef cnp.ndarray f_arr "ffnd_arrayObject" cdef int in_place, x_type, f_type @@ -848,6 +842,9 @@ def _direct_fftnd( else: raise ValueError("An input argument x is not complex type array") + if err: + _raise_dfti_error(err) + return x_arr else: if x_type == cnp.NPY_CDOUBLE or x_type == cnp.NPY_DOUBLE: @@ -890,6 +887,9 @@ def _direct_fftnd( else: raise ValueError("An input argument x is not complex type array") + if err: + _raise_dfti_error(err) + if out is not None and f_arr is not out: out[...] = f_arr return out diff --git a/mkl_fft/src/mklfft.c.src b/mkl_fft/src/mklfft.c.src index 8d9b42c2..18410480 100644 --- a/mkl_fft/src/mklfft.c.src +++ b/mkl_fft/src/mklfft.c.src @@ -548,11 +548,19 @@ int @name@_mkl_@mode@_in(PyArrayObject* x_inout, npy_intp n, int axis, double fs int i; mask = (int *) mkl_malloc((x_rank-1)*sizeof(int), 64); + if (!mask) { + status = DFTI_MEMORY_ERROR; + goto failed; + } for(i = 0; i < axis; i++) mask[i] = i; for(i = axis + 1; i < x_rank; i++) mask[i-1] = i; - multi_iter_masked_new(&mit, x_shape, x_rank, mask, x_rank - 1); + if (multi_iter_masked_new(&mit, x_shape, x_rank, mask, x_rank - 1)) { + mkl_free(mask); + status = DFTI_MEMORY_ERROR; + goto failed; + } while(!MultiIter_Done(mit)) { char *tmp; @@ -646,7 +654,7 @@ int @REALIN@_@COMPLEXOUT@_mkl_@mode@_out( assert(xout_itemsize == sizeof(@MKL_OUT_TYPE@)); assert(xin_itemsize == sizeof(@MKL_IN_TYPE@)); - assert(xout_itemsize = 2 * xin_itemsize); + assert(xout_itemsize == 2 * xin_itemsize); for(i=0; i < axis; i++) assert(xin_shape[i] == xout_shape[i]); @@ -744,11 +752,20 @@ int @REALIN@_@COMPLEXOUT@_mkl_@mode@_out( int i; mask = (int *) mkl_malloc((xin_rank-1)*sizeof(int), 64); + if (!mask) { + status = DFTI_MEMORY_ERROR; + goto failed; + } for(i = 0; i < axis; i++) mask[i] = i; for(i = axis + 1; i < xin_rank; i++) mask[i-1] = i; - multi_iter_masked_new(&mit, xin_shape, xin_rank, mask, xin_rank - 1); + if (multi_iter_masked_new( + &mit, xin_shape, xin_rank, mask, xin_rank - 1)) { + mkl_free(mask); + status = DFTI_MEMORY_ERROR; + goto failed; + } while(!MultiIter_Done(mit)) { char *tmp1, *tmp2; @@ -805,11 +822,19 @@ int @REALIN@_@COMPLEXOUT@_mkl_@mode@_out( npy_intp *half_shape; half_shape = (npy_intp *) mkl_malloc(xout_rank * sizeof(npy_intp), 64); + if (!half_shape) { + status = DFTI_MEMORY_ERROR; + goto failed; + } memcpy(half_shape, xout_shape, xout_rank * sizeof(npy_intp)); half_shape[axis] = (n_last > 2) ? n_last - nh_last: 0; - multi_iter_new(&mit, half_shape, xout_rank); + if (multi_iter_new(&mit, half_shape, xout_rank)) { + mkl_free(half_shape); + status = DFTI_MEMORY_ERROR; + goto failed; + } mkl_free(half_shape); while(!MultiIter_Done(mit)) { @@ -1006,11 +1031,20 @@ int @COMPLEXIN@_@COMPLEXOUT@_mkl_@mode@_out( int i; mask = (int *) mkl_malloc((xin_rank-1)*sizeof(int), 64); + if (!mask) { + status = DFTI_MEMORY_ERROR; + goto failed; + } for(i = 0; i < axis; i++) mask[i] = i; for(i = axis + 1; i < xin_rank; i++) mask[i-1] = i; - multi_iter_masked_new(&mit, xin_shape, xin_rank, mask, xin_rank - 1); + if (multi_iter_masked_new( + &mit, xin_shape, xin_rank, mask, xin_rank - 1)) { + mkl_free(mask); + status = DFTI_MEMORY_ERROR; + goto failed; + } while(!MultiIter_Done(mit)) { char *tmp1, *tmp2; @@ -1193,11 +1227,20 @@ int int i; mask = (int *) mkl_malloc((xin_rank-1)*sizeof(int), 64); + if (!mask) { + status = DFTI_MEMORY_ERROR; + goto failed; + } for(i = 0; i < axis; i++) mask[i] = i; for(i = axis + 1; i < xin_rank; i++) mask[i-1] = i; - multi_iter_masked_new(&mit, xin_shape, xin_rank, mask, xin_rank - 1); + if (multi_iter_masked_new( + &mit, xin_shape, xin_rank, mask, xin_rank - 1)) { + mkl_free(mask); + status = DFTI_MEMORY_ERROR; + goto failed; + } while(!MultiIter_Done(mit)) { char *tmp1, *tmp2; @@ -1285,7 +1328,10 @@ int } else { // TODO: should rather use alloca or something optimized for small allocations x_shape_mkl = (MKL_LONG *) mkl_malloc(x_rank*sizeof(MKL_LONG), 64); - if (!x_shape_mkl) goto cleanup; + if (!x_shape_mkl) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } for(i = 0; i < x_rank; i++) x_shape_mkl[i] = _to_mkl_long(x_shape[i]); } @@ -1308,7 +1354,10 @@ int if (0 != status) goto cleanup; input_strides = (MKL_LONG *) mkl_malloc((x_rank + 1) * sizeof(MKL_LONG), 64); - if (!input_strides) goto cleanup; + if (!input_strides) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } input_strides[0] = 0; for(i = 0; i < x_rank; i++) { @@ -1400,7 +1449,10 @@ int } else { // TODO: should rather use alloca or something optimized for small allocations xin_shape_mkl = (MKL_LONG *) mkl_malloc(xin_rank * sizeof(MKL_LONG), 64); - if (!xin_shape_mkl) goto cleanup; + if (!xin_shape_mkl) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } for(i = 0; i < xin_rank; i++) xin_shape_mkl[i] = _to_mkl_long(xin_shape[i]); } @@ -1423,10 +1475,16 @@ int if (0 != status) goto cleanup; input_strides = (MKL_LONG *) mkl_malloc((xin_rank + 1) * sizeof(MKL_LONG), 64); - if (!input_strides) goto cleanup; + if (!input_strides) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } output_strides = (MKL_LONG *) mkl_malloc((xout_rank + 1) * sizeof(MKL_LONG), 64); - if (!output_strides) goto cleanup; + if (!output_strides) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } input_strides[0] = 0; output_strides[0] = 0; @@ -1534,7 +1592,10 @@ int } else { /* TODO: should rather use alloca or something optimized for small allocations */ xin_shape_mkl = (MKL_LONG *) mkl_malloc(xin_rank * sizeof(MKL_LONG), 64); - if (!xin_shape_mkl) goto cleanup; + if (!xin_shape_mkl) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } for(i = 0; i < xin_rank; i++) xin_shape_mkl[i] = _to_mkl_long(xin_shape[i]); } @@ -1557,10 +1618,16 @@ int if (0 != status) goto cleanup; input_strides = (MKL_LONG *) mkl_malloc((xin_rank + 1) * sizeof(MKL_LONG), 64); - if (!input_strides) goto cleanup; + if (!input_strides) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } output_strides = (MKL_LONG *) mkl_malloc((xout_rank + 1) * sizeof(MKL_LONG), 64); - if (!output_strides) goto cleanup; + if (!output_strides) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } input_strides[0] = 0; output_strides[0] = 0; @@ -1603,13 +1670,21 @@ int int i, last_idx = xout_rank - 1; half_shape = (npy_intp *) mkl_malloc(xout_rank * sizeof(npy_intp), 64); + if (!half_shape) { + status = DFTI_MEMORY_ERROR; + goto cleanup; + } memcpy(half_shape, xout_shape, xout_rank * sizeof(npy_intp)); n_last = xout_shape[last_idx]; nh_last = (n_last/2) + 1; half_shape[last_idx] = (n_last > 2) ? n_last - nh_last: 0; - multi_iter_new(&mit, half_shape, xout_rank); + if (multi_iter_new(&mit, half_shape, xout_rank)) { + mkl_free(half_shape); + status = DFTI_MEMORY_ERROR; + goto cleanup; + } mkl_free(half_shape); while(!MultiIter_Done(mit)) { diff --git a/mkl_fft/src/multi_iter.h b/mkl_fft/src/multi_iter.h index f3f73129..3bc7988e 100644 --- a/mkl_fft/src/multi_iter.h +++ b/mkl_fft/src/multi_iter.h @@ -62,18 +62,20 @@ typedef struct multi_iter_masked_t #define MultiIter_ShapeElem(mit, i) ((mit).shape)[(i)] #define MultiIter_MaskElem(mit, i) ((mit).mask)[(i)] -/* -void multi_iter_new(multi_iter_t*, npy_intp*, int); -void multi_iter_masked_new(multi_iter_masked_t*, npy_intp*, int, int*, int); +/* The constructors return 0 on success, and -1 if a memory allocation failed. + The iterator is safe to pass to the matching destructor, and the caller + is expected to propagate the error rather than iterate. */ +static NPY_INLINE int multi_iter_new(multi_iter_t *, npy_intp *, int); +static NPY_INLINE int + multi_iter_masked_new(multi_iter_masked_t *, npy_intp *, int, int *, int); -void multi_iter_free(multi_iter_t*); -int multi_iter_next(multi_iter_t*); +static NPY_INLINE void multi_iter_free(multi_iter_t *); +static NPY_INLINE int multi_iter_next(multi_iter_t *); -void multi_iter_masked_free(multi_iter_masked_t*); -int multi_iter_masked_next(multi_iter_masked_t*); -*/ +static NPY_INLINE void multi_iter_masked_free(multi_iter_masked_t *); +static NPY_INLINE int multi_iter_masked_next(multi_iter_masked_t *); -static NPY_INLINE void +static NPY_INLINE int multi_iter_new(multi_iter_t *mi, npy_intp shape[], int rank) { int i; @@ -81,10 +83,17 @@ static NPY_INLINE void assert(rank > 0); + MultiIter_Rank(*mi) = rank; + /* an iterator that failed to allocate must not be iterated over */ + MultiIter_Done(*mi) = 1; + MultiIter_Index(*mi) = (npy_intp *)mkl_calloc(rank, sizeof(npy_intp), 64); MultiIter_Shape(*mi) = (npy_intp *)mkl_malloc(rank * sizeof(npy_intp), 64); + if (!MultiIter_Index(*mi) || !MultiIter_Shape(*mi)) { + multi_iter_free(mi); + return -1; + } memcpy(MultiIter_Shape(*mi), shape, rank * sizeof(npy_intp)); - MultiIter_Rank(*mi) = rank; for (i = 0; i < rank; i++) { d |= MultiIter_IndexElem(*mi, i) >= MultiIter_ShapeElem(*mi, i); @@ -94,24 +103,36 @@ static NPY_INLINE void MultiIter_Done(*mi) = d; - return; + return 0; } -static NPY_INLINE void multi_iter_masked_new(multi_iter_masked_t *mi, - npy_intp shape[], - int rank, - int mask[], - int mask_len) +static NPY_INLINE int multi_iter_masked_new(multi_iter_masked_t *mi, + npy_intp shape[], + int rank, + int mask[], + int mask_len) { int i; char d = 0; assert(rank > 0); + assert(mask_len > 0); + + MultiIter_Rank(*mi) = rank; + MultiIter_MaskLength(*mi) = mask_len; + /* an iterator that failed to allocate must not be iterated over */ + MultiIter_Done(*mi) = 1; MultiIter_Index(*mi) = (npy_intp *)mkl_calloc(rank, sizeof(npy_intp), 64); MultiIter_Shape(*mi) = (npy_intp *)mkl_malloc(rank * sizeof(npy_intp), 64); + MultiIter_Mask(*mi) = (int *)mkl_malloc(mask_len * sizeof(int), 64); + if (!MultiIter_Index(*mi) || !MultiIter_Shape(*mi) || + !MultiIter_Mask(*mi)) { + multi_iter_masked_free(mi); + return -1; + } memcpy(MultiIter_Shape(*mi), shape, rank * sizeof(npy_intp)); - MultiIter_Rank(*mi) = rank; + memcpy(MultiIter_Mask(*mi), mask, mask_len * sizeof(int)); for (i = 0; i < rank; i++) { d |= MultiIter_IndexElem(*mi, i) >= MultiIter_ShapeElem(*mi, i); @@ -121,25 +142,26 @@ static NPY_INLINE void multi_iter_masked_new(multi_iter_masked_t *mi, MultiIter_Done(*mi) = d; - assert(mask_len > 0); - MultiIter_MaskLength(*mi) = mask_len; - MultiIter_Mask(*mi) = (int *)mkl_malloc(mask_len * sizeof(int), 64); - memcpy(MultiIter_Mask(*mi), mask, mask_len * sizeof(int)); - - return; + return 0; } static NPY_INLINE void multi_iter_masked_free(multi_iter_masked_t *mi) { if (mi) { - if (MultiIter_Index(*mi)) + if (MultiIter_Index(*mi)) { mkl_free(MultiIter_Index(*mi)); + MultiIter_Index(*mi) = NULL; + } - if (MultiIter_Shape(*mi)) + if (MultiIter_Shape(*mi)) { mkl_free(MultiIter_Shape(*mi)); + MultiIter_Shape(*mi) = NULL; + } - if (MultiIter_Mask(*mi)) + if (MultiIter_Mask(*mi)) { mkl_free(MultiIter_Mask(*mi)); + MultiIter_Mask(*mi) = NULL; + } } return; @@ -148,11 +170,15 @@ static NPY_INLINE void multi_iter_masked_free(multi_iter_masked_t *mi) static NPY_INLINE void multi_iter_free(multi_iter_t *mi) { if (mi) { - if (MultiIter_Index(*mi)) + if (MultiIter_Index(*mi)) { mkl_free(MultiIter_Index(*mi)); + MultiIter_Index(*mi) = NULL; + } - if (MultiIter_Shape(*mi)) + if (MultiIter_Shape(*mi)) { mkl_free(MultiIter_Shape(*mi)); + MultiIter_Shape(*mi) = NULL; + } } return; From 5f9acbd6de2aa9d9af8290d78daa8db71a430b34 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 1 Sep 2026 16:45:15 -0700 Subject: [PATCH 3/8] Remove format strings --- mkl_fft/tests/test_fftnd.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/mkl_fft/tests/test_fftnd.py b/mkl_fft/tests/test_fftnd.py index d42e0647..4aa6f159 100644 --- a/mkl_fft/tests/test_fftnd.py +++ b/mkl_fft/tests/test_fftnd.py @@ -50,7 +50,7 @@ def _get_rtol_atol(x): or dt == np.complex128 or dt == np.float32 or dt == np.complex64 - ), "Unexpected dtype {}".format(dt) + ), f"Unexpected dtype {dt}" return reps_64, atol_64 @@ -75,18 +75,14 @@ def test_matrix1(self): t2, rtol=r_tol, atol=a_tol, - err_msg="failed test for dtype {}, max abs diff: {}".format( - d.dtype, np.max(np.abs(t1 - t2)) - ), + err_msg=f"failed test for dtype {d.dtype}, max abs diff: {np.max(np.abs(t1 - t2))}", ) assert_allclose( t1, t3, rtol=r_tol, atol=a_tol, - err_msg="failed test for dtype {}, max abs diff: {}".format( - d.dtype, np.max(np.abs(t1 - t3)) - ), + err_msg=f"failed test for dtype {d.dtype}, max abs diff: {np.max(np.abs(t1 - t3))}", ) def test_matrix2(self): @@ -100,9 +96,7 @@ def test_matrix2(self): t, rtol=r_tol, atol=a_tol, - err_msg="failed test for dtype {}, max abs diff: {}".format( - d.dtype, np.max(np.abs(d - t)) - ), + err_msg=f"failed test for dtype {d.dtype}, max abs diff: {np.max(np.abs(d - t))}", ) def test_matrix3(self): @@ -116,9 +110,7 @@ def test_matrix3(self): t, rtol=r_tol, atol=a_tol, - err_msg="failed test for dtype {}, max abs diff: {}".format( - d.dtype, np.max(np.abs(d - t)) - ), + err_msg=f"failed test for dtype {d.dtype}, max abs diff: {np.max(np.abs(d - t))}", ) def test_matrix4(self): @@ -163,9 +155,7 @@ def test_matrix6(self): t, rtol=r_tol, atol=a_tol, - err_msg="failed test for dtype {}, max abs diff: {}".format( - d.dtype, np.max(np.abs(d - t)) - ), + err_msg=f"failed test for dtype {d.dtype}, max abs diff: {np.max(np.abs(d - t))}", ) From 6aeadf660d6b499948e18727f8dd49cf6380cb7c Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 1 Sep 2026 16:48:25 -0700 Subject: [PATCH 4/8] Add gh-373 to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9253ca45..5224fb12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +* Multi-iterator constructors now return a status corresponding to allocation success or failure, and raise `MemoryError` instead of `ValueError` [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) +* `_direct_fftnd` now also checks the status returned by the backend instead of discarding it [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) ### Fixed * Declared `f_ndim` as a C `int` in `_allocate_result` so the buffer size is computed in C rather than through a Python object, resolving a Coverity out-of-bounds (OVERRUN) false positive [gh-364](https://github.com/IntelPython/mkl_fft/pull/364) * Silenced a Coverity `UNUSED_VALUE` finding in `__create_descriptor_1d` by marking the `DftiFreeDescriptor` status (used only by a debug-only `assert`) as intentionally unused [gh-365](https://github.com/IntelPython/mkl_fft/pull/365) +* Fixed possible memory leaks when `PyMem_Malloc` fails, and raise `MemoryError` [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) +* Fixed possible memory leaks when multi-iterator constructors fail [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) +* Fixed N-D transforms returning a success status when a scratch allocation fails [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) ## [2.3.2] - 2026-08-04 From 98a7a6c6af150b7981ea77b8aed4548ac8303e2b Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 1 Sep 2026 19:03:24 -0700 Subject: [PATCH 5/8] fix redundant str in f-string --- mkl_fft/_pydfti.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkl_fft/_pydfti.pyx b/mkl_fft/_pydfti.pyx index 04dda953..32b88c19 100644 --- a/mkl_fft/_pydfti.pyx +++ b/mkl_fft/_pydfti.pyx @@ -173,7 +173,7 @@ cdef _raise_dfti_error(int status): cdef char * c_error_msg = mkl_dfti_error(status) cdef bytes py_error_msg = c_error_msg - message = f"Internal error occurred: {str(py_error_msg)}" + message = f"Internal error occurred: {py_error_msg}" if status == DFTI_MEMORY_ERROR: raise MemoryError(message) raise ValueError(message) From b6dbf8382c87cb79c4f66636784d065129c06ca7 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Tue, 1 Sep 2026 19:03:58 -0700 Subject: [PATCH 6/8] fix typos in changelog --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5224fb12..1f148340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed -* Multi-iterator constructors now return a status corresponding to allocation success or failure, and raise `MemoryError` instead of `ValueError` [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) -* `_direct_fftnd` now also checks the status returned by the backend instead of discarding it [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) +* Multi-iterator constructors now return a status corresponding to allocation success or failure, and raise `MemoryError` instead of `ValueError` [gh-373](https://github.com/IntelPython/mkl_fft/pull/373) +* `_direct_fftnd` now also checks the status returned by the backend instead of discarding it [gh-373](https://github.com/IntelPython/mkl_fft/pull/373) ### Fixed * Declared `f_ndim` as a C `int` in `_allocate_result` so the buffer size is computed in C rather than through a Python object, resolving a Coverity out-of-bounds (OVERRUN) false positive [gh-364](https://github.com/IntelPython/mkl_fft/pull/364) * Silenced a Coverity `UNUSED_VALUE` finding in `__create_descriptor_1d` by marking the `DftiFreeDescriptor` status (used only by a debug-only `assert`) as intentionally unused [gh-365](https://github.com/IntelPython/mkl_fft/pull/365) -* Fixed possible memory leaks when `PyMem_Malloc` fails, and raise `MemoryError` [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) -* Fixed possible memory leaks when multi-iterator constructors fail [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) -* Fixed N-D transforms returning a success status when a scratch allocation fails [gh-373].(https://github.com/IntelPython/mkl_fft/pull/373) +* Fixed possible memory leaks when `PyMem_Malloc` fails, and raise `MemoryError` [gh-373](https://github.com/IntelPython/mkl_fft/pull/373) +* Fixed possible memory leaks when multi-iterator constructors fail [gh-373](https://github.com/IntelPython/mkl_fft/pull/373) +* Fixed N-D transforms returning a success status when a scratch allocation fails [gh-373](https://github.com/IntelPython/mkl_fft/pull/373) ## [2.3.2] - 2026-08-04 From 00cf01a06f9b28fdb9604e7ebbc07aa1e8c780da Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 2 Sep 2026 07:19:39 -0700 Subject: [PATCH 7/8] use finally block instead of bare except --- mkl_fft/_pydfti.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mkl_fft/_pydfti.pyx b/mkl_fft/_pydfti.pyx index 32b88c19..fe4a423f 100644 --- a/mkl_fft/_pydfti.pyx +++ b/mkl_fft/_pydfti.pyx @@ -84,9 +84,9 @@ def _tls_dfti_cache_capsule(): capsule = cpython.pycapsule.PyCapsule_New( _cache_struct, capsule_name, &_capsule_destructor ) - except: - PyMem_Free(_cache_struct) - raise + finally: + if capsule is None: + PyMem_Free(_cache_struct) # only publish to TLS once the capsule owns the allocation _tls.capsule = capsule if (not cpython.pycapsule.PyCapsule_IsValid(capsule, capsule_name)): From 80739ae39118baf5a309763416e4534ffbd023a5 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 2 Sep 2026 07:31:02 -0700 Subject: [PATCH 8/8] decode DFTI error message --- mkl_fft/_pydfti.pyx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mkl_fft/_pydfti.pyx b/mkl_fft/_pydfti.pyx index fe4a423f..36e152fb 100644 --- a/mkl_fft/_pydfti.pyx +++ b/mkl_fft/_pydfti.pyx @@ -170,10 +170,9 @@ cdef extern from "src/mklfft.h": cdef _raise_dfti_error(int status): """Raise the exception described by a non-zero MKL DFTI status""" - cdef char * c_error_msg = mkl_dfti_error(status) - cdef bytes py_error_msg = c_error_msg + cdef bytes error_msg = mkl_dfti_error(status) - message = f"Internal error occurred: {py_error_msg}" + message = f"Internal error occurred: {error_msg.decode()}" if status == DFTI_MEMORY_ERROR: raise MemoryError(message) raise ValueError(message)