#5297·or-tools

CP-SAT 9.15.6755: memory-safety regression building large models — `std::bad_alloc` via `repr(IntVar)` and heap corruption (0xC0000374) via `IntVar.Proto()` (works on 9.14)

Author: IDreiCreated Aug 19, 2026Updated Sep 15, 2026
LabelsSolver: CP-SAT Solver

What version of OR-Tools and what language are you using? Version: ortools 9.15.6755 (Python), regression vs 9.14.6206 and 9.12.4544 Language: Python 3.12.9

Which solver are you using (e.g. CP-SAT, Routing Solver, GLOP, BOP, Gurobi) CP-SAT (ortools.sat.python.cp_model)

What operating system (Linux, Windows, ...) and version? Windows 10/11 x64 (also reproduced on Linux CI).

We build a moderately sized CP-SAT model (~16 000 variables) programmatically. The build code and input data are unchanged across ortools versions.

  • On 9.12.4544 and 9.14.6206: the model builds and solves fine (peak working set ≈ 360 MB).
  • On 9.15.6755: the build aborts.

The failure first appears as MemoryError: bad allocation (std::bad_alloc) raised from a plain repr() / str() of an IntVar, executed inside an eager debug f-string:

python
logger.debug(f"{volume_var=}")   # volume_var: normal IntVar, domain [0, 40]

At the moment of that crash the process working set is only ~140 MB, so this is a single pathological allocation, not gradual growth. Inside a ProcessPoolExecutor it surfaces as BrokenProcessPool.

It is not only repr()

We removed the offending log line — the crash just moved to the next eager f"{var=}" site, and then to a third one in another module. We then monkey‑patched __repr__/__str__ to a cheap <Cls id=...> on all 27 classes of ortools.sat.python.cp_model. Logging stopped crashing and the build progressed further, but the process still died, now with heap corruption:

Windows fatal exception: code 0xC0000374        # STATUS_HEAP_CORRUPTION
Current thread (most recent call first):
  File ".../classes/vars.py", line 113 in _var_can_be_positive
      domain = list(var.Proto().domain)
  ...

In isolation, both repr(var) and var.Proto().domain behave correctly on 9.15 (e.g. new_int_var(0, 50, "v")repr is short, Proto().domain == [0, 50]). The failures only appear while building the full model, and manifest in different forms (std::bad_alloc via repr, heap corruption via Proto()). This strongly suggests an underlying memory-safety regression in the 9.15 CP-SAT / pybind layer for large models.

Repro / attachments

  • model_dump.pbCpModelProto (≈1.8 MB, 16 027 variables) serialized on 9.14 at the failing construction step.
  • mem_probe.py — benchmark showing common construction patterns (sum, min/max, multiplication, big domains, affine) do not regress in memory on 9.15 (negative evidence).
  • repr_probe.py — rebuilds each variable from the proto in isolation and str()s them; does not reproduce (domain alone is not the trigger).
  • real_build_probe.py, min_repro_attempt.py — driver / attempts.

Note: because the crash is context-dependent (full model construction), the isolated scripts don’t reproduce it by themselves; the reliable reproduction is the full build. We’re happy to provide a runnable end-to-end reproducer or work with a maintainer to capture the exact triggering API-call sequence.

What did you expect to see?

Building the model succeeds on 9.15, as it does on 9.14 / 9.12; repr()/str() and IntVar.Proto() remain safe.

What did you see instead?

std::bad_alloc (via repr) and, after neutralizing repr, STATUS_HEAP_CORRUPTION (0xC0000374) via IntVar.Proto(), during model construction on 9.15.6755 only.

API notes (possibly related)

  • CpModel.proto is now a native cp_model_helper.CpModelProto; proto.ByteSize() and proto.ParseFromString() are gone (present on ≤9.14).
  • str(IntVar) formatting is inconsistent for in-model variables (sometimes name(lb..ub), sometimes just name); some variables report an empty .name.

Workaround

Pin ortools to ~9.14.6206. Removing logging is not a reliable fix (the crash reappears via IntVar.Proto()).

mem_probe.py min_repro_attempt.py model_dump.zip real_build_probe.py repr_probe.py