Repository navigation
UBSAN: invalid bool load in HVectorBase move assignment from Basis::build() (QP solver) #3419
Description
What version of HiGHS and what language are you using?
Version: v1.15.1
Language: C++
Reproduced standalone with v1.15.1 (below). Also seen with v1.14.0 as bundled in the R package highs 1.14.0-2; the four files involved (HVectorBase.h, HVectorBase.cpp, HFactor.h, qpsolver/basis.cpp) are identical between those tags, and latest (2100494d80) still has the same code.
Which solver are you using (e.g. LP, MIP, QP)
QP
What operating system (Linux, Windows, ...) and version?
Linux: Debian GNU/Linux forky/sid, x86_64. GCC 16.2.0, CMake 4.3.4.
What did you do?
Built HiGHS v1.15.1 and a two-variable QP with UndefinedBehaviorSanitizer, then solved it.
reprex.cpp:
#include "Highs.h"
#include <cstdio>
int main() {
// min x0^2 + x1^2 - x0 - x1 s.t. x0 + x1 >= 1, x >= 0
HighsModel model;
model.lp_.num_col_ = 2;
model.lp_.num_row_ = 1;
model.lp_.col_cost_ = {-1.0, -1.0};
model.lp_.col_lower_ = {0.0, 0.0};
model.lp_.col_upper_ = {kHighsInf, kHighsInf};
model.lp_.row_lower_ = {1.0};
model.lp_.row_upper_ = {kHighsInf};
model.lp_.a_matrix_.format_ = MatrixFormat::kColwise;
model.lp_.a_matrix_.start_ = {0, 1, 2};
model.lp_.a_matrix_.index_ = {0, 0};
model.lp_.a_matrix_.value_ = {1.0, 1.0};
model.hessian_.dim_ = 2; // Q = 2 I
model.hessian_.start_ = {0, 1, 2};
model.hessian_.index_ = {0, 1};
model.hessian_.value_ = {2.0, 2.0};
Highs highs;
highs.setOptionValue("output_flag", false);
highs.passModel(model);
highs.run();
const auto& x = highs.getSolution().col_value;
std::printf("status %s x = (%g, %g)\n",
highs.modelStatusToString(highs.getModelStatus()).c_str(), x[0], x[1]);
return 0;
}Build and run:
cmake -S HiGHS -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF -DFAST_BUILD=ON \
-DCMAKE_CXX_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer" \
-DCMAKE_C_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer"
cmake --build build -j --target highs
g++ -std=c++17 -g -fsanitize=undefined -fno-omit-frame-pointer \
-IHiGHS/highs -Ibuild -IHiGHS/extern reprex.cpp build/lib/libhighs.a -lz -lpthread -ldl -o reprex
UBSAN_OPTIONS=print_stacktrace=1 ./reprexWhat did you expect to see
status Optimal x = (0.5, 0.5)
with no sanitizer report.
What did you see instead?
The correct solution, preceded by a UBSAN report on every run:
highs/util/HVectorBase.h:25:7: runtime error: load of value 16, which is not a valid value for type 'bool'
#0 HVectorBase<double>::operator=(HVectorBase<double>&&) highs/util/HVectorBase.h:25
#1 HFactor::operator=(HFactor&&) highs/util/HFactor.h:103
#2 Basis::build() highs/qpsolver/basis.cpp:53
#3 Basis::Basis(Runtime&, ...)
#4 solveqp_actual(...)
#5 solveqp(...)
#6 Highs::callSolveQp() highs/lp_data/Highs.cpp:4302
#7 Highs::calledOptimizeModel() highs/lp_data/Highs.cpp:1369
#8 Highs::optimizeModel() highs/lp_data/Highs.cpp:1162
#9 Highs::optimizeHighs() highs/lp_data/Highs.cpp:1142
#10 Highs::run() highs/lp_data/Highs.cpp:1116
#11 main reprex.cpp:30
status Optimal x = (0.5, 0.5)
The value read differs from run to run (184, 96 and 16 were observed), as expected from uninitialized memory.
Cause: Basis::build() (highs/qpsolver/basis.cpp:53) does basisfactor = HFactor();. The temporary HFactor holds HVectorBase members that never went through setup(). HVectorBase::packFlag is declared without an initializer (highs/util/HVectorBase.h:71, bool packFlag;) and is only set in setup() / clear() (HVectorBase.cpp:41, :72), so the implicitly generated move assignment copies an indeterminate bool, which is undefined behavior.
Suggested fix, in highs/util/HVectorBase.h:
- bool packFlag; //!< Flag to indicate whether to pack or not
+ bool packFlag = false; //!< Flag to indicate whether to pack or notWith that one change the reproducer reports nothing in five runs out of five, and the result is unchanged. The other scalar members of HVectorBase (size, count, packCount, synthetic_tick, next) are also indeterminate in a default-constructed object; copying them is not flagged by UBSAN, but default initializers for them too (or value-initializing in Basis::build()) would make the moved-from temporary fully defined.
Anything else we should know about your project / environment
Found while running an R package (I am a co-author with Florian Schwendinger) checks under UBSAN. The R package highs bundles HiGHS, and CRAN runs package checks under UBSAN, so any R package whose tests solve a QP with HiGHS shows this line in its sanitizer results. The Linux environment above is an x86_64 container (rocker/r-devel-san base) running under Rosetta on an Apple-silicon Mac.
Activity
jajhall commentedon Oct 15, 2026
Interesting, as this is a very old and fundamental object in HiGHS that has not caused problems before
jajhall commentedon Oct 16, 2026
Closed by #3430