Skip to content

Commit 9b8d487

Browse files
committed
Make break statements trigger finally blocks
Similar to google#368, fixes an issue where break statements were bypassing finally clauses. This change changes for and while loops to use the checkpoint stack instead of plain goto statements so that break can properly trigger finally handlers.
1 parent e19dab7 commit 9b8d487

File tree

4 files changed

+92
-62
lines changed

4 files changed

+92
-62
lines changed

compiler/block.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ def __init__(self, name, alias=None):
4545
class Loop(object):
4646
"""Represents a for or while loop within a particular block."""
4747

48-
def __init__(self, start_label, end_label):
49-
self.start_label = start_label
50-
self.end_label = end_label
48+
def __init__(self, breakvar):
49+
self.breakvar = breakvar
5150

5251

5352
class Block(object):
@@ -124,8 +123,8 @@ def free_temp(self, v):
124123
self.used_temps.remove(v)
125124
self.free_temps.add(v)
126125

127-
def push_loop(self):
128-
loop = Loop(self.genlabel(), self.genlabel())
126+
def push_loop(self, breakvar):
127+
loop = Loop(breakvar)
129128
self.loop_stack.append(loop)
130129
return loop
131130

compiler/block_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ class BlockTest(unittest.TestCase):
4343

4444
def testLoop(self):
4545
b = _MakeModuleBlock()
46-
loop = b.push_loop()
46+
loop = b.push_loop(None)
4747
self.assertEqual(loop, b.top_loop())
48-
inner_loop = b.push_loop()
48+
inner_loop = b.push_loop(None)
4949
self.assertEqual(inner_loop, b.top_loop())
5050
b.pop_loop()
5151
self.assertEqual(loop, b.top_loop())

compiler/stmt.py

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ def visit_Break(self, node):
9393
if not self.block.loop_stack:
9494
raise util.ParseError(node, "'break' not in loop")
9595
self._write_py_context(node.lineno)
96-
self.writer.write('goto Label{}'.format(self.block.top_loop().end_label))
96+
self.writer.write_tmpl(textwrap.dedent("""\
97+
$breakvar = true
98+
continue"""), breakvar=self.block.top_loop().breakvar.name)
9799

98100
def visit_ClassDef(self, node):
99101
# Since we only care about global vars, we end up throwing away the locals
@@ -166,7 +168,7 @@ def visit_Continue(self, node):
166168
if not self.block.loop_stack:
167169
raise util.ParseError(node, "'continue' not in loop")
168170
self._write_py_context(node.lineno)
169-
self.writer.write('goto Label{}'.format(self.block.top_loop().start_label))
171+
self.writer.write('continue')
170172

171173
def visit_Delete(self, node):
172174
self._write_py_context(node.lineno)
@@ -192,40 +194,27 @@ def visit_Expr(self, node):
192194
self.visit_expr(node.value).free()
193195

194196
def visit_For(self, node):
195-
loop = self.block.push_loop()
196-
orelse_label = self.block.genlabel() if node.orelse else loop.end_label
197-
self._write_py_context(node.lineno)
198-
with self.visit_expr(node.iter) as iter_expr, \
199-
self.block.alloc_temp() as i, \
200-
self.block.alloc_temp() as n:
201-
self.writer.write_checked_call2(i, 'πg.Iter(πF, {})', iter_expr.expr)
202-
self.writer.write_label(loop.start_label)
203-
tmpl = textwrap.dedent("""\
204-
if $n, πE = πg.Next(πF, $i); πE != nil {
205-
\tisStop, exc := πg.IsInstance(πF, πE.ToObject(), πg.StopIterationType.ToObject())
206-
\tif exc != nil {
207-
\t\tπE = exc
208-
\t\tcontinue
209-
\t}
210-
\tif !isStop {
211-
\t\tcontinue
212-
\t}
213-
\tπE = nil
214-
\tπF.RestoreExc(nil, nil)
215-
\tgoto Label$orelse
216-
}""")
217-
self.writer.write_tmpl(tmpl, n=n.name, i=i.expr, orelse=orelse_label)
218-
self._tie_target(node.target, n.expr)
219-
self._visit_each(node.body)
220-
self.writer.write('goto Label{}'.format(loop.start_label))
221-
222-
self.block.pop_loop()
223-
if node.orelse:
224-
self.writer.write_label(orelse_label)
225-
self._visit_each(node.orelse)
226-
# Avoid label "defined and not used" in case there's no break statements.
227-
self.writer.write('goto Label{}'.format(loop.end_label))
228-
self.writer.write_label(loop.end_label)
197+
with self.block.alloc_temp() as i:
198+
with self.visit_expr(node.iter) as iter_expr:
199+
self.writer.write_checked_call2(i, 'πg.Iter(πF, {})', iter_expr.expr)
200+
def testfunc(testvar):
201+
with self.block.alloc_temp() as n:
202+
self.writer.write_tmpl(textwrap.dedent("""\
203+
if $n, πE = πg.Next(πF, $i); πE != nil {
204+
\tisStop, exc := πg.IsInstance(πF, πE.ToObject(), πg.StopIterationType.ToObject())
205+
\tif exc != nil {
206+
\t\tπE = exc
207+
\t} else if isStop {
208+
\t\tπE = nil
209+
\t\tπF.RestoreExc(nil, nil)
210+
\t}
211+
\t$testvar = !isStop
212+
} else {
213+
\t$testvar = true"""), n=n.name, i=i.expr, testvar=testvar.name)
214+
with self.writer.indent_block():
215+
self._tie_target(node.target, n.expr)
216+
self.writer.write('}')
217+
self._visit_loop(testfunc, node)
229218

