Skip to content

UBSAN: invalid bool load in HVectorBase move assignment from Basis::build() (QP solver) #3419

Closed

Description

@bnaras

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 ./reprex

What 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 not

With 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

  1. jajhall commented on Oct 15, 2026

    @jajhall
    SponsorMember

    Interesting, as this is a very old and fundamental object in HiGHS that has not caused problems before

  2. self-assigned this
    on Oct 15, 2026
  3. jajhall commented on Oct 16, 2026

    @jajhall
    SponsorMember

    Closed by #3430

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions