742 lines
25 KiB
Diff
742 lines
25 KiB
Diff
From 0d941ffeac1ac04a6d76890bdf114d898267d488 Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Sat, 20 Dec 2025 00:39:36 +0100
|
|
Subject: [PATCH 01/15] BUG: ndimage: fix potential double-free in
|
|
NI_InitFilterOffsets
|
|
|
|
Null out offset pointers after freeing them in the error path of
|
|
NI_InitFilterOffsets. Without this, callers that also free these
|
|
pointers in their cleanup code could trigger a double-free if the
|
|
function fails after allocation.
|
|
|
|
Found by Coverity static analysis.
|
|
---
|
|
scipy/ndimage/src/ni_support.c | 2 ++
|
|
1 file changed, 2 insertions(+)
|
|
|
|
diff --git a/scipy/ndimage/src/ni_support.c b/scipy/ndimage/src/ni_support.c
|
|
index a6e3ff0..934ac74 100644
|
|
--- a/scipy/ndimage/src/ni_support.c
|
|
+++ b/scipy/ndimage/src/ni_support.c
|
|
@@ -734,8 +734,10 @@ int NI_InitFilterOffsets(PyArrayObject *array, npy_bool *footprint,
|
|
exit:
|
|
if (PyErr_Occurred()) {
|
|
free(*offsets);
|
|
+ *offsets = NULL;
|
|
if (coordinate_offsets) {
|
|
free(*coordinate_offsets);
|
|
+ *coordinate_offsets = NULL;
|
|
}
|
|
return 0;
|
|
} else {
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 51ec935d77be786ce3b5e8b95c5fa23a4dd3dbe7 Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Sat, 20 Dec 2025 01:08:47 +0100
|
|
Subject: [PATCH 02/15] BUG: signal/_firfilter.cc: fix out-of-bounds read in
|
|
pylab_convolve_2d
|
|
|
|
Move the type_num bounds check before using it as an array index.
|
|
Previously, OneMultAdd[type_num] was accessed before validating that
|
|
type_num was within bounds, which could cause an out-of-bounds read
|
|
when passed an unsupported array dtype.
|
|
|
|
Also fix off-by-one error via changing > MAXTYPES to >= MAXTYPES since
|
|
the arrays have MAXTYPES-1 elements.
|
|
|
|
Found by Coverity static analysis scan.
|
|
---
|
|
scipy/signal/_firfilter.cc | 3 ++-
|
|
1 file changed, 2 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/scipy/signal/_firfilter.cc b/scipy/signal/_firfilter.cc
|
|
index 9636961..b1f3f19 100644
|
|
--- a/scipy/signal/_firfilter.cc
|
|
+++ b/scipy/signal/_firfilter.cc
|
|
@@ -180,10 +180,11 @@ int pylab_convolve_2d (char *in, /* Input data Ns[0] x Ns[1] */
|
|
const int type_num = (flag & TYPE_MASK) >> TYPE_SHIFT;
|
|
/*type_size*/
|
|
|
|
+ if (type_num < 0 || type_num >= MAXTYPES) return -4; /* Invalid type */
|
|
+
|
|
OneMultAddFunction *mult_and_add = OneMultAdd[type_num];
|
|
if (mult_and_add == NULL) return -5; /* Not available for this type */
|
|
|
|
- if (type_num < 0 || type_num > MAXTYPES) return -4; /* Invalid type */
|
|
const int type_size = elsizes[type_num];
|
|
|
|
int64_t Os[2];
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 94b1d8ee75e0cbf37bd970b70ba1e120d4d47d19 Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Sat, 20 Dec 2025 01:20:43 +0100
|
|
Subject: [PATCH 03/15] BUG: optimize/__lbfgsb.c: fix pointer arithmetic bug in
|
|
cauchy function
|
|
|
|
Change nseg += 1 to *nseg += 1 to increment the segment counter
|
|
value rather than performing pointer arithmetic on the pointer itself.
|
|
|
|
Found by Coverity static analysis.
|
|
---
|
|
scipy/optimize/__lbfgsb.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/scipy/optimize/__lbfgsb.c b/scipy/optimize/__lbfgsb.c
|
|
index 306f6d6..4a1dc98 100644
|
|
--- a/scipy/optimize/__lbfgsb.c
|
|
+++ b/scipy/optimize/__lbfgsb.c
|
|
@@ -1694,7 +1694,7 @@ cauchy(int n, double* x, double* l, double* u,
|
|
}
|
|
|
|
// Update the derivative information.
|
|
- nseg += 1;
|
|
+ *nseg += 1;
|
|
dibp2 = pow(dibp, 2.0);
|
|
|
|
// Update f1 and f2
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 7e65e5528db55cffb8814ecdb51e9573060695e2 Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Sat, 20 Dec 2025 03:43:12 +0100
|
|
Subject: [PATCH 04/15] BUG: optimize/tnc/tnc.c: fix uninitialized xoffset when
|
|
scale is provided
|
|
|
|
When calling tnc() with a non-NULL scale array but NULL offset array,
|
|
and scale[i] != 0.0, the xoffset[i] element was never initialized.
|
|
|
|
The uninitialized value was then read in tnc_minimize() -> scalex().
|
|
|
|
Found via Coverity scan analysis.
|
|
---
|
|
scipy/optimize/tnc/tnc.c | 2 ++
|
|
1 file changed, 2 insertions(+)
|
|
|
|
diff --git a/scipy/optimize/tnc/tnc.c b/scipy/optimize/tnc/tnc.c
|
|
index 0b06d0e..d902dbf 100644
|
|
--- a/scipy/optimize/tnc/tnc.c
|
|
+++ b/scipy/optimize/tnc/tnc.c
|
|
@@ -353,6 +353,8 @@ int tnc(int n, double x[], double *f, double g[], tnc_function * function,
|
|
xscale[i] = fabs(scale[i]);
|
|
if (xscale[i] == 0.0) {
|
|
xoffset[i] = low[i] = up[i] = x[i];
|
|
+ } else {
|
|
+ xoffset[i] = x[i];
|
|
}
|
|
} else if (low[i] != -HUGE_VAL && up[i] != HUGE_VAL) {
|
|
xscale[i] = up[i] - low[i];
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From c7da8ee0aa7a24fe937467de4b845b0c222519ba Mon Sep 17 00:00:00 2001
|
|
From: SoheilStar <75124326+soheil-star01@users.noreply.github.com>
|
|
Date: Sat, 20 Dec 2025 23:21:11 +0200
|
|
Subject: [PATCH 05/15] BUG: optimize: Remove redundant conditional in
|
|
_shgo.sampling_custom
|
|
|
|
Both if and else branches executed the same code. Simplifies to a
|
|
single assignment as confirmed by the original author.
|
|
---
|
|
scipy/optimize/_shgo.py | 5 +----
|
|
1 file changed, 1 insertion(+), 4 deletions(-)
|
|
|
|
diff --git a/scipy/optimize/_shgo.py b/scipy/optimize/_shgo.py
|
|
index b98a4b5..5fe0899 100644
|
|
--- a/scipy/optimize/_shgo.py
|
|
+++ b/scipy/optimize/_shgo.py
|
|
@@ -1458,10 +1458,7 @@ class SHGO:
|
|
"""
|
|
# Generate sampling points.
|
|
# Generate uniform sample points in [0, 1]^m \subset R^m
|
|
- if self.n_sampled == 0:
|
|
- self.C = self.sampling_function(n, dim)
|
|
- else:
|
|
- self.C = self.sampling_function(n, dim)
|
|
+ self.C = self.sampling_function(n, dim)
|
|
# Distribute over bounds
|
|
for i in range(len(self.bounds)):
|
|
self.C[:, i] = (self.C[:, i] *
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From e07ace4aa764caef5c5abe2b8aa1982bb0348164 Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Mon, 22 Dec 2025 01:40:36 +0100
|
|
Subject: [PATCH 06/15] BUG: sparse.linalg: Fix copy-paste error in
|
|
get_OPinv_matvec
|
|
|
|
In the type check condition for matrix M, is_pydata_spmatrix() was
|
|
incorrectly called with A instead of M.
|
|
---
|
|
scipy/sparse/linalg/_eigen/arpack/arpack.py | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/scipy/sparse/linalg/_eigen/arpack/arpack.py b/scipy/sparse/linalg/_eigen/arpack/arpack.py
|
|
index f678dea..0c70bf9 100644
|
|
--- a/scipy/sparse/linalg/_eigen/arpack/arpack.py
|
|
+++ b/scipy/sparse/linalg/_eigen/arpack/arpack.py
|
|
@@ -1084,7 +1084,7 @@ def get_OPinv_matvec(A, M, sigma, hermitian=False, tol=0):
|
|
M, sigma, tol=tol).matvec
|
|
else:
|
|
if ((not isdense(A) and not issparse(A) and not is_pydata_spmatrix(A)) or
|
|
- (not isdense(M) and not issparse(M) and not is_pydata_spmatrix(A))):
|
|
+ (not isdense(M) and not issparse(M) and not is_pydata_spmatrix(M))):
|
|
return IterOpInv(aslinearoperator(A),
|
|
aslinearoperator(M),
|
|
sigma, tol=tol).matvec
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From c3d3b109da83c889a12cb1735b31ed5466b4066e Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Mon, 22 Dec 2025 02:23:55 +0100
|
|
Subject: [PATCH 07/15] BUG: optimize/_direct: Fix memory leaks in
|
|
direct_direct_()
|
|
|
|
Two early return paths bypassed the cleanup section, leaking all
|
|
allocated memory (13 malloc'd buffers).
|
|
|
|
After direct_dirinit_() failure changed return NULL to goto
|
|
cleanup. The ret variable is already NULL in this case.
|
|
|
|
After Python callback failure set ret = NULL and goto cleanup
|
|
to ensure proper memory deallocation.
|
|
|
|
Found by Coverity static analysis.
|
|
---
|
|
scipy/optimize/_direct/DIRect.c | 5 +++--
|
|
1 file changed, 3 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/scipy/optimize/_direct/DIRect.c b/scipy/optimize/_direct/DIRect.c
|
|
index 05249fa..9ec6a42 100644
|
|
--- a/scipy/optimize/_direct/DIRect.c
|
|
+++ b/scipy/optimize/_direct/DIRect.c
|
|
@@ -455,7 +455,7 @@
|
|
fmax, &ifeasiblef, &iinfesiblef, ierror, args, jones,
|
|
force_stop);
|
|
if (!ret) {
|
|
- return NULL;
|
|
+ goto cleanup;
|
|
}
|
|
/* +-----------------------------------------------------------------------+ */
|
|
/* | Added error checking. | */
|
|
@@ -773,7 +773,8 @@
|
|
PyObject* callback_py = PyObject_CallObject(callback, arg_tuple);
|
|
Py_DECREF(arg_tuple);
|
|
if( !callback_py ) {
|
|
- return NULL;
|
|
+ ret = NULL;
|
|
+ goto cleanup;
|
|
}
|
|
}
|
|
/* L10: */
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 6f4515431ceb479e788255e5b0041670a4d28a6d Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Tue, 13 Jan 2026 23:07:38 +0100
|
|
Subject: [PATCH 08/15] BUG: fix freeing of uninitialized memory in error paths
|
|
in ndimage
|
|
|
|
Initialize pointer arrays immediately after allocation and error check,
|
|
before any jumps to cleanup code. Previously, if an allocation failed
|
|
after data_offsets, offsets, or splvals were allocated but before
|
|
their elements were initialized, the cleanup code would attempt to free
|
|
uninitialized pointers.
|
|
|
|
Found via Coverity scan analysis
|
|
---
|
|
scipy/ndimage/src/ni_interpolation.c | 22 ++++++++++++++--------
|
|
1 file changed, 14 insertions(+), 8 deletions(-)
|
|
|
|
diff --git a/scipy/ndimage/src/ni_interpolation.c b/scipy/ndimage/src/ni_interpolation.c
|
|
index f0f86a7..5a11419 100644
|
|
--- a/scipy/ndimage/src/ni_interpolation.c
|
|
+++ b/scipy/ndimage/src/ni_interpolation.c
|
|
@@ -301,6 +301,8 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*,
|
|
PyErr_NoMemory();
|
|
goto exit;
|
|
}
|
|
+ for(jj = 0; jj < irank; jj++)
|
|
+ data_offsets[jj] = NULL;
|
|
|
|
if (mode == NI_EXTEND_GRID_CONSTANT) {
|
|
// boolean indicating if the current point in the filter footprint is
|
|
@@ -323,8 +325,6 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*,
|
|
}
|
|
}
|
|
|
|
- for(jj = 0; jj < irank; jj++)
|
|
- data_offsets[jj] = NULL;
|
|
for(jj = 0; jj < irank; jj++) {
|
|
data_offsets[jj] = malloc((order + 1) * sizeof(npy_intp));
|
|
if (NPY_UNLIKELY(!data_offsets[jj])) {
|
|
@@ -717,19 +717,25 @@ int NI_ZoomShift(PyArrayObject *input, PyArrayObject* zoom_ar,
|
|
}
|
|
/* store offsets, along each axis: */
|
|
offsets = malloc(rank * sizeof(npy_intp*));
|
|
+ if (NPY_UNLIKELY(!offsets)) {
|
|
+ NPY_END_THREADS;
|
|
+ PyErr_NoMemory();
|
|
+ goto exit;
|
|
+ }
|
|
+ for(jj = 0; jj < rank; jj++)
|
|
+ offsets[jj] = NULL;
|
|
+
|
|
/* store spline coefficients, along each axis: */
|
|
splvals = malloc(rank * sizeof(double**));
|
|
- /* store offsets at all edges: */
|
|
-
|
|
- if (NPY_UNLIKELY(!offsets || !splvals)) {
|
|
+ if (NPY_UNLIKELY(!splvals)) {
|
|
NPY_END_THREADS;
|
|
PyErr_NoMemory();
|
|
goto exit;
|
|
}
|
|
- for(jj = 0; jj < rank; jj++) {
|
|
- offsets[jj] = NULL;
|
|
+ for(jj = 0; jj < rank; jj++)
|
|
splvals[jj] = NULL;
|
|
- }
|
|
+
|
|
+ /* store offsets at all edges: */
|
|
for(jj = 0; jj < rank; jj++) {
|
|
offsets[jj] = malloc(odimensions[jj] * sizeof(npy_intp));
|
|
splvals[jj] = malloc(odimensions[jj] * sizeof(double*));
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 329c6a1df8fbe16c8d59b4a8b58abe29ec03284e Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Wed, 14 Jan 2026 04:14:29 +0100
|
|
Subject: [PATCH 09/15] BUG: fix uninitialized variables in odr
|
|
|
|
The if-else clauses handling pwe and pwd arrays had no terminal
|
|
else clause. If an object passed validation but didn't match any
|
|
handling branch, ldwe,ld2we,ldwd and ld2wd would be used uninitialized.
|
|
|
|
Add else clauses to ensure all code paths either initialize these
|
|
variables or exit with an error.
|
|
|
|
Found via Coverity static analysis
|
|
---
|
|
scipy/odr/__odrpack.c | 8 ++++++++
|
|
1 file changed, 8 insertions(+)
|
|
|
|
diff --git a/scipy/odr/__odrpack.c b/scipy/odr/__odrpack.c
|
|
index f86a65f..2027166 100644
|
|
--- a/scipy/odr/__odrpack.c
|
|
+++ b/scipy/odr/__odrpack.c
|
|
@@ -780,6 +780,10 @@ PyObject *odr(PyObject * self, PyObject * args, PyObject * kwds)
|
|
PYERR(PyExc_ValueError, "could not convert we to a suitable array");
|
|
}
|
|
} /* we */
|
|
+ else
|
|
+ {
|
|
+ PYERR(PyExc_ValueError, "could not convert we to a suitable array");
|
|
+ }
|
|
|
|
if (pwd == NULL)
|
|
{
|
|
@@ -871,6 +875,10 @@ PyObject *odr(PyObject * self, PyObject * args, PyObject * kwds)
|
|
}
|
|
|
|
} /* wd */
|
|
+ else
|
|
+ {
|
|
+ PYERR(PyExc_ValueError, "could not convert wd to a suitable array");
|
|
+ }
|
|
|
|
|
|
if (pifixb == NULL)
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 9e52501a67a6e190bc81dbd91c4a14866639663c Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Wed, 14 Jan 2026 05:07:07 +0100
|
|
Subject: [PATCH 10/15] BUG: fix uninitialized variable in ILU complex copy at
|
|
sparse/SuperLU
|
|
|
|
The SMILU_3 case in the second loop of ilu_ccopy_to_ucol.c and
|
|
ilu_zcopy_to_ucol.c used tmp which could be uninitialized. Compute
|
|
the absolute value directly instead.
|
|
|
|
Found via Coverity static analysis
|
|
---
|
|
scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c | 2 +-
|
|
scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c | 2 +-
|
|
2 files changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c
|
|
index 9817fe3..93a3c1b 100644
|
|
--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c
|
|
+++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_ccopy_to_ucol.c
|
|
@@ -190,7 +190,7 @@ ilu_ccopy_to_ucol(
|
|
c_add(sum, sum, &ucol[i]);
|
|
break;
|
|
case SMILU_3:
|
|
- sum->r += tmp;
|
|
+ sum->r += c_abs1(&ucol[i]);
|
|
break;
|
|
case SILU:
|
|
default:
|
|
diff --git a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c
|
|
index bb444d7..063d685 100644
|
|
--- a/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c
|
|
+++ b/scipy/sparse/linalg/_dsolve/SuperLU/SRC/ilu_zcopy_to_ucol.c
|
|
@@ -190,7 +190,7 @@ ilu_zcopy_to_ucol(
|
|
z_add(sum, sum, &ucol[i]);
|
|
break;
|
|
case SMILU_3:
|
|
- sum->r += tmp;
|
|
+ sum->r += z_abs1(&ucol[i]);
|
|
break;
|
|
case SILU:
|
|
default:
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 09b460c7de6e9c8ddabd52d4a49b7fbba4003d4a Mon Sep 17 00:00:00 2001
|
|
From: ilayn <ilhanpolat@gmail.com>
|
|
Date: Thu, 15 Jan 2026 18:10:05 +0100
|
|
Subject: [PATCH 11/15] MAINT:optimize: Fix memleaks in DIRECT solver and
|
|
ext.mod.
|
|
|
|
---
|
|
scipy/optimize/_direct/DIRect.c | 6 +++++-
|
|
scipy/optimize/_direct/DIRserial.c | 1 +
|
|
scipy/optimize/_direct/DIRsubrout.c | 21 ++++++++++++++++++---
|
|
scipy/optimize/_directmodule.c | 13 ++++++++++---
|
|
4 files changed, 34 insertions(+), 7 deletions(-)
|
|
|
|
diff --git a/scipy/optimize/_direct/DIRect.c b/scipy/optimize/_direct/DIRect.c
|
|
index 9ec6a42..85bdaed 100644
|
|
--- a/scipy/optimize/_direct/DIRect.c
|
|
+++ b/scipy/optimize/_direct/DIRect.c
|
|
@@ -595,11 +595,15 @@
|
|
/* +-----------------------------------------------------------------------+ */
|
|
/* | JG 01/22/01 Added variable to keep track of the maximum value found. | */
|
|
/* +-----------------------------------------------------------------------+ */
|
|
- direct_dirsamplef_(c__, arrayi, &delta, &help, &start, length,
|
|
+ Py_XDECREF(ret); /* DECREF previous return value before getting new one */
|
|
+ ret = direct_dirsamplef_(c__, arrayi, &delta, &help, &start, length,
|
|
logfile, f, &ifree, &maxi, point, fcn, &x[
|
|
1], x_seq, &l[1], minf, &minpos, &u[1], n, &MAXFUNC, &
|
|
MAXDEEP, &oops, &fmax, &ifeasiblef, &iinfesiblef,
|
|
args, force_stop);
|
|
+ if (!ret) {
|
|
+ goto cleanup;
|
|
+ }
|
|
if (force_stop && *force_stop) {
|
|
*ierror = -102;
|
|
*numiter = t;
|
|
diff --git a/scipy/optimize/_direct/DIRserial.c b/scipy/optimize/_direct/DIRserial.c
|
|
index 10a4870..c2c1688 100644
|
|
--- a/scipy/optimize/_direct/DIRserial.c
|
|
+++ b/scipy/optimize/_direct/DIRserial.c
|
|
@@ -87,6 +87,7 @@
|
|
if (force_stop && *force_stop) /* skip eval after forced stop */
|
|
f[(pos << 1) + 1] = *fmax;
|
|
else {
|
|
+ Py_XDECREF(ret); /* DECREF previous iteration's return value */
|
|
ret = direct_dirinfcn_(fcn, &x[1], x_seq, &l[1], &u[1], n, &f[(pos << 1) + 1],
|
|
&kret, args);
|
|
if (!ret) {
|
|
diff --git a/scipy/optimize/_direct/DIRsubrout.c b/scipy/optimize/_direct/DIRsubrout.c
|
|
index 35b10c7..718802f 100644
|
|
--- a/scipy/optimize/_direct/DIRsubrout.c
|
|
+++ b/scipy/optimize/_direct/DIRsubrout.c
|
|
@@ -7,8 +7,19 @@
|
|
#include <math.h>
|
|
// #include "numpy/ndarrayobject.h"
|
|
|
|
-/* Table of constant values */
|
|
-
|
|
+/*
|
|
+ * SCIPY NOTE (2026-01-15):
|
|
+ * The following are read-only variables after initialization.
|
|
+ *
|
|
+ * They exist because f2c passes all arguments by reference (Fortran convention),
|
|
+ * so literal constants need addresses. The code is still thread-safe since they
|
|
+ * are never modified.
|
|
+ *
|
|
+ * Note: Cannot mark as 'const' due to f2c function signatures expecting non-const
|
|
+ * pointers which makes the surgery more involved. Instead an f2c-free rewrite
|
|
+ * is better for long-term maintenance.
|
|
+ *
|
|
+ */
|
|
static integer c__1 = 1;
|
|
static integer c__32 = 32;
|
|
static integer c__0 = 0;
|
|
@@ -1295,11 +1306,15 @@ L50:
|
|
/* | JG 01/22/01 Added variable to keep track of the maximum value found. | */
|
|
/* | Added variable to keep track if feasible point was found. | */
|
|
/* +-----------------------------------------------------------------------+ */
|
|
- direct_dirsamplef_(&c__[c_offset], &arrayi[1], &delta, &c__1, &new__, &length[
|
|
+ Py_DECREF(ret); /* DECREF before overwriting with new return value */
|
|
+ ret = direct_dirsamplef_(&c__[c_offset], &arrayi[1], &delta, &c__1, &new__, &length[
|
|
length_offset], logfile, &f[3], free, maxi, &point[
|
|
1], fcn, &x[1], x_seq, &l[1], minf, minpos, &u[1], n, maxfunc,
|
|
maxdeep, &oops, fmax, ifeasiblef, iinfeasible, args,
|
|
force_stop);
|
|
+ if (!ret) {
|
|
+ return NULL;
|
|
+ }
|
|
if (force_stop && *force_stop) {
|
|
*ierror = -102;
|
|
return ret;
|
|
diff --git a/scipy/optimize/_directmodule.c b/scipy/optimize/_directmodule.c
|
|
index d1bfb33..d21734d 100644
|
|
--- a/scipy/optimize/_directmodule.c
|
|
+++ b/scipy/optimize/_directmodule.c
|
|
@@ -36,7 +36,7 @@ direct(PyObject *self, PyObject *args)
|
|
|
|
dimension = PyArray_DIMS((PyArrayObject*)lb)[0];
|
|
x = (double *) malloc(sizeof(double) * (dimension + 1));
|
|
- if (!(x)) {
|
|
+ if (!x) {
|
|
ret_code = DIRECT_OUT_OF_MEMORY;
|
|
}
|
|
PyObject *x_seq = PyList_New(dimension);
|
|
@@ -46,17 +46,24 @@ direct(PyObject *self, PyObject *args)
|
|
force_stop = 0;
|
|
direct_return_info info;
|
|
|
|
- if (!direct_optimize(f, x, x_seq, f_args, dimension, lower_bounds,
|
|
+ PyObject *direct_ret = direct_optimize(f, x, x_seq, f_args, dimension, lower_bounds,
|
|
upper_bounds, &minf, max_feval, max_iter,
|
|
magic_eps, magic_eps_abs, volume_reltol,
|
|
sigma_reltol, &force_stop, fglobal, fglobal_reltol,
|
|
- logfile, algorithm, &info, &ret_code, callback)) {
|
|
+ logfile, algorithm, &info, &ret_code, callback);
|
|
+ if (!direct_ret) {
|
|
+ Py_DECREF(x_seq);
|
|
if (x)
|
|
free(x);
|
|
return NULL;
|
|
}
|
|
+ /* DECREF the return value from direct_optimize - we only needed it for error checking */
|
|
+ Py_DECREF(direct_ret);
|
|
PyObject* ret_py = Py_BuildValue("Odiii", x_seq, minf, (int) ret_code,
|
|
info.numfunc, info.numiter);
|
|
+ /* Py_BuildValue with "O" increments refcount. We need to DECREF our
|
|
+ original reference since the tuple now owns it. */
|
|
+ Py_DECREF(x_seq);
|
|
if (x)
|
|
free(x);
|
|
return ret_py;
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From ba52739d21f8cf3ee52775df23c05605143de74f Mon Sep 17 00:00:00 2001
|
|
From: ilayn <ilhanpolat@gmail.com>
|
|
Date: Thu, 15 Jan 2026 18:19:06 +0100
|
|
Subject: [PATCH 12/15] MAINT:optimize: Enable multi-phase init to DIRECT
|
|
|
|
---
|
|
scipy/optimize/_directmodule.c | 54 +++++++++++++++++++---------------
|
|
1 file changed, 31 insertions(+), 23 deletions(-)
|
|
|
|
diff --git a/scipy/optimize/_directmodule.c b/scipy/optimize/_directmodule.c
|
|
index d21734d..aa3a80b 100644
|
|
--- a/scipy/optimize/_directmodule.c
|
|
+++ b/scipy/optimize/_directmodule.c
|
|
@@ -73,38 +73,46 @@ direct(PyObject *self, PyObject *args)
|
|
* Standard Python module interface
|
|
*/
|
|
|
|
-static PyMethodDef
|
|
-DIRECTMethods[] = {
|
|
+static struct PyMethodDef direct_module_methods[] = {
|
|
{"direct", direct, METH_VARARGS, "DIRECT Optimization Algorithm"},
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
-static struct PyModuleDef moduledef = {
|
|
- PyModuleDef_HEAD_INIT,
|
|
- "_direct",
|
|
- NULL,
|
|
- -1,
|
|
- DIRECTMethods,
|
|
- NULL,
|
|
- NULL,
|
|
- NULL,
|
|
- NULL
|
|
-};
|
|
|
|
-PyMODINIT_FUNC
|
|
-PyInit__direct(void)
|
|
-{
|
|
- PyObject *module;
|
|
+static int module_exec(PyObject *module) {
|
|
+ (void)module; /* unused */
|
|
|
|
- import_array();
|
|
- module = PyModule_Create(&moduledef);
|
|
- if (module == NULL) {
|
|
- return module;
|
|
- }
|
|
+ if (_import_array() < 0) { return -1; }
|
|
|
|
#if Py_GIL_DISABLED
|
|
PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
|
|
#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
|
|
- return module;
|
|
+static struct PyModuleDef_Slot direct_slots[] = {
|
|
+ {Py_mod_exec, module_exec},
|
|
+ {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
|
|
+#if PY_VERSION_HEX >= 0x030d00f0 /* Python 3.13+ */
|
|
+ /* signal that this module supports running without an active GIL */
|
|
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
|
+#endif
|
|
+ {0, NULL},
|
|
+};
|
|
+
|
|
+
|
|
+static struct PyModuleDef moduledef = {
|
|
+ .m_base = PyModuleDef_HEAD_INIT,
|
|
+ .m_name = "_direct",
|
|
+ .m_size = 0,
|
|
+ .m_methods = direct_module_methods,
|
|
+ .m_slots = direct_slots
|
|
+};
|
|
+
|
|
+
|
|
+PyMODINIT_FUNC
|
|
+PyInit__direct(void)
|
|
+{
|
|
+ return PyModuleDef_Init(&moduledef);
|
|
}
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From da22a05e07ba8e88a1220be834b29de0ad975601 Mon Sep 17 00:00:00 2001
|
|
From: ilayn <ilhanpolat@gmail.com>
|
|
Date: Thu, 15 Jan 2026 20:51:09 +0100
|
|
Subject: [PATCH 13/15] MAINT:optimize: Decref callback in DIRECT solver
|
|
|
|
---
|
|
scipy/optimize/_direct/DIRect.c | 1 +
|
|
1 file changed, 1 insertion(+)
|
|
|
|
diff --git a/scipy/optimize/_direct/DIRect.c b/scipy/optimize/_direct/DIRect.c
|
|
index 85bdaed..16c6426 100644
|
|
--- a/scipy/optimize/_direct/DIRect.c
|
|
+++ b/scipy/optimize/_direct/DIRect.c
|
|
@@ -780,6 +780,7 @@
|
|
ret = NULL;
|
|
goto cleanup;
|
|
}
|
|
+ Py_DECREF(callback_py); /* DECREF the callback's return value */
|
|
}
|
|
/* L10: */
|
|
}
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From e38338f279ba7235d479e3ed202b118c508d82d1 Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Wed, 28 Jan 2026 01:00:20 +0100
|
|
Subject: [PATCH 14/15] BUG: Initialize icoor array in NI_GeometricTransform in
|
|
ndimage
|
|
|
|
The icoor array could be used uninitialized if a user-provided
|
|
LowLevelCallable fails to write its output, or if the function is
|
|
called without map, matrix, or coordinates set. Initialize to zero
|
|
to ensure defined behavior in these edge cases.
|
|
|
|
Additionally, add explicit error when of map, matrix, or
|
|
coordinates is not provided, rather than silently proceeding
|
|
with zero-initialized coordinates.
|
|
|
|
Uncovered by Coverity static analysis
|
|
---
|
|
scipy/ndimage/src/ni_interpolation.c | 7 ++++++-
|
|
1 file changed, 6 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/scipy/ndimage/src/ni_interpolation.c b/scipy/ndimage/src/ni_interpolation.c
|
|
index 5a11419..84e9a72 100644
|
|
--- a/scipy/ndimage/src/ni_interpolation.c
|
|
+++ b/scipy/ndimage/src/ni_interpolation.c
|
|
@@ -265,7 +265,7 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*,
|
|
npy_intp ftmp[NPY_MAXDIMS], *fcoordinates = NULL, *foffsets = NULL;
|
|
npy_intp cstride = 0, kk, hh, ll, jj;
|
|
npy_intp size;
|
|
- double **splvals = NULL, icoor[NPY_MAXDIMS], tmp;
|
|
+ double **splvals = NULL, icoor[NPY_MAXDIMS] = {0}, tmp;
|
|
npy_intp idimensions[NPY_MAXDIMS], istrides[NPY_MAXDIMS];
|
|
NI_Iterator io, ic;
|
|
npy_double *matrix = matrix_ar ? (npy_double*)PyArray_DATA(matrix_ar) : NULL;
|
|
@@ -467,6 +467,11 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*,
|
|
"coordinate array data type not supported");
|
|
goto exit;
|
|
}
|
|
+ } else {
|
|
+ NPY_END_THREADS;
|
|
+ PyErr_SetString(PyExc_RuntimeError,
|
|
+ "One of `map`, `matrix` or `coordinates` must be provided");
|
|
+ goto exit;
|
|
}
|
|
|
|
/* iterate over axes: */
|
|
--
|
|
2.53.0
|
|
|
|
|
|
From 859399cecffdbbbc6a0f86c8aec1339200afd6fb Mon Sep 17 00:00:00 2001
|
|
From: Charalampos Stratakis <cstratak@redhat.com>
|
|
Date: Wed, 28 Jan 2026 02:17:37 +0100
|
|
Subject: [PATCH 15/15] BUG: optimize: validate itmax in trlib_eigen_inverse
|
|
|
|
Add early return when itmax <= 0 to prevent use of uninitialized
|
|
residuals array. The inverse iteration loop sets residuals[jj] only
|
|
after the first iteration completes. With itmax <= 0, the loop breaks
|
|
immediately, leaving residuals uninitialized.
|
|
|
|
Uncovered by Coverity static analysis
|
|
---
|
|
scipy/optimize/_trlib/trlib_eigen_inverse.c | 3 ++-
|
|
1 file changed, 2 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/scipy/optimize/_trlib/trlib_eigen_inverse.c b/scipy/optimize/_trlib/trlib_eigen_inverse.c
|
|
index 79c4aaf..4156483 100644
|
|
--- a/scipy/optimize/_trlib/trlib_eigen_inverse.c
|
|
+++ b/scipy/optimize/_trlib/trlib_eigen_inverse.c
|
|
@@ -78,7 +78,8 @@ trlib_int_t trlib_eigen_inverse(
|
|
*lam_pert = -minuslam;
|
|
|
|
if ( *iter_inv == TRLIB_EIR_FAIL_FACTOR ) { TRLIB_PRINTLN_2("Failure on factorizing in inverse correction!") TRLIB_RETURN(TRLIB_EIR_FAIL_FACTOR) }
|
|
-
|
|
+ if ( itmax <= 0 ) { TRLIB_PRINTLN_2("Failure on solving inverse correction! (itmax <= 0)") TRLIB_RETURN(TRLIB_EIR_FAIL_LINSOLVE) }
|
|
+
|
|
// try with TRLIB_EIR_N_STARTVEC different start vectors and hope that it converges for one
|
|
seeds[0] = time(NULL);
|
|
for(jj = 1; jj < TRLIB_EIR_N_STARTVEC; ++jj ) { seeds[jj] = rand(); }
|
|
--
|
|
2.53.0
|
|
|