Skip to content

Commit 55de424

Browse files
WarrenWeckessertylerjereddy
authored andcommitted
BUG: special: Fix unchecked memory allocations in specfun.h (scipy#22080)
* WIP: Fix unchecked memory allocation in aswfa() Summary of the changes: * Add another SF error code, "SF_ERROR_NOMEM", that is used by ufuncs that require internally allocated memory. Just like the other possible errors, how this error is handled can be queried and set using `geterr()` and `seterr()`. Unlike the other error conditions, the default behavior is to raise an exception if a memory allocation fails. * Modify the function `aswfa()` in `specfun.h` to return a status code that indicates when a mempory allocation failed. * Modify the functions `prolate_aswfa_nocv()`, `oblate_aswfa_nocv()`, `prolate_aswfa()` and `oblate_aswfa()` in `sphd_wave.h` to check the return value of `aswfa()`, and set the error state and return values appropriately if `aswfa()` returns `NoMemory`. * MAINT: Rename 'nomem' -> 'memory'. * Fix all uses of unchecked allocations in specfun.h * Fix aswfa() in specfunc.h * Fix refguide check. * Add comment about the return value when nonconvergence is detected in rmn2l. * Change (c/m)alloc calls to use 'new' wrapped in a unique_ptr. * Fix a few more names passed to set_error() * Set outputs to nan in mtu12 when alloc fails Also update docstring-like comments of some C++ functions.
1 parent aea469d commit 55de424

File tree

15 files changed

+609
-222
lines changed

15 files changed

+609
-222
lines changed

scipy/special/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
2424
Errors are handled by returning NaNs or other appropriate values.
2525
Some of the special function routines can emit warnings or raise
26-
exceptions when an error occurs. By default this is disabled; to
27-
query and control the current error handling state the following
26+
exceptions when an error occurs. By default this is disabled, except
27+
for memory allocation errors, which result in an exception being raised.
28+
To query and control the current error handling state the following
2829
functions are provided.
2930
3031
.. autosummary::

scipy/special/_ellip_harm.pxd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ cdef inline double* lame_coefficients(double h2, double k2, int n, int p,
9898
+ (sizeof(CBLAS_INT)*(2*size + liwork)))
9999
bufferp[0] = buffer
100100
if not buffer:
101-
sf_error.error("ellip_harm", sf_error.NO_RESULT, "failed to allocate memory")
101+
sf_error.error("ellip_harm", sf_error.MEMORY, "failed to allocate memory")
102102
return NULL
103103

104104
cdef double *g = <double *>buffer
@@ -166,7 +166,7 @@ cdef inline double* lame_coefficients(double h2, double k2, int n, int p,
166166
&size, isuppz, work, &lwork, iwork, &liwork, &info)
167167

168168
if info != 0:
169-
sf_error.error("ellip_harm", sf_error.NO_RESULT, "failed to allocate memory")
169+
sf_error.error("ellip_harm", sf_error.MEMORY, "failed to allocate memory")
170170
return NULL
171171

172172
for i in range(0, size):

scipy/special/_specfun.pyx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,29 @@ cdef extern from "xsf/par_cyl.h" nogil:
1616
void specfun_pbdv 'xsf::detail::pbdv'(double x, double v, double *dv, double *dp, double *pdf, double *pdd)
1717
void specfun_pbvv 'xsf::detail::pbvv'(double x, double v, double *vv, double *vp, double *pvf, double *pvd)
1818

19+
cdef extern from "xsf/specfun/specfun.h" namespace "xsf::specfun":
20+
21+
cpdef enum class Status:
22+
OK = 0
23+
NoMemory
24+
Other
25+
1926
cdef extern from "xsf/specfun/specfun.h" nogil:
2027
void specfun_bernob 'xsf::specfun::bernob'(int n, double *bn)
2128
void specfun_cerzo 'xsf::specfun::cerzo'(int nt, ccomplex[double] *zo)
2229
void specfun_cpbdn 'xsf::specfun::cpbdn'(int n, ccomplex[double] z, ccomplex[double] *cpb, ccomplex[double] *cpd)
2330
void specfun_cyzo 'xsf::specfun::cyzo'(int nt, int kf, int kc, ccomplex[double] *zo, ccomplex[double] *zv)
2431
void specfun_eulerb 'xsf::specfun::eulerb'(int n, double *en)
2532
void specfun_fcoef 'xsf::specfun::fcoef'(int kd, int m, double q, double a, double *fc)
26-
void specfun_jdzo 'xsf::specfun::jdzo'(int nt, double *zo, int *n, int *m, int *p)
33+
Status specfun_jdzo 'xsf::specfun::jdzo'(int nt, double *zo, int *n, int *m, int *p)
2734
void specfun_jyzo 'xsf::specfun::jyzo'(int n, int nt, double *rj0, double *rj1, double *ry0, double *ry1)
2835
void specfun_lamn 'xsf::specfun::lamn'(int n, double x, int *nm, double *bl, double *dl)
2936
void specfun_lamv 'xsf::specfun::lamv'(double v, double x, double *vm, double *vl, double *dl)
3037
void specfun_lqnb 'xsf::specfun::lqnb'(int n, double x, double* qn, double* qd)
3138
void specfun_pbdv 'xsf::specfun::pbdv'(double x, double v, double *dv, double *dp, double *pdf, double *pdd)
3239
void specfun_pbvv 'xsf::specfun::pbvv'(double x, double v, double *vv, double *vp, double *pvf, double *pvd)
33-
void specfun_sdmn 'xsf::specfun::sdmn'(int m, int n, double c, double cv, double kd, double *df)
34-
void specfun_segv 'xsf::specfun::segv'(int m, int n, double c, int kd, double *cv, double *eg)
40+
Status specfun_sdmn 'xsf::specfun::sdmn'(int m, int n, double c, double cv, double kd, double *df)
41+
Status specfun_segv 'xsf::specfun::segv'(int m, int n, double c, int kd, double *cv, double *eg)
3542

3643

3744
def airyzo(int nt, int kf):
@@ -224,7 +231,8 @@ def jdzo(int nt):
224231
mm = <int*>cnp.PyArray_DATA(m)
225232
pp = <int*>cnp.PyArray_DATA(p)
226233

227-
specfun_jdzo(nt, zzo, nn, mm, pp)
234+
if (specfun_jdzo(nt, zzo, nn, mm, pp) == Status.NoMemory):
235+
raise MemoryError('jnjnp_zeros: failed to allocate working memory in jdzo.')
228236

229237
return n, m, p, zo
230238

@@ -353,7 +361,8 @@ def sdmn(int m, int n, double c, double cv, int kd):
353361

354362
df = cnp.PyArray_ZEROS(1, dims, cnp.NPY_FLOAT64, 0)
355363
ddf = <cnp.float64_t *>cnp.PyArray_DATA(df)
356-
specfun_sdmn(m, n, c, cv, kd, ddf)
364+
if specfun_sdmn(m, n, c, cv, kd, ddf) == Status.NoMemory:
365+
raise MemoryError('sdmn: failed to allocate working memory.')
357366
return df
358367

359368

@@ -369,5 +378,11 @@ def segv(int m, int n, double c, int kd):
369378

370379
eg = cnp.PyArray_ZEROS(1, dims, cnp.NPY_FLOAT64, 0)
371380
eeg = <cnp.float64_t *>cnp.PyArray_DATA(eg)
372-
specfun_segv(m, n, c, kd, &cv, eeg)
381+
if specfun_segv(m, n, c, kd, &cv, eeg) == Status.NoMemory:
382+
# Note: segv is a private function that is called by either pro_cv_seq
383+
# or obl_cv_seq. We make the error message useful by including the
384+
# approriate name in it.
385+
caller = 'pro_cv_seq' if kd == 1 else 'obl_cv_seq'
386+
msg = f'{caller}: failed to allocate working memory in segv.'
387+
raise MemoryError(msg)
373388
return cv, eg

scipy/special/_special_ufuncs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ PyMODINIT_FUNC PyInit__special_ufuncs() {
802802

803803
PyObject *pro_cv = xsf::numpy::ufunc(
804804
{static_cast<xsf::numpy::fff_f>(xsf::prolate_segv), static_cast<xsf::numpy::ddd_d>(xsf::prolate_segv)},
805-
"obl_cv", pro_cv_doc);
805+
"pro_cv", pro_cv_doc);
806806
PyModule_AddObjectRef(_special_ufuncs, "pro_cv", pro_cv);
807807

808808
PyObject *pro_rad1 = xsf::numpy::ufunc({static_cast<xsf::numpy::ffff_ff>(xsf::prolate_radial1_nocv),

scipy/special/_ufuncs_extra_code.pxi

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ _sf_error_code_map = {
1212
'no_result': 6,
1313
'domain': 7,
1414
'arg': 8,
15-
'other': 9
15+
'other': 9,
16+
'memory': 10
1617
}
1718

1819
_sf_error_action_map = {
@@ -54,11 +55,12 @@ def geterr():
5455
5556
>>> import scipy.special as sc
5657
>>> for key, value in sorted(sc.geterr().items()):
57-
... print("{}: {}".format(key, value))
58+
... print(f'{key}: {value}')
5859
...
5960
arg: ignore
6061
domain: ignore
6162
loss: ignore
63+
memory: raise
6264
no_result: ignore
6365
other: ignore
6466
overflow: ignore

scipy/special/ellint_carlson_cpp_lite/ellint_common.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ namespace ellint_carlson
9191
bad_args,
9292
bad_rerr,
9393
other,
94+
memory,
9495
unused
9596
};
9697

scipy/special/sf_error.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const char *sf_error_messages[] = {
2020
"domain error",
2121
"invalid input argument",
2222
"other error",
23+
"memory allocation failed",
2324
NULL
2425
};
2526

@@ -35,7 +36,7 @@ void sf_error_v(const char *func_name, sf_error_t code, const char *fmt, va_list
3536
static PyObject *py_SpecialFunctionWarning = NULL;
3637
sf_action_t action;
3738

38-
if ((int) code < 0 || (int) code >= 10) {
39+
if ((int) code < 0 || (int) code >= SF_ERROR__LAST) {
3940
code = SF_ERROR_OTHER;
4041
}
4142
action = scipy_sf_error_get_action(code);

scipy/special/sf_error.pxd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ cdef extern from "sf_error.h":
1212
DOMAIN "SF_ERROR_DOMAIN"
1313
ARG "SF_ERROR_ARG"
1414
OTHER "SF_ERROR_OTHER"
15+
MEMORY "SF_ERROR_MEMORY"
16+
LAST "SF_ERROR__LAST"
1517

1618
ctypedef enum sf_action_t:
1719
IGNORE "SF_ERROR_IGNORE"
@@ -32,7 +34,7 @@ cdef inline int _sf_error_test_function(int code) noexcept nogil:
3234
"""
3335
cdef sf_error_t sf_error_code
3436

35-
if code < 0 or code >= 10:
37+
if code < 0 or code >= LAST:
3638
sf_error_code = OTHER
3739
else:
3840
sf_error_code = <sf_error_t>code

scipy/special/sf_error_state.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ static volatile sf_action_t sf_error_actions[] = {
1515
SF_ERROR_IGNORE, /* SF_ERROR_DOMAIN */
1616
SF_ERROR_IGNORE, /* SF_ERROR_ARG */
1717
SF_ERROR_IGNORE, /* SF_ERROR_OTHER */
18+
SF_ERROR_RAISE, /* SF_ERROR_MEMORY */
1819
SF_ERROR_IGNORE /* SF_ERROR__LAST */
1920
};
2021

scipy/special/tests/test_sf_error.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
'no_result': 6,
2020
'domain': 7,
2121
'arg': 8,
22-
'other': 9
22+
'other': 9,
23+
'memory': 10,
2324
}
2425

2526
_sf_error_actions = [

0 commit comments

Comments
 (0)