Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.

Commit 67ca845

Browse files
committed
Ensure return statements don't bypass finally
Instead of translating a Python return directly into a Go return statement, set a reserved variable πR to the return value and continue up the checkpoint stack. When a function is done and it's not a propagating an exception it will return πR. This fixes #364
1 parent c9311c0 commit 67ca845

6 files changed

Lines changed: 77 additions & 43 deletions

File tree

compiler/stmt.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def visit_ClassDef(self, node):
139139
self.writer.write_temp_decls(body_visitor.block)
140140
self.writer.write_block(body_visitor.block,
141141
body_visitor.writer.getvalue())
142+
self.writer.write('return nil, nil')
142143
tmpl = textwrap.dedent("""\
143144
}).Eval(πF, πF.Globals(), nil, nil)
144145
if πE != nil {
@@ -355,9 +356,8 @@ def visit_Return(self, node):
355356
raise util.ParseError(node, 'returning a value in a generator function')
356357
if node.value:
357358
with self.visit_expr(node.value) as value:
358-
self.writer.write('return {}, nil'.format(value.expr))
359-
else:
360-
self.writer.write('return nil, nil')
359+
self.writer.write('πR = {}'.format(value.expr))
360+
self.writer.write('continue')
361361

362362
def visit_Try(self, node):
363363
# The general structure generated by this method is shown below:
@@ -404,39 +404,38 @@ def visit_Try(self, node):
404404

405405
with self.block.alloc_temp('*πg.BaseException') as exc:
406406
if except_label:
407-
if (len(node.handlers) == 1 and not node.handlers[0].type and
408-
not node.orelse):
409-
# When there's just a bare except, no dispatch is required.
410-
self._write_except_block(except_label, exc.expr, node.handlers[0])
407+
with self.block.alloc_temp('*πg.Traceback') as tb:
408+
self.writer.write_label(except_label)
409+
self.writer.write_tmpl(textwrap.dedent("""\
410+
if πE == nil {
411+
continue
412+
}
413+
πE = nil
414+
$exc, $tb = πF.ExcInfo()"""), exc=exc.expr, tb=tb.expr)
415+
handler_labels = self._write_except_dispatcher(
416+
exc.expr, tb.expr, node.handlers)
417+
418+
# Write the bodies of each of the except handlers.
419+
for handler_label, except_node in zip(handler_labels, node.handlers):
420+
self._write_except_block(handler_label, exc.expr, except_node)
411421
if node.finalbody:
412422
self.writer.write('πF.PopCheckpoint()') # finally_label
413423
self.writer.write('goto Label{}'.format(finally_label))
414-
else:
415-
with self.block.alloc_temp('*πg.Traceback') as tb:
416-
self.writer.write_label(except_label)
417-
self.writer.write('{}, {} = πF.ExcInfo()'.format(exc.expr, tb.expr))
418-
handler_labels = self._write_except_dispatcher(
419-
exc.expr, tb.expr, node.handlers)
420-
421-
# Write the bodies of each of the except handlers.
422-
for handler_label, except_node in zip(handler_labels, node.handlers):
423-
self._write_except_block(handler_label, exc.expr, except_node)
424-
if node.finalbody:
425-
self.writer.write('πF.PopCheckpoint()') # finally_label
426-
self.writer.write('goto Label{}'.format(finally_label))
427424

428425
# Write the finally body.
429426
self.writer.write_label(finally_label)
430427
if node.finalbody:
431428
with self.block.alloc_temp('*πg.Traceback') as tb:
432-
self.writer.write('πE = nil')
433429
self.writer.write('{}, {} = πF.RestoreExc(nil, nil)'.format(
434430
exc.expr, tb.expr))
435431
self._visit_each(node.finalbody)
436432
self.writer.write_tmpl(textwrap.dedent("""\
437433
if $exc != nil {
438434
\tπE = πF.Raise($exc.ToObject(), nil, $tb.ToObject())
439435
\tcontinue
436+
}
437+
if πR != nil {
438+
\tcontinue
440439
}"""), exc=exc.expr, tb=tb.expr)
441440

442441
def visit_While(self, node):
@@ -532,6 +531,9 @@ def visit_With(self, node):
532531
if $exc != nil && $swallow_exc != true {
533532
\tπE = πF.Raise(nil, nil, nil)
534533
\tcontinue
534+
}
535+
if πR != nil {
536+
\tcontinue
535537
}"""), exc=exc.expr, swallow_exc=swallow_exc_bool.expr)
536538

537539
def visit_function_inline(self, node):
@@ -584,14 +586,25 @@ def visit_function_inline(self, node):
584586
self.writer.write(fmt.format(
585587
util.adjust_local_name(var.name), var.init_expr))
586588
self.writer.write_temp_decls(func_block)
589+
self.writer.write('var πR *πg.Object; _ = πR')
590+
self.writer.write('var πE *πg.BaseException; _ = πE')
587591
if func_block.is_generator:
588-
self.writer.write('return πg.NewGenerator(πF, func(πSent *πg.Object) '
589-
'(*πg.Object, *πg.BaseException) {')
592+
self.writer.write(
593+
'return πg.NewGenerator(πF, func(πSent *πg.Object) '
594+
'(*πg.Object, *πg.BaseException) {')
590595
with self.writer.indent_block():
591596
self.writer.write_block(func_block, visitor.writer.getvalue())
597+
self.writer.write('return nil, πE')
592598
self.writer.write('}).ToObject(), nil')
593599
else:
594600
self.writer.write_block(func_block, visitor.writer.getvalue())
601+
self.writer.write(textwrap.dedent("""\
602+
if πE != nil {
603+
\tπR = nil
604+
} else if πR == nil {
605+
\tπR = πg.None
606+
}
607+
return πR, πE"""))
595608
self.writer.write('}), πF.Globals()).ToObject()')
596609
return result
597610

