Skip to content

Commit 831975f

Browse files
Copilotmballance
andcommitted
Fix potential IndexError in bounds propagators with empty domains
Co-authored-by: mballance <1340805+mballance@users.noreply.github.com>
1 parent c50ded3 commit 831975f

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

src/vsc/model/variable_bound_bounds_max_propagator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,9 @@ def __init__(self,
2323

2424
def max(self):
2525
# print("max: " + str(self.other.domain.range_l[-1][1]+self.offset))
26+
# Guard against empty domain
27+
if len(self.other.domain.range_l) == 0:
28+
# Return a very small value to force constraint failure
29+
return -(2**63)
2630
return (self.other.domain.range_l[-1][1]+self.offset)
2731

src/vsc/model/variable_bound_bounds_min_propagator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,9 @@ def __init__(self,
1919
other.add_propagator(self)
2020

2121
def min(self):
22+
# Guard against empty domain
23+
if len(self.other.domain.range_l) == 0:
24+
# Return a very large value to force constraint failure
25+
return 2**63
2226
return (self.other.domain.range_l[0][0]+self.offset)
2327

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
'''
2+
Created on 2026-02-03
3+
4+
Test cases for variable bound propagators with empty domains
5+
Tests to catch IndexError in bounds propagators
6+
'''
7+
import unittest
8+
from vsc_test_case import VscTestCase
9+
import vsc
10+
11+
class TestVariableBoundPropagators(VscTestCase):
12+
13+
def test_bounds_max_propagator_with_empty_domain(self):
14+
"""Test that bounds max propagator handles empty domains correctly"""
15+
16+
@vsc.randobj
17+
class C():
18+
def __init__(self):
19+
self.a = vsc.rand_uint8_t()
20+
self.b = vsc.rand_uint8_t()
21+
22+
@vsc.constraint
23+
def constr(self):
24+
# Create a scenario where b's domain becomes empty
25+
# and b is used to set bounds on a
26+
self.b < self.a
27+
self.a < 10
28+
self.b > 250 # Impossible: b > 250 and b < a < 10
29+
30+
c = C()
31+
with self.assertRaises(vsc.SolveFailure):
32+
c.randomize()
33+
34+
def test_bounds_min_propagator_with_empty_domain(self):
35+
"""Test that bounds min propagator handles empty domains correctly"""
36+
37+
@vsc.randobj
38+
class C():
39+
def __init__(self):
40+
self.a = vsc.rand_uint8_t()
41+
self.b = vsc.rand_uint8_t()
42+
43+
@vsc.constraint
44+
def constr(self):
45+
# Create a scenario where b's domain becomes empty
46+
# and b is used to set minimum bounds on a
47+
self.a > self.b
48+
self.b > 200
49+
self.b < 10 # Impossible: b > 200 and b < 10
50+
51+
c = C()
52+
with self.assertRaises(vsc.SolveFailure):
53+
c.randomize()
54+
55+
def test_chained_bounds_propagators_empty_domain(self):
56+
"""Test chained bounds propagators with empty intermediate domain"""
57+
58+
@vsc.randobj
59+
class C():
60+
def __init__(self):
61+
self.a = vsc.rand_uint8_t()
62+
self.b = vsc.rand_uint8_t()
63+
self.c = vsc.rand_uint8_t()
64+
65+
@vsc.constraint
66+
def constr(self):
67+
# Chain: a < b < c, but make b's domain empty
68+
self.a < self.b
69+
self.b < self.c
70+
self.b > 200
71+
self.b < 50 # Impossible for b
72+
73+
c = C()
74+
with self.assertRaises(vsc.SolveFailure):
75+
c.randomize()
76+
77+
def test_variable_comparison_with_rangelist_empty_domain(self):
78+
"""Test variable comparison combined with rangelist causing empty domain"""
79+
80+
@vsc.randobj
81+
class C():
82+
def __init__(self):
83+
self.a = vsc.rand_uint8_t()
84+
self.b = vsc.rand_uint8_t()
85+
86+
@vsc.constraint
87+
def constr(self):
88+
self.a > self.b
89+
self.b > 128
90+
91+
c = C()
92+
with self.assertRaises(vsc.SolveFailure):
93+
with c.randomize_with() as it:
94+
# This combined with b > 128 and a > b makes a's valid range very limited
95+
# Then the rangelist makes it impossible
96+
it.a in vsc.rangelist((0, 50))
97+
98+
def test_multiple_variable_bounds_empty_domain(self):
99+
"""Test multiple variable bounds creating empty domain"""
100+
101+
@vsc.randobj
102+
class C():
103+
def __init__(self):
104+
self.a = vsc.rand_uint8_t()
105+
self.b = vsc.rand_uint8_t()
106+
self.c = vsc.rand_uint8_t()
107+
108+
@vsc.constraint
109+
def constr(self):
110+
# Create circular-like impossible constraints
111+
self.a < self.b
112+
self.b < self.c
113+
self.c < self.a # Impossible: a < b < c < a
114+
115+
c = C()
116+
with self.assertRaises(vsc.SolveFailure):
117+
c.randomize()
118+
119+
def test_bounds_with_offset_empty_domain(self):
120+
"""Test bounds propagators with offset causing empty domain"""
121+
122+
@vsc.randobj
123+
class C():
124+
def __init__(self):
125+
self.a = vsc.rand_uint8_t()
126+
self.b = vsc.rand_uint8_t()
127+
128+
@vsc.constraint
129+
def constr(self):
130+
# a must equal b + 50, but both constrained to small range
131+
self.a == self.b + 50
132+
self.a < 30 # a must be < 30
133+
self.b > 100 # b must be > 100, so a must be > 150
134+
135+
c = C()
136+
with self.assertRaises(vsc.SolveFailure):
137+
c.randomize()
138+
139+
def test_variable_bounds_success_case(self):
140+
"""Verify variable bounds work correctly when constraints are solvable"""
141+
142+
@vsc.randobj
143+
class C():
144+
def __init__(self):
145+
self.a = vsc.rand_uint8_t()
146+
self.b = vsc.rand_uint8_t()
147+
148+
@vsc.constraint
149+
def constr(self):
150+
self.a > self.b
151+
self.b > 50
152+
self.b < 100
153+
154+
c = C()
155+
# This should succeed
156+
c.randomize()
157+
158+
# Verify the constraints are satisfied
159+
self.assertGreater(c.a, c.b)
160+
self.assertGreater(c.b, 50)
161+
self.assertLess(c.b, 100)

0 commit comments

Comments
 (0)