|
| 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