Fix issues uncovered via coverity scanning

Related: RHEL-120814
This commit is contained in:
Charalampos Stratakis 2026-02-09 21:09:42 +01:00
parent 07ecb81a04
commit 35d1d1cb22
4 changed files with 1228 additions and 1 deletions

View File

@ -0,0 +1,379 @@
From e9525346e36552686c6f9266c7388791bb27cb0b Mon Sep 17 00:00:00 2001
From: fwesselm <fwesselm@mathworks.com>
Date: Wed, 11 Dec 2024 15:55:52 +0100
Subject: [PATCH 1/7] Fix detection of implied integral (already
integer-constrained) variables and implicit (continuous) integer variables
---
subprojects/highs/src/presolve/HPresolve.cpp | 24 +++++++++++++-------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/subprojects/highs/src/presolve/HPresolve.cpp b/subprojects/highs/src/presolve/HPresolve.cpp
index 5360c9e..d6dd158 100644
--- a/subprojects/highs/src/presolve/HPresolve.cpp
+++ b/subprojects/highs/src/presolve/HPresolve.cpp
@@ -223,6 +223,7 @@ void HPresolve::dualImpliedFreeGetRhsAndRowType(
}
bool HPresolve::isImpliedIntegral(HighsInt col) {
+ // check if the integer constraint on a variable is implied by the model
bool runDualDetection = true;
assert(model->integrality_[col] == HighsVarType::kInteger);
@@ -263,10 +264,15 @@ bool HPresolve::isImpliedIntegral(HighsInt col) {
if (!runDualDetection) return false;
+ bool impliedIntegral = true;
for (const HighsSliceNonzero& nz : getColumnVector(col)) {
double scale = 1.0 / nz.value();
- if (!rowCoefficientsIntegral(nz.index(), scale)) return false;
+ // if row coefficients are not integral, variable is not (implied) integral
+ bool rowIntegral = rowCoefficientsIntegral(nz.index(), scale);
+ impliedIntegral = impliedIntegral && rowIntegral;
+ if (!rowIntegral) continue;
if (model->row_upper_[nz.index()] != kHighsInf) {
+ // scale, round down and unscale right-hand side again
double rUpper =
std::abs(nz.value()) *
std::floor(model->row_upper_[nz.index()] * std::abs(scale) +
@@ -276,24 +282,26 @@ bool HPresolve::isImpliedIntegral(HighsInt col) {
model->row_upper_[nz.index()] = rUpper;
markChangedRow(nz.index());
}
- } else {
- assert(model->row_lower_[nz.index()] != -kHighsInf);
+ }
+ if (model->row_lower_[nz.index()] != -kHighsInf) {
+ // scale, round up and unscale left-hand side again
double rLower =
std::abs(nz.value()) *
- std::ceil(model->row_upper_[nz.index()] * std::abs(scale) -
+ std::ceil(model->row_lower_[nz.index()] * std::abs(scale) -
primal_feastol);
if (std::abs(model->row_lower_[nz.index()] - rLower) >
options->small_matrix_value) {
- model->row_upper_[nz.index()] = rLower;
+ model->row_lower_[nz.index()] = rLower;
markChangedRow(nz.index());
}
}
}
- return true;
+ return impliedIntegral;
}
bool HPresolve::isImpliedInteger(HighsInt col) {
+ // check if a non-integer variable is implied integer
bool runDualDetection = true;
assert(model->integrality_[col] == HighsVarType::kContinuous);
@@ -345,11 +353,11 @@ bool HPresolve::isImpliedInteger(HighsInt col) {
for (const HighsSliceNonzero& nz : getColumnVector(col)) {
double scale = 1.0 / nz.value();
if (model->row_upper_[nz.index()] != kHighsInf &&
- fractionality(model->row_upper_[nz.index()]) > primal_feastol)
+ fractionality(model->row_upper_[nz.index()] * scale) > primal_feastol)
return false;
if (model->row_lower_[nz.index()] != -kHighsInf &&
- fractionality(model->row_lower_[nz.index()]) > primal_feastol)
+ fractionality(model->row_lower_[nz.index()] * scale) > primal_feastol)
return false;
if (!rowCoefficientsIntegral(nz.index(), scale)) return false;
--
2.53.0
From 7f75cc45101d393b4247e0eec00c59dc2c46f58e Mon Sep 17 00:00:00 2001
From: Ivet Galabova <galabovaa@gmail.com>
Date: Mon, 13 Jan 2025 16:57:56 +0000
Subject: [PATCH 2/7] free gpu memory, localtermination wip
---
subprojects/highs/src/pdlp/CupdlpWrapper.cpp | 55 +++++++++++++------
.../highs/src/pdlp/cupdlp/cupdlp_linalg.c | 13 +++++
2 files changed, 51 insertions(+), 17 deletions(-)
diff --git a/subprojects/highs/src/pdlp/CupdlpWrapper.cpp b/subprojects/highs/src/pdlp/CupdlpWrapper.cpp
index 31fa31e..8d905da 100644
--- a/subprojects/highs/src/pdlp/CupdlpWrapper.cpp
+++ b/subprojects/highs/src/pdlp/CupdlpWrapper.cpp
@@ -66,8 +66,6 @@ HighsStatus solveLpCupdlp(const HighsOptions& options, HighsTimer& timer,
0.0; // true objVal = sig * c'x - offset, sig = 1 (min) or -1 (max)
double sense_origin = 1; // 1 (min) or -1 (max)
int* constraint_new_idx = NULL;
- cupdlp_float* x_origin = cupdlp_NULL;
- cupdlp_float* y_origin = cupdlp_NULL;
void* model = NULL;
void* presolvedmodel = NULL;
@@ -159,8 +157,6 @@ HighsStatus solveLpCupdlp(const HighsOptions& options, HighsTimer& timer,
// CUPDLP_CALL(LP_SolvePDHG(prob, ifChangeIntParam, intParam,
// ifChangeFloatParam, floatParam, fp));
- cupdlp_init_double(x_origin, nCols_origin);
- cupdlp_init_double(y_origin, nRows);
// Resize the highs_solution so cuPDLP-c can use it internally
highs_solution.col_value.resize(lp.num_col_);
highs_solution.row_value.resize(lp.num_row_);
@@ -208,19 +204,31 @@ HighsStatus solveLpCupdlp(const HighsOptions& options, HighsTimer& timer,
analysePdlpSolution(options, lp, highs_solution);
#endif
- free(cost);
- free(lower);
- free(upper);
- free(csc_beg);
- free(csc_idx);
- free(csc_val);
- free(rhs);
+ // free(cost);
+ // free(lower);
+ // free(upper);
+ // free(csc_beg);
+ // free(csc_idx);
+ // free(csc_val);
+ // free(rhs);
- free(x_origin);
- free(y_origin);
+ // free(constraint_new_idx);
- free(constraint_new_idx);
+ // Scaling
+#ifdef CUPDLP_CPU
+ if (scaling->rowScale != nullptr) free(scaling->rowScale);
+ if (scaling->colScale != nullptr) free(scaling->colScale);
+ free(scaling);
+
+#else
+ // free problem
+ if (scaling) {
+ scaling_clear(scaling);
+ }
+#endif
+
+#ifdef CUPDLP_CPU
free(prob->cost);
free(prob->lower);
free(prob->upper);
@@ -248,10 +256,23 @@ HighsStatus solveLpCupdlp(const HighsOptions& options, HighsTimer& timer,
free(csc_cpu->colMatElem);
free(csc_cpu);
+#endif
- if (scaling->rowScale != nullptr) free(scaling->rowScale);
- if (scaling->colScale != nullptr) free(scaling->colScale);
- free(scaling);
+ if (cost != NULL) cupdlp_free(cost);
+ if (csc_beg != NULL) cupdlp_free(csc_beg);
+ if (csc_idx != NULL) cupdlp_free(csc_idx);
+ if (csc_val != NULL) cupdlp_free(csc_val);
+ if (rhs != NULL) cupdlp_free(rhs);
+ if (lower != NULL) cupdlp_free(lower);
+ if (upper != NULL) cupdlp_free(upper);
+ if (constraint_new_idx != NULL) cupdlp_free(constraint_new_idx);
+
+ // constraint type is std::vector
+ // if (constraint_type != NULL) cupdlp_free(constraint_type);
+
+ // free memory
+ csc_clear(csc_cpu);
+ problem_clear(prob);
return HighsStatus::kOk;
}
diff --git a/subprojects/highs/src/pdlp/cupdlp/cupdlp_linalg.c b/subprojects/highs/src/pdlp/cupdlp/cupdlp_linalg.c
index 8d28043..bf3e2fe 100644
--- a/subprojects/highs/src/pdlp/cupdlp/cupdlp_linalg.c
+++ b/subprojects/highs/src/pdlp/cupdlp/cupdlp_linalg.c
@@ -801,3 +801,16 @@ void cupdlp_compute_interaction_and_movement(CUPDLPwork *w,
cupdlp_sub(w->buffer3, iterates->aty->data, iterates->atyUpdate->data, nCols);
cupdlp_dot(w, nCols, w->buffer2, w->buffer3, dInteraction);
}
+
+double get_fabs_value(double* vec, int index) {
+#ifdef CUPDLP_CPU
+ return vec[index];
+#else
+ double result = 0;
+ int status = -1;
+ get_gpu_vec_element(vec, index, &result, &status);
+ if (!status)
+ return 0;
+ return result;
+#endif
+}
\ No newline at end of file
--
2.53.0
From 9efff0fc9ae0eb870ea8f6b5742ea9e17c90de52 Mon Sep 17 00:00:00 2001
From: Julian Hall <jajhall@ed.ac.uk>
Date: Thu, 11 Dec 2025 20:23:13 +0000
Subject: [PATCH 3/7] Modify version and URL for HiGHS release
Updated version and URL in CITATION.cff
---
subprojects/highs/CITATION.cff | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/subprojects/highs/CITATION.cff b/subprojects/highs/CITATION.cff
index 0987d34..da14abc 100644
--- a/subprojects/highs/CITATION.cff
+++ b/subprojects/highs/CITATION.cff
@@ -6,14 +6,10 @@ authors:
email: HighsOpt@gmail.com
- given-names: Ivet
family-names: Galabova
-- given-names: Leona
- family-names: Gottwald
-- given-names: Michael
- family-names: Feldmeier
title: "HiGHS"
-version: 1.2.2
+version: 1.12.0
date-released: 2022-04-18
-url: "https://github.com/ERGO-Code/HiGHS/releases/tag/v1.2.2"
+url: "https://github.com/ERGO-Code/HiGHS/releases/tag/v1.12.0"
preferred-citation:
type: article
authors:
--
2.53.0
From 7d971de3169912e449b2d2f8ab00f9f7be0968f0 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com>
Date: Fri, 19 Dec 2025 17:42:13 +0100
Subject: [PATCH 4/7] Fix use-after-close in writeSolution when ranging fails
Add missing return statement after returnFromWriteSolution() call
when getRangingInterface() returns an error, preventing fprintf()
from writing to an already-closed file handle.
---
subprojects/highs/src/lp_data/Highs.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/subprojects/highs/src/lp_data/Highs.cpp b/subprojects/highs/src/lp_data/Highs.cpp
index c1c3254..a175f1c 100644
--- a/subprojects/highs/src/lp_data/Highs.cpp
+++ b/subprojects/highs/src/lp_data/Highs.cpp
@@ -3272,7 +3272,7 @@ HighsStatus Highs::writeSolution(const std::string& filename,
interpretCallStatus(options_.log_options, this->getRangingInterface(),
return_status, "getRangingInterface");
if (return_status == HighsStatus::kError)
- returnFromWriteSolution(file, return_status);
+ return returnFromWriteSolution(file, return_status);
fprintf(file, "\n# Ranging\n");
writeRangingFile(file, model_.lp_, info_.objective_function_value, basis_,
solution_, ranging_, style);
--
2.53.0
From d7a19c9fad468ca4b8e094da67ea2d8cc3760e7f Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com>
Date: Mon, 22 Dec 2025 02:02:25 +0100
Subject: [PATCH 5/7] mip: Fix use-after-move in HighsLpRelaxation::loadModel
Save lpmodel.num_col_ before passing lpmodel via std::move to
lpsolver.passModel(). After the move, lpmodel is in a valid but
unspecified state, making access to its members undefined behavior.
Found by Coverity static analysis.
---
subprojects/highs/src/mip/HighsLpRelaxation.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/subprojects/highs/src/mip/HighsLpRelaxation.cpp b/subprojects/highs/src/mip/HighsLpRelaxation.cpp
index 4288f99..58e0e4d 100644
--- a/subprojects/highs/src/mip/HighsLpRelaxation.cpp
+++ b/subprojects/highs/src/mip/HighsLpRelaxation.cpp
@@ -222,11 +222,12 @@ void HighsLpRelaxation::loadModel() {
for (HighsInt i = 0; i != lpmodel.num_row_; ++i)
lprows.push_back(LpRow::model(i));
lpmodel.integrality_.clear();
+ HighsInt num_col = lpmodel.num_col_;
lpsolver.clearSolver();
lpsolver.clearModel();
lpsolver.passModel(std::move(lpmodel));
- colLbBuffer.resize(lpmodel.num_col_);
- colUbBuffer.resize(lpmodel.num_col_);
+ colLbBuffer.resize(num_col);
+ colUbBuffer.resize(num_col);
}
void HighsLpRelaxation::resetToGlobalDomain() {
--
2.53.0
From cba9eba8aed879bc6335d3f4d43a5cd9f891e854 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com>
Date: Wed, 14 Jan 2026 04:50:19 +0100
Subject: [PATCH 6/7] BUG: initialize save_value field to fix uninitialized
memory use
When a HighsSimplexBadBasisChangeRecord is created, save_value is not
set until later in applyTabooRowOut/applyTabooVariableIn. Pushing the
struct with an uninitialized field copies garbage memory.
Found via Coverity static analysis
---
subprojects/highs/src/simplex/SimplexStruct.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/subprojects/highs/src/simplex/SimplexStruct.h b/subprojects/highs/src/simplex/SimplexStruct.h
index eb49c8c..996dcc4 100644
--- a/subprojects/highs/src/simplex/SimplexStruct.h
+++ b/subprojects/highs/src/simplex/SimplexStruct.h
@@ -258,7 +258,7 @@ struct HighsSimplexBadBasisChangeRecord {
HighsInt variable_out;
HighsInt variable_in;
BadBasisChangeReason reason;
- double save_value;
+ double save_value = 0.0;
};
#endif /* SIMPLEX_SIMPLEXSTRUCT_H_ */
--
2.53.0
From ee47ee8895c789980a1e90d8e0c17f54a69b4440 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com>
Date: Wed, 28 Jan 2026 01:23:57 +0100
Subject: [PATCH 7/7] Initialize nowactiveatlower in ratiotest_textbook
The nowactiveatlower field was only set inside the conditional blocks
when a limiting constraint was found (alpha_i < result.alpha). If both
loops completed without finding a limiting constraint, the field remained
uninitialized.
Uncovered by Coverity static analysis
---
subprojects/highs/src/qpsolver/ratiotest.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/subprojects/highs/src/qpsolver/ratiotest.cpp b/subprojects/highs/src/qpsolver/ratiotest.cpp
index dd313c4..424f695 100644
--- a/subprojects/highs/src/qpsolver/ratiotest.cpp
+++ b/subprojects/highs/src/qpsolver/ratiotest.cpp
@@ -17,6 +17,7 @@ static RatiotestResult ratiotest_textbook(Runtime& rt, const QpVector& p,
RatiotestResult result;
result.limitingconstraint = -1;
result.alpha = alphastart;
+ result.nowactiveatlower = false;
// check ratio towards variable bounds
for (HighsInt j = 0; j < p.num_nz; j++) {
--
2.53.0

View File

@ -0,0 +1,70 @@
From 7f973498c9c628cd4f1d04272ea9bf81ec150912 Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com>
Date: Sat, 20 Dec 2025 01:40:34 +0100
Subject: [PATCH 1/2] BUG: amos/amos.h: fix pointer dereference in uni2
The code was performing pointer arithmetic instead of incrementing
the value, unlike the identical pattern in uni1.
Found by coverity static analysis.
---
subprojects/xsf/include/xsf/amos/amos.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/subprojects/xsf/include/xsf/amos/amos.h b/subprojects/xsf/include/xsf/amos/amos.h
index 08d4ef5..48c0fff 100644
--- a/subprojects/xsf/include/xsf/amos/amos.h
+++ b/subprojects/xsf/include/xsf/amos/amos.h
@@ -5304,7 +5304,7 @@ namespace amos {
// SET UNDERFLOW AND UPDATE PARAMETERS
//
y[nd - 1] = 0.0;
- nz += 1;
+ *nz += 1;
nd -= 1;
if (nd == 0) {
return;
@@ -5312,7 +5312,7 @@ namespace amos {
nuf = uoik(z, fnu, kode, 1, nd, y, tol, elim, alim);
if (nuf >= 0) {
nd -= nuf;
- nz += nuf;
+ *nz += nuf;
if (nd == 0) {
return;
}
--
2.53.0
From 8ff3b3f02eb7e35f3741a72c078ba49f8b8de3db Mon Sep 17 00:00:00 2001
From: Charalampos Stratakis <cstratak@redhat.com>
Date: Wed, 28 Jan 2026 01:54:42 +0100
Subject: [PATCH 2/2] BUG: specfun/specfun.h: initialize kd in mtu12 to fix
potential uninitialized use
The kd variable was not initialized before the kf==1/kf==2 conditional
blocks. If kf had an unexpected value, kd would be used uninitialized
when passed to cva2(). Initialize to 0 for consistency with mtu0.
Uncovered by Coverity static analysis.
---
subprojects/xsf/include/xsf/specfun/specfun.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/subprojects/xsf/include/xsf/specfun/specfun.h b/subprojects/xsf/include/xsf/specfun/specfun.h
index 6247601..a5a3ac6 100644
--- a/subprojects/xsf/include/xsf/specfun/specfun.h
+++ b/subprojects/xsf/include/xsf/specfun/specfun.h
@@ -4759,7 +4759,7 @@ namespace specfun {
T eps = 1.0e-14;
T a, qm, c1, c2, u1, u2, w1, w2;
- int kd, km, ic, k, nm = 0;
+ int kd = 0, km, ic, k, nm = 0;
if ((kf == 1) && (m % 2 == 0)) {
kd = 1;
--
2.53.0

View File

@ -46,7 +46,7 @@
Summary: Scientific Tools for Python
Name: python%{python3_pkgversion}-scipy
Version: 1.16.2
Release: 2%{?dist}
Release: 3%{?dist}
# BSD-3-Clause -- whole package except:
# BSD-2-Clause -- scipy/_lib/_pep440.py
@ -78,6 +78,39 @@ Source2: https://github.com/mesonbuild/meson/releases/download/1.4.1/meson-1.
# Compatibility patch for meson 1.4.1
Patch: compatibility-with-meson-1.4.1.patch
# Coverity scan fixes for scipy
# Backported from upstream:
# https://github.com/scipy/scipy/pull/24211
# https://github.com/scipy/scipy/pull/24202
# https://github.com/scipy/scipy/pull/24204
# https://github.com/scipy/scipy/pull/24206
# https://github.com/scipy/scipy/pull/24220
# https://github.com/scipy/scipy/pull/24219
# https://github.com/scipy/scipy/pull/24453
# https://github.com/scipy/scipy/pull/24368
# https://github.com/scipy/scipy/pull/24373
# https://github.com/scipy/scipy/pull/24455
# https://github.com/scipy/scipy/pull/24209
# https://github.com/scipy/scipy/pull/24374
# https://github.com/scipy/scipy/pull/24384
Patch: scipy-coverity-fixes.patch
# Coverity scan fixes for the bundled HiGHS project
# Backported from upstream
# https://github.com/ERGO-Code/HiGHS/pull/2711
# https://github.com/ERGO-Code/HiGHS/commit/f175850569ffbb2aba55b7645cec19c0132d8e63
# https://github.com/ERGO-Code/HiGHS/commit/1f86a97198609680ce3c950401ae056e3d7aca39
# https://github.com/ERGO-Code/HiGHS/pull/2713
# https://github.com/ERGO-Code/HiGHS/pull/2778
# https://github.com/ERGO-Code/HiGHS/pull/2747
Patch: bundled-highs-coverity-fixes.patch
# Coverity scan fixes for the bundled xfs project
# Backported from upstream:
# https://github.com/scipy/xsf/pull/86
# https://github.com/scipy/xsf/pull/91
Patch: bundled-xfs-coverity-fixes.patch
BuildRequires: %{blaslib}-devel
BuildRequires: gcc-gfortran, gcc-c++
@ -297,6 +330,10 @@ popd
%changelog
* Tue Feb 10 2026 Charalampos Stratakis <cstratak@redhat.com> - 1.16.2-3
- Fix issues uncovered via coverity scanning
Related: RHEL-120814
* Wed Jan 28 2026 Lumir Balhar <lbalhar@redhat.com> - 1.16.2-2
- Rebuild with newer Cython

741
scipy-coverity-fixes.patch Normal file
View File

@ -0,0 +1,741 @@
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