@@ -733,7 +746,6 @@ def _write_except_block(self, label, exc, except_node):
733746
self.block.bind_var(self.writer, except_node.name.id,
734747
'{}.ToObject()'.format(exc))
735748
self._visit_each(except_node.body)
736-
self.writer.write('πE = nil')
737749
self.writer.write('πF.RestoreExc(nil, nil)')
738750

739751
def _write_except_dispatcher(self, exc, tb, handlers):

compiler/util.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ def write_block(self, block_, body):
8787
block_: The Block object representing the code block.
8888
body: String containing Go code making up the body of the code block.
8989
"""
90-
self.write('var πE *πg.BaseException; _ = πE')
9190
self.write('for ; πF.State() >= 0; πF.PopCheckpoint() {')
9291
with self.indent_block():
9392
self.write('switch πF.State() {')
@@ -99,9 +98,7 @@ def write_block(self, block_, body):
9998
# Assume that body is aligned with goto labels.
10099
with self.indent_block(-1):
101100
self.write(body)
102-
self.write('return nil, nil')
103101
self.write('}')
104-
self.write('return nil, πE')
105102

106103
def write_import_block(self, imports):
107104
if not imports:

compiler/util_test.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ def testWriteBlock(self):
4343
output = writer.getvalue()
4444
dispatch = 'switch πF.State() {\n\tcase 0:\n\tdefault: panic'
4545
self.assertIn(dispatch, output)
46-
self.assertIn('return nil, nil\n}', output)
4746

4847
def testWriteImportBlockEmptyImports(self):
4948
writer = util.Writer()

testing/try_test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,15 @@ def f():
220220

221221

222222
foo()
223+
224+
225+
# Return statement should not bypass the finally.
226+
def foo():
227+
try:
228+
return 1
229+
finally:
230+
return 2
231+
return 3
232+
233+
234+
assert foo() == 2

testing/with_test.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,30 @@ def __exit__(self, *args):
182182
assert j == 3
183183

184184

185-
# This checks for a bug where a with clause inside an except body raises an
186-
# exception because it was checking ExcInfo() to determine whether an exception
187-
# occurred.
188185
class Foo(object):
186+
exited = False
189187
def __enter__(self):
190188
pass
191189
def __exit__(self, *args):
192-
pass
190+
self.exited = True
191+
192+
193+
# This checks for a bug where a with clause inside an except body raises an
194+
# exception because it was checking ExcInfo() to determine whether an exception
195+
# occurred.
193196
try:
194197
raise AssertionError
195198
except:
196-
with Foo():
199+
foo = Foo()
200+
with foo:
197201
pass
202+
assert foo.exited
203+
204+
205+
# Return statement should not bypass the with exit handler.
206+
foo = Foo()
207+
def bar():
208+
with foo:
209+
return
210+
bar()
211+
assert foo.exited

tools/grumpc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,22 @@ def main(args):
8383
package_name = args.modname.split('.')[-1]
8484
writer.write('package {}'.format(package_name))
8585
writer.write_import_block(mod_block.imports)
86-
87-
writer.write('func initModule(πF *πg.Frame, '
88-
'_ []*πg.Object) (*πg.Object, *πg.BaseException) {')
89-
with writer.indent_block():
86+
writer.write_tmpl(textwrap.dedent("""\
87+
var Code *πg.Code
88+
func init() {
89+
\tCode = πg.NewCode("<module>", $script, nil, 0, func(πF *πg.Frame, _ []*πg.Object) (*πg.Object, *πg.BaseException) {
90+
\t\tvar πR *πg.Object; _ = πR
91+
\t\tvar πE *πg.BaseException; _ = πE"""), script=util.go_str(args.script))
92+
with writer.indent_block(2):
9093
for s in sorted(mod_block.strings):
9194
writer.write('ß{} := πg.InternStr({})'.format(s, util.go_str(s)))
9295
writer.write_temp_decls(mod_block)
9396
writer.write_block(mod_block, visitor.writer.getvalue())
94-
writer.write('}')
95-
writer.write('var Code *πg.Code')
96-
9797
writer.write_tmpl(textwrap.dedent("""\
98-
func init() {
99-
\tCode = πg.NewCode("<module>", $script, nil, 0, initModule)
98+
\t\treturn nil, πE
99+
\t})
100100
\tπg.RegisterModule($modname, Code)
101-
}"""), script=util.go_str(args.script), modname=util.go_str(args.modname))
101+
}"""), modname=util.go_str(args.modname))
102102
return 0
103103

104104

0 commit comments

Comments
 (0)