230219
def visit_FunctionDef(self, node):
231220
self._write_py_context(node.lineno + len(node.decorator_list))
@@ -357,6 +346,8 @@ def visit_Return(self, node):
357346
if node.value:
358347
with self.visit_expr(node.value) as value:
359348
self.writer.write('πR = {}'.format(value.expr))
349+
else:
350+
self.writer.write('πR = πg.None')
360351
self.writer.write('continue')
361352

362353
def visit_Try(self, node):
@@ -439,26 +430,12 @@ def visit_Try(self, node):
439430
}"""), exc=exc.expr, tb=tb.expr)
440431

441432
def visit_While(self, node):
442-
loop = self.block.push_loop()
443433
self._write_py_context(node.lineno)
444-
self.writer.write_label(loop.start_label)
445-
orelse_label = self.block.genlabel() if node.orelse else loop.end_label
446-
with self.visit_expr(node.test) as cond,\
447-
self.block.alloc_temp('bool') as is_true:
448-
self.writer.write_checked_call2(is_true, 'πg.IsTrue(πF, {})', cond.expr)
449-
self.writer.write_tmpl(textwrap.dedent("""\
450-
if !$is_true {
451-
\tgoto Label$orelse_label
452-
}"""), is_true=is_true.expr, orelse_label=orelse_label)
453-
self._visit_each(node.body)
454-
self.writer.write('goto Label{}'.format(loop.start_label))
455-
if node.orelse:
456-
self.writer.write_label(orelse_label)
457-
self._visit_each(node.orelse)
458-
# Avoid label "defined and not used" in case there's no break statements.
459-
self.writer.write('goto Label{}'.format(loop.end_label))
460-
self.writer.write_label(loop.end_label)
461-
self.block.pop_loop()
434+
def testfunc(testvar):
435+
with self.visit_expr(node.test) as cond:
436+
self.writer.write_checked_call2(
437+
testvar, 'πg.IsTrue(πF, {})', cond.expr)
438+
self._visit_loop(testfunc, node)
462439

463440
def visit_With(self, node):
464441
assert len(node.items) == 1, 'multiple items in a with not yet supported'
@@ -739,6 +716,44 @@ def _visit_each(self, nodes):
739716
for node in nodes:
740717
self.visit(node)
741718

719+
def _visit_loop(self, testfunc, node):
720+
start_label = self.block.genlabel(is_checkpoint=True)
721+
else_label = self.block.genlabel(is_checkpoint=True)
722+
end_label = self.block.genlabel()
723+
with self.block.alloc_temp('bool') as breakvar:
724+
self.block.push_loop(breakvar)
725+
self.writer.write('πF.PushCheckpoint({})'.format(else_label))
726+
self.writer.write('{} = false'.format(breakvar.name))
727+
self.writer.write_label(start_label)
728+
self.writer.write_tmpl(textwrap.dedent("""\
729+
if πE != nil || πR != nil {
730+
\tcontinue
731+
}
732+
if $breakvar {
733+
\tπF.PopCheckpoint()
734+
\tgoto Label$end_label
735+
}"""), breakvar=breakvar.expr, end_label=end_label)
736+
with self.block.alloc_temp('bool') as testvar:
737+
testfunc(testvar)
738+
self.writer.write_tmpl(textwrap.dedent("""\
739+
if πE != nil || !$testvar {
740+
\tcontinue
741+
}
742+
πF.PushCheckpoint($start_label)\
743+
"""), testvar=testvar.name, start_label=start_label)
744+
self._visit_each(node.body)
745+
self.writer.write('continue')
746+
# End the loop so that break applies to an outer loop if present.
747+
self.block.pop_loop()
748+
self.writer.write_label(else_label)
749+
self.writer.write(textwrap.dedent("""\
750+
if πE != nil || πR != nil {
751+
\tcontinue
752+
}"""))
753+
if node.orelse:
754+
self._visit_each(node.orelse)
755+
self.writer.write_label(end_label)
756+
742757
def _write_except_block(self, label, exc, except_node):
743758
self._write_py_context(except_node.lineno)
744759
self.writer.write_label(label)

testing/try_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,19 @@ def foo():
232232

233233

234234
assert foo() == 2
235+
236+
237+
# Break statement should not bypass finally.
238+
x = []
239+
def foo():
240+
while True:
241+
try:
242+
x.append(1)
243+
break
244+
finally:
245+
x.append(2)
246+
x.append(3)
247+
248+
249+
foo()
250+
assert x == [1, 2, 3]

0 commit comments

Comments
 (0)