Skip to content

Commit e6d3b91

Browse files
committed
mlir: fix break/continue inside a try/with
1 parent 8bd37cd commit e6d3b91

3 files changed

Lines changed: 547 additions & 49 deletions

File tree

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# Regression test: `break`/`continue` that leaves a `try`/`except` or `with`
2+
# block inside a loop. These previously produced a `cf.br` from inside the
3+
# still-nested python.try/with region to a block in the enclosing loop region
4+
# (an invalid cross-region branch), which sent MLIR's region DCE into unbounded
5+
# recursion and crashed the compiler. os.py's removedirs()/_walk() hit this,
6+
# so `import os` segfaulted.
7+
8+
# break out of an except handler (while) -- the removedirs() shape.
9+
def break_in_except_while(items):
10+
out = []
11+
i = 0
12+
while i < 5:
13+
try:
14+
out.append(items[i])
15+
except IndexError:
16+
break
17+
i += 1
18+
return out
19+
20+
assert break_in_except_while([10, 20]) == [10, 20], break_in_except_while([10, 20])
21+
22+
# continue out of an except handler (for).
23+
def continue_in_except_for(items):
24+
out = []
25+
for x in items:
26+
try:
27+
if x == 0:
28+
raise ValueError("zero")
29+
out.append(x)
30+
except ValueError:
31+
continue
32+
return out
33+
34+
assert continue_in_except_for([1, 0, 2, 0, 5]) == [1, 2, 5], continue_in_except_for([1, 0, 2, 0, 5])
35+
36+
# break from the try body itself (not from a handler).
37+
def break_in_try_body():
38+
out = []
39+
i = 0
40+
while i < 10:
41+
try:
42+
out.append(i)
43+
if i == 3:
44+
break
45+
except Exception:
46+
out.append(-1)
47+
i += 1
48+
return out
49+
50+
assert break_in_try_body() == [0, 1, 2, 3], break_in_try_body()
51+
52+
# break/continue out of a `with` must still run __exit__.
53+
class CM:
54+
def __init__(self, log):
55+
self._log = log
56+
def __enter__(self):
57+
self._log.append("enter")
58+
return self
59+
def __exit__(self, *a):
60+
self._log.append("exit")
61+
return False
62+
63+
def break_out_of_with():
64+
log = []
65+
i = 0
66+
while i < 3:
67+
with CM(log):
68+
if i == 1:
69+
break
70+
i += 1
71+
return log
72+
73+
assert break_out_of_with() == ["enter", "exit", "enter", "exit"], break_out_of_with()
74+
75+
def continue_out_of_with():
76+
log = []
77+
for i in range(3):
78+
with CM(log):
79+
if i == 1:
80+
continue
81+
log.append(("after", i))
82+
return log
83+
84+
assert continue_out_of_with() == [
85+
"enter", "exit", ("after", 0),
86+
"enter", "exit",
87+
"enter", "exit", ("after", 2),
88+
], continue_out_of_with()
89+
90+
# nested try/except with break in the inner handler -- the _walk() shape.
91+
def nested_try_break(values):
92+
out = []
93+
it = iter(values)
94+
while True:
95+
try:
96+
try:
97+
v = next(it)
98+
except StopIteration:
99+
break
100+
except RuntimeError:
101+
out.append("runtime")
102+
continue
103+
out.append(v)
104+
return out
105+
106+
assert nested_try_break([1, 2, 3]) == [1, 2, 3], nested_try_break([1, 2, 3])
107+
108+
# break/continue out of a try must still run its finally first.
109+
def break_in_except_with_finally():
110+
out = []
111+
i = 0
112+
while i < 5:
113+
try:
114+
out.append(("body", i))
115+
raise ValueError
116+
except ValueError:
117+
out.append(("except", i))
118+
break
119+
finally:
120+
out.append(("finally", i))
121+
i += 1
122+
return out
123+
124+
assert break_in_except_with_finally() == [
125+
("body", 0), ("except", 0), ("finally", 0),
126+
], break_in_except_with_finally()
127+
128+
def continue_with_finally():
129+
out = []
130+
for i in range(3):
131+
try:
132+
if i == 1:
133+
raise ValueError
134+
out.append(("ok", i))
135+
except ValueError:
136+
continue
137+
finally:
138+
out.append(("fin", i))
139+
return out
140+
141+
assert continue_with_finally() == [
142+
("ok", 0), ("fin", 0), ("fin", 1), ("ok", 2), ("fin", 2),
143+
], continue_with_finally()
144+
145+
# both break and continue, each unwinding the same finally.
146+
def break_and_continue_with_finally():
147+
out = []
148+
for i in range(6):
149+
try:
150+
if i == 1:
151+
continue
152+
if i == 4:
153+
break
154+
out.append(("ok", i))
155+
finally:
156+
out.append(("fin", i))
157+
return out
158+
159+
assert break_and_continue_with_finally() == [
160+
("ok", 0), ("fin", 0),
161+
("fin", 1),
162+
("ok", 2), ("fin", 2),
163+
("ok", 3), ("fin", 3),
164+
("fin", 4),
165+
], break_and_continue_with_finally()
166+
167+
# break from the try body (no exception raised) still runs finally.
168+
def break_in_body_with_finally():
169+
out = []
170+
i = 0
171+
while i < 5:
172+
try:
173+
out.append(("body", i))
174+
if i == 2:
175+
break
176+
finally:
177+
out.append(("fin", i))
178+
i += 1
179+
return out
180+
181+
assert break_in_body_with_finally() == [
182+
("body", 0), ("fin", 0),
183+
("body", 1), ("fin", 1),
184+
("body", 2), ("fin", 2),
185+
], break_in_body_with_finally()
186+
187+
# break through *nested* try/finally runs every finally, innermost first.
188+
def break_through_nested_finally():
189+
out = []
190+
i = 0
191+
while i < 4:
192+
try:
193+
try:
194+
out.append(("body", i))
195+
if i == 1:
196+
break
197+
finally:
198+
out.append(("inner-fin", i))
199+
finally:
200+
out.append(("outer-fin", i))
201+
i += 1
202+
return out
203+
204+
assert break_through_nested_finally() == [
205+
("body", 0), ("inner-fin", 0), ("outer-fin", 0),
206+
("body", 1), ("inner-fin", 1), ("outer-fin", 1),
207+
], break_through_nested_finally()
208+
209+
# break written *inside* a finally exits the loop.
210+
def break_inside_finally():
211+
out = []
212+
for i in range(4):
213+
try:
214+
out.append(("body", i))
215+
finally:
216+
out.append(("fin", i))
217+
if i == 1:
218+
break
219+
return out
220+
221+
assert break_inside_finally() == [
222+
("body", 0), ("fin", 0), ("body", 1), ("fin", 1),
223+
], break_inside_finally()
224+
225+
# a break inside a finally swallows an exception that is in flight -- even one
226+
# raised by a called function.
227+
def boom():
228+
raise RuntimeError("from callee")
229+
230+
def break_inside_finally_swallows_exception():
231+
out = []
232+
for i in range(4):
233+
try:
234+
out.append(("body", i))
235+
if i == 1:
236+
boom()
237+
finally:
238+
out.append(("fin", i))
239+
if i == 1:
240+
break
241+
return out
242+
243+
assert break_inside_finally_swallows_exception() == [
244+
("body", 0), ("fin", 0), ("body", 1), ("fin", 1),
245+
], break_inside_finally_swallows_exception()
246+
247+
# a continue inside a finally overrides a break in the try body.
248+
def finally_continue_overrides_break():
249+
out = []
250+
for i in range(4):
251+
try:
252+
out.append(("body", i))
253+
if i == 1:
254+
break
255+
finally:
256+
out.append(("fin", i))
257+
if i == 1:
258+
continue
259+
return out
260+
261+
assert finally_continue_overrides_break() == [
262+
("body", 0), ("fin", 0),
263+
("body", 1), ("fin", 1),
264+
("body", 2), ("fin", 2),
265+
("body", 3), ("fin", 3),
266+
], finally_continue_overrides_break()
267+
268+
# a break inside an inner finally still runs the enclosing finally.
269+
def break_inside_inner_finally():
270+
out = []
271+
for i in range(3):
272+
try:
273+
try:
274+
out.append(("inner-body", i))
275+
finally:
276+
out.append(("inner-fin", i))
277+
if i == 1:
278+
break
279+
finally:
280+
out.append(("outer-fin", i))
281+
return out
282+
283+
assert break_inside_inner_finally() == [
284+
("inner-body", 0), ("inner-fin", 0), ("outer-fin", 0),
285+
("inner-body", 1), ("inner-fin", 1), ("outer-fin", 1),
286+
], break_inside_inner_finally()
287+
288+
print("break_continue_in_try: ok")

0 commit comments

Comments
 (0)