From 0a5b034d53df714b70ece5ae086f618ba60854c3 Mon Sep 17 00:00:00 2001 From: wstran <60024285+wstran@users.noreply.github.com> Date: Sun, 31 May 2026 23:40:27 +0700 Subject: [PATCH] Fix unsound relaxation of contradictory pure range constraints `optimize_range_constraints` merges the pure range constraints of all unconditional bus interactions per expression via `RangeConstraint::conjunction`. When two of them are mutually exclusive (e.g. `x = 1` and `x = 2`), `conjunction` cannot represent an empty range and falls back to the single value `{0}`, so the contradiction is silently relaxed into a satisfiable constraint and re-emitted. This turns an unsatisfiable system into a satisfiable one, violating the documented invariant that range-constraint implementation must never relax a constraint for soundness. Detect the contradiction with `RangeConstraint::is_disjoint` while merging and, when found, record an explicit unsatisfiable constraint instead of relaxing it, keeping the system unsatisfiable. Reported in #3748. --- .../src/range_constraint_optimizer.rs | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/autoprecompiles/src/range_constraint_optimizer.rs b/autoprecompiles/src/range_constraint_optimizer.rs index 02d4576d71..90a18e747d 100644 --- a/autoprecompiles/src/range_constraint_optimizer.rs +++ b/autoprecompiles/src/range_constraint_optimizer.rs @@ -65,6 +65,9 @@ pub fn optimize_range_constraints for SingleValueHandler { + fn handle_bus_interaction( + &self, + bus_interaction: BusInteraction>, + ) -> BusInteraction> { + bus_interaction + } + } + + impl RangeConstraintHandler for SingleValueHandler { + fn pure_range_constraints( + &self, + bus_interaction: &BusInteraction>, + ) -> Option> { + let expr = bus_interaction.payload.first()?.clone(); + let value = bus_interaction.payload.get(1)?.try_to_number()?; + Some(vec![(expr, RangeConstraint::from_value(value))]) + } + + fn batch_make_range_constraints( + &self, + range_constraints: RangeConstraints, + ) -> Result>>, MakeRangeConstraintsError> + { + Ok(range_constraints + .into_iter() + .map(|(expr, _)| BusInteraction { + bus_id: GroupedExpression::from_number(T::from(0u64)), + multiplicity: GroupedExpression::from_number(T::one()), + payload: vec![expr], + }) + .collect()) + } + } + + fn single_value_bus_interaction( + var: Var, + value: u64, + ) -> BusInteraction> { + BusInteraction { + bus_id: GroupedExpression::from_number(T::from(0u64)), + multiplicity: GroupedExpression::from_number(T::one()), + payload: vec![ + GroupedExpression::from_unknown_variable(var), + GroupedExpression::from_number(T::from(value)), + ], + } + } + + fn always_false() -> AlgebraicConstraint> { + AlgebraicConstraint::assert_zero(GroupedExpression::from_number(T::one())) + } + + fn run(system: ConstraintSystem) -> ConstraintSystem { + optimize_range_constraints( + system, + SingleValueHandler, + DegreeBound { + identities: 100, + bus_interactions: 100, + }, + ) + } + + /// Two unconditional pure range constraints force `x` to be both 1 and 2, so the system is + /// unsatisfiable. The optimizer must not relax this into a satisfiable `x = 0`. + #[test] + fn contradictory_pure_range_constraints_stay_unsatisfiable() { + let system = ConstraintSystem:: { + bus_interactions: vec![ + single_value_bus_interaction("x", 1), + single_value_bus_interaction("x", 2), + ], + ..Default::default() + }; + let optimized = run(system); + assert!( + optimized.algebraic_constraints.contains(&always_false()), + "contradiction must be preserved as an unsatisfiable constraint, not relaxed" + ); + } + + /// Two compatible constraints (`x == 1` twice) must not be flagged as a contradiction. + #[test] + fn compatible_pure_range_constraints_are_not_flagged() { + let system = ConstraintSystem:: { + bus_interactions: vec![ + single_value_bus_interaction("x", 1), + single_value_bus_interaction("x", 1), + ], + ..Default::default() + }; + let optimized = run(system); + assert!( + !optimized.algebraic_constraints.contains(&always_false()), + "compatible constraints must not be treated as a contradiction" + ); + } +}