From 35d1d1cb22cfd192ab443015188e5146234f3c90 Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Mon, 9 Feb 2026 21:09:42 +0100 Subject: [PATCH] Fix issues uncovered via coverity scanning Related: RHEL-120814 --- bundled-highs-coverity-fixes.patch | 379 +++++++++++++++ bundled-xfs-coverity-fixes.patch | 70 +++ python3.14-scipy.spec | 39 +- scipy-coverity-fixes.patch | 741 +++++++++++++++++++++++++++++ 4 files changed, 1228 insertions(+), 1 deletion(-) create mode 100644 bundled-highs-coverity-fixes.patch create mode 100644 bundled-xfs-coverity-fixes.patch create mode 100644 scipy-coverity-fixes.patch diff --git a/bundled-highs-coverity-fixes.patch b/bundled-highs-coverity-fixes.patch new file mode 100644 index 0000000..b7c07e8 --- /dev/null +++ b/bundled-highs-coverity-fixes.patch @@ -0,0 +1,379 @@ +From e9525346e36552686c6f9266c7388791bb27cb0b Mon Sep 17 00:00:00 2001 +From: fwesselm +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 +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 +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 +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 +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 +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 +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 + diff --git a/bundled-xfs-coverity-fixes.patch b/bundled-xfs-coverity-fixes.patch new file mode 100644 index 0000000..5480aef --- /dev/null +++ b/bundled-xfs-coverity-fixes.patch @@ -0,0 +1,70 @@ +From 7f973498c9c628cd4f1d04272ea9bf81ec150912 Mon Sep 17 00:00:00 2001 +From: Charalampos Stratakis +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 +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 + diff --git a/python3.14-scipy.spec b/python3.14-scipy.spec index 6db20c4..4566aa8 100644 --- a/python3.14-scipy.spec +++ b/python3.14-scipy.spec @@ -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 - 1.16.2-3 +- Fix issues uncovered via coverity scanning +Related: RHEL-120814 + * Wed Jan 28 2026 Lumir Balhar - 1.16.2-2 - Rebuild with newer Cython diff --git a/scipy-coverity-fixes.patch b/scipy-coverity-fixes.patch new file mode 100644 index 0000000..bfe8eab --- /dev/null +++ b/scipy-coverity-fixes.patch @@ -0,0 +1,741 @@ +From 0d941ffeac1ac04a6d76890bdf114d898267d488 Mon Sep 17 00:00:00 2001 +From: Charalampos Stratakis +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 + // #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 +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 +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 +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 +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 +