Skip to content

model_builder (Python): add_enforced_linear_constraint corrupts an unrelated constraint or segfaults (writes via non-enforced setters) #5302

Closed

Description

@winklemad

What version of OR-Tools and what language are you using?

Version: 9.15.6755 (reproduced against the published wheel; the affected function is byte-identical on the current main)
Language: Python

Which solver are you using (e.g. CP-SAT, Routing, GLOP, BOP, Gurobi)?

ortools.linear_solver.python.model_builder (ModelBuilder)

What operating system (Linux, Windows, ...)?

Linux (platform-independent — it's a proto-index bug in pure Python)

What did you do?

Called Model.add_enforced_linear_constraint:

from ortools.linear_solver.python import model_builder as mb

# (a) crash
m = mb.Model()
x = m.new_bool_var("x"); y = m.new_num_var(0.0, 10.0, "y")
m.add_enforced_linear_constraint(y, x, True, lb=0.0, ub=3.0, name="enf")
# -> process dies, SIGSEGV (exit 139)

# (b) silent corruption + wrong optimum
m = mb.Model()
yy = m.new_num_var(0.0, 10.0, "y"); w = m.new_num_var(0.0, 10.0, "w"); z = m.new_bool_var("z")
cap = m.add_linear_constraint(yy, ub=2.0, name="cap")       # user constraint y <= 2
m.add_enforced_linear_constraint(w, z, True, ub=3.0, name="ind")
m.maximize(yy)
s = mb.Solver("sat"); print(s.solve(m), s.objective_value)
# -> OPTIMAL 3.0   (expected 2.0); cap.name is now "ind", cap.upper_bound is now 3.0

What did you expect to see?

The indicator constraint stored in general_constraint, the unrelated cap constraint untouched, and the optimum 2.0.

What did you see instead?

Either a segfault or silent corruption of an unrelated constraint with a wrong optimum and no error.

Root cause. Model.add_enforced_linear_constraint and the BoundedLinearExpression branch of _add_enforced_linear_constraint_to_helper (ortools/linear_solver/python/model_builder.py) write the indicator constraint's name, bounds and terms through the non-enforced helper setters — set_constraint_name / set_constraint_lower_bound / set_constraint_upper_bound / add_terms_to_constraint — while passing the enforced constraint's index.

Enforced constraints live in MPModelProto.general_constraint[i]; those setters address MPModelProto.constraint[i]. The index spaces are disjoint, so the write lands on an unrelated linear constraint (destroying it) while the indicator is left empty — or, when no linear constraint exists at that index, mutable_constraint(i) reads out of bounds and the interpreter segfaults (the DCHECK is compiled out in the release wheel).

This is a lone divergence: the bool branch of the same helper already uses the set_enforced_* / add_terms_to_enforced_constraint variants, as do the EnforcedLinearConstraint property setters, and both the Java and C# ports do it correctly (ModelBuilder.java addEnforcedConstraintTerm + lin.setLowerBound, ModelBuilder.cs AddEnforcedConstraintTerm). Only the Python add_enforced_linear_constraint path uses the wrong setters.

I have a fix ready that routes every setter in the enforced paths through the set_enforced_* variants (mirroring the already-correct bool branch), plus regression tests for both the crash and the corruption case, and will open a PR referencing this issue.

Activity

  1. added this to the v10.0 milestone on Aug 24, 2026
  2. added a commit that references this issue on Aug 25, 2026
    202812c
  3. lperron commented on Aug 25, 2026

    @lperron
    Collaborator

    Thanks, I pushed the fix on the main branch.

  4. added 2 commits that reference this issue on Aug 26, 2026
    eea76e5
    b77c7fb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions