Skip to content

Commit 3d425a8

Browse files
committed
vm: fix exception handling
1 parent 4865ebf commit 3d425a8

16 files changed

+274
-182
lines changed

vm/eval.go

+97-57
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Evaluate opcodes
22
package vm
33

4+
// FIXME make opcode its own type so can stringer
5+
46
// FIXME use LocalVars instead of storing everything in the Locals dict
57
// see frameobject.c dict_to_map and LocalsToFast
68

@@ -34,6 +36,7 @@ objects so they can be GCed
3436
*/
3537

3638
import (
39+
"fmt"
3740
"runtime/debug"
3841
"strings"
3942

@@ -48,9 +51,13 @@ const (
4851
cannotCatchMsg = "catching '%s' that does not inherit from BaseException is not allowed"
4952
)
5053

54+
const debugging = false
55+
5156
// Debug print
5257
func debugf(format string, a ...interface{}) {
53-
// fmt.Printf(format, a...)
58+
if debugging {
59+
fmt.Printf(format, a...)
60+
}
5461
}
5562

5663
// Stack operations
@@ -108,43 +115,33 @@ func (vm *Vm) AddTraceback(exc *py.ExceptionInfo) {
108115
// The exception must be a valid exception instance (eg as returned by
109116
// py.MakeException)
110117
//
111-
// It sets vm.exc.* and sets vm.exit to exitException
118+
// It sets vm.curexc.* and sets vm.exit to exitException
112119
func (vm *Vm) SetException(exception py.Object) {
113-
vm.old_exc = vm.exc
114-
vm.exc.Value = exception
115-
vm.exc.Type = exception.Type()
116-
vm.exc.Traceback = nil
117-
vm.AddTraceback(&vm.exc)
120+
vm.curexc.Value = exception
121+
vm.curexc.Type = exception.Type()
122+
vm.curexc.Traceback = nil
123+
vm.AddTraceback(&vm.curexc)
118124
vm.exit = exitException
119125
}
120126

121-
// Clears the current exception
122-
//
123-
// Doesn't adjust the exit code
124-
func (vm *Vm) ClearException() {
125-
// Clear the exception
126-
vm.exc.Type = nil
127-
vm.exc.Value = nil
128-
vm.exc.Traceback = nil
129-
}
130-
131127
// Check for an exception (panic)
132128
//
133129
// Should be called with the result of recover
134130
func (vm *Vm) CheckExceptionRecover(r interface{}) {
135131
// If what was raised was an ExceptionInfo the stuff this into the current vm
136132
if exc, ok := r.(py.ExceptionInfo); ok {
137-
vm.old_exc = vm.exc
138-
vm.exc = exc
139-
vm.AddTraceback(&vm.exc)
133+
vm.curexc = exc
134+
vm.AddTraceback(&vm.curexc)
140135
vm.exit = exitException
141136
debugf("*** Propagating exception: %s\n", exc.Error())
142137
} else {
143138
// Coerce whatever was raised into a *Exception
144139
vm.SetException(py.MakeException(r))
145140
debugf("*** Exception raised %v\n", r)
146141
// Dump the goroutine stack
147-
debug.PrintStack()
142+
if debugging {
143+
debug.PrintStack()
144+
}
148145
}
149146
}
150147

@@ -153,24 +150,11 @@ func (vm *Vm) CheckExceptionRecover(r interface{}) {
153150
// Must be called as a defer function
154151
func (vm *Vm) CheckException() {
155152
if r := recover(); r != nil {
153+
debugf("*** Panic recovered %v\n", r)
156154
vm.CheckExceptionRecover(r)
157155
}
158156
}
159157

160-
// Checks if r is StopIteration and if so returns true
161-
//
162-
// Otherwise deals with the as per vm.CheckException and returns false
163-
func (vm *Vm) catchStopIteration(r interface{}) bool {
164-
if py.IsException(py.StopIteration, r) {
165-
// StopIteration or subclass raises
166-
return true
167-
} else {
168-
// Deal with the exception as normal
169-
vm.CheckExceptionRecover(r)
170-
}
171-
return false
172-
}
173-
174158
// Illegal instruction
175159
func do_ILLEGAL(vm *Vm, arg int32) {
176160
defer vm.CheckException()
@@ -722,9 +706,13 @@ func do_POP_EXCEPT(vm *Vm, arg int32) {
722706
func do_END_FINALLY(vm *Vm, arg int32) {
723707
defer vm.CheckException()
724708
v := vm.POP()
725-
debugf("END_FINALLY v=%v\n", v)
726-
if vInt, ok := v.(py.Int); ok {
709+
debugf("END_FINALLY v=%#v\n", v)
710+
if v == py.None {
711+
// None exception
712+
debugf(" END_FINALLY: None\n")
713+
} else if vInt, ok := v.(py.Int); ok {
727714
vm.exit = vmExit(vInt)
715+
debugf(" END_FINALLY: Int %v\n", vm.exit)
728716
switch vm.exit {
729717
case exitYield:
730718
panic("Unexpected exitYield in END_FINALLY")
@@ -749,15 +737,14 @@ func do_END_FINALLY(vm *Vm, arg int32) {
749737
} else if py.ExceptionClassCheck(v) {
750738
w := vm.POP()
751739
u := vm.POP()
740+
debugf(" END_FINALLY: Exc %v, Type %v, Traceback %v\n", v, w, u)
752741
// FIXME PyErr_Restore(v, w, u)
753-
vm.exc.Type = v.(*py.Type)
754-
vm.exc.Value = w
755-
vm.exc.Traceback = u.(*py.Traceback)
756-
vm.exit = exitReraise
757-
} else if v != py.None {
758-
vm.SetException(py.ExceptionNewf(py.SystemError, "'finally' pops bad exception %#v", v))
742+
vm.curexc.Type, _ = v.(*py.Type)
743+
vm.curexc.Value = w
744+
vm.curexc.Traceback, _ = u.(*py.Traceback)
745+
vm.exit = exitException
759746
} else {
760-
vm.ClearException()
747+
vm.SetException(py.ExceptionNewf(py.SystemError, "'finally' pops bad exception %#v", v))
761748
}
762749
debugf("END_FINALLY: vm.exit = %v\n", vm.exit)
763750
}
@@ -1269,8 +1256,11 @@ func (vm *Vm) raise(exc, cause py.Object) {
12691256
if !vm.exc.IsSet() {
12701257
vm.SetException(py.ExceptionNewf(py.RuntimeError, "No active exception to reraise"))
12711258
} else {
1259+
// Resignal the exception
1260+
vm.curexc = vm.exc
12721261
// Signal the existing exception again
1273-
vm.exit = exitReraise
1262+
vm.exit = exitException
1263+
12741264
}
12751265
} else {
12761266
// raise <instance>
@@ -1504,9 +1494,9 @@ func (vm *Vm) UnwindExceptHandler(frame *py.Frame, block *py.TryBlock) {
15041494
frame.Stack = frame.Stack[:block.Level+3]
15051495
}
15061496
debugf("** UnwindExceptHandler stack depth now %v\n", vm.STACK_LEVEL())
1507-
vm.exc.Type = vm.POP().(*py.Type)
1497+
vm.exc.Type, _ = vm.POP().(*py.Type)
15081498
vm.exc.Value = vm.POP()
1509-
vm.exc.Traceback = vm.POP().(*py.Traceback)
1499+
vm.exc.Traceback, _ = vm.POP().(*py.Traceback)
15101500
debugf("** UnwindExceptHandler exc = (type: %v, value: %v, traceback: %v)\n", vm.exc.Type, vm.exc.Value, vm.exc.Traceback)
15111501
}
15121502

@@ -1565,6 +1555,9 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
15651555
// }
15661556
// }
15671557
}
1558+
if vm.exit == exitYield {
1559+
goto fast_yield
1560+
}
15681561

15691562
// Something exceptional has happened - unwind the block stack
15701563
// and find out what
@@ -1574,8 +1567,11 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
15741567
b := frame.Block
15751568
debugf("*** Unwinding %#v vm %#v\n", b, vm)
15761569

1577-
if vm.exit == exitYield {
1578-
return vm.result, nil
1570+
if b.Type == SETUP_LOOP && vm.exit == exitContinue {
1571+
vm.exit = exitNot
1572+
dest := vm.result.(py.Int)
1573+
frame.Lasti = int32(dest)
1574+
break
15791575
}
15801576

15811577
// Now we have to pop the block.
@@ -1593,18 +1589,25 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
15931589
frame.Lasti = b.Handler
15941590
break
15951591
}
1596-
if (vm.exit == exitException || vm.exit == exitReraise) && (b.Type == SETUP_EXCEPT || b.Type == SETUP_FINALLY) {
1592+
if vm.exit == exitException && (b.Type == SETUP_EXCEPT || b.Type == SETUP_FINALLY) {
15971593
debugf("*** Exception\n")
15981594
handler := b.Handler
15991595
// This invalidates b
16001596
frame.PushBlock(EXCEPT_HANDLER, -1, vm.STACK_LEVEL())
16011597
vm.PUSH(vm.exc.Traceback)
16021598
vm.PUSH(vm.exc.Value)
1603-
vm.PUSH(vm.exc.Type) // can be nil
1599+
if vm.exc.Type == nil {
1600+
vm.PUSH(py.None)
1601+
} else {
1602+
vm.PUSH(vm.exc.Type) // can be nil
1603+
}
16041604
// FIXME PyErr_Fetch(&exc, &val, &tb)
1605-
exc := vm.exc.Type
1606-
val := vm.exc.Value
1607-
tb := vm.exc.Traceback
1605+
exc := vm.curexc.Type
1606+
val := vm.curexc.Value
1607+
tb := vm.curexc.Traceback
1608+
vm.curexc.Type = nil
1609+
vm.curexc.Value = nil
1610+
vm.curexc.Traceback = nil
16081611
// Make the raw exception data
16091612
// available to the handler,
16101613
// so a program can emulate the
@@ -1616,7 +1619,11 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
16161619
vm.exc.Traceback = tb
16171620
vm.PUSH(tb)
16181621
vm.PUSH(val)
1619-
vm.PUSH(exc)
1622+
if exc == nil {
1623+
vm.PUSH(py.None)
1624+
} else {
1625+
vm.PUSH(exc)
1626+
}
16201627
vm.exit = exitNot
16211628
frame.Lasti = handler
16221629
break
@@ -1632,8 +1639,41 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
16321639
}
16331640
}
16341641
}
1635-
if vm.exc.IsSet() {
1636-
return vm.result, vm.exc
1642+
debugf("EXIT with %v\n", vm.exit)
1643+
if vm.exit != exitReturn {
1644+
vm.result = nil
1645+
}
1646+
if vm.result == nil && !vm.curexc.IsSet() {
1647+
panic("vm: no result or exception")
1648+
}
1649+
if vm.result != nil && vm.curexc.IsSet() {
1650+
panic("vm: result and exception")
1651+
}
1652+
1653+
fast_yield:
1654+
// FIXME
1655+
// if (co->co_flags & CO_GENERATOR) {
1656+
// /* The purpose of this block is to put aside the generator's exception
1657+
// state and restore that of the calling frame. If the current
1658+
// exception state is from the caller, we clear the exception values
1659+
// on the generator frame, so they are not swapped back in latter. The
1660+
// origin of the current exception state is determined by checking for
1661+
// except handler blocks, which we must be in iff a new exception
1662+
// state came into existence in this frame. (An uncaught exception
1663+
// would have why == WHY_EXCEPTION, and we wouldn't be here). */
1664+
// int i;
1665+
// for (i = 0; i < f->f_iblock; i++)
1666+
// if (f->f_blockstack[i].b_type == EXCEPT_HANDLER)
1667+
// break;
1668+
// if (i == f->f_iblock)
1669+
// /* We did not create this exception. */
1670+
// restore_and_clear_exc_state(tstate, f);
1671+
// else
1672+
// swap_exc_state(tstate, f);
1673+
// }
1674+
1675+
if vm.curexc.IsSet() {
1676+
return vm.result, vm.curexc
16371677
}
16381678
return vm.result, nil
16391679
}

vm/stringer.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ package vm
44

55
import "fmt"
66

7-
const _vmExit_name = "exitNotexitExceptionexitReraiseexitReturnexitBreakexitContinueexitYieldexitSilenced"
7+
const _vmExit_name = "exitNotexitExceptionexitReturnexitBreakexitContinueexitYieldexitSilenced"
88

9-
var _vmExit_index = [...]uint8{0, 7, 20, 31, 41, 50, 62, 71, 83}
9+
var _vmExit_index = [...]uint8{0, 7, 20, 30, 39, 51, 60, 72}
1010

1111
func (i vmExit) String() string {
1212
if i+1 >= vmExit(len(_vmExit_index)) {

vm/tests/README.md

+8
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@ These simple programs are designed to exercise the VM.
66
They should run with no errors raised.
77

88
They should also all run clean with python3.4
9+
10+
Set doc="string" before every test
11+
12+
Set doc="finished" at the end
13+
14+
If you want to test an error is raised properly, then set
15+
err=ErrorObject so the test runner can check the error was raised
16+
properly.

vm/tests/attr.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,23 @@ def __init__(self):
77
self.attr3 = 44
88
c = C()
99

10-
# Test LOAD_ATTR
10+
doc="Test LOAD_ATTR"
1111
assert c.attr1 == 42
1212
assert C.attr1 == 42
1313
assert c.attr2 == 43
1414
assert c.attr3 == 44
1515

16-
# Test DELETE_ATTR
16+
doc="Test DELETE_ATTR"
1717
del c.attr3
1818

19-
# FIXME - exception handling broken
20-
# ok = False
21-
# try:
22-
# c.attr3
23-
# except AttributeError:
24-
# ok = True
25-
# assert ok
19+
ok = False
20+
try:
21+
c.attr3
22+
except AttributeError:
23+
ok = True
24+
assert ok
2625

27-
# Test STORE_ATTR
26+
doc="Test STORE_ATTR"
2827
c.attr1 = 100
2928
c.attr2 = 101
3029
c.attr3 = 102
@@ -34,4 +33,5 @@ def __init__(self):
3433
assert c.attr3 == 102
3534

3635
# End with this
36+
doc="finished"
3737
finished = True

vm/tests/class.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env python3.4
22

3-
# Test class definitions
4-
3+
doc="Test class definitions"
54
class C1:
65
"Test 1"
76
def method1(self, x):
@@ -15,6 +14,7 @@ def method2(self, m2):
1514
assert c.method1(1) == 2
1615
assert c.method2(1) == 3
1716

17+
doc="Test class definitions 2"
1818
class C2:
1919
"Test 2"
2020
_VAR = 1
@@ -30,7 +30,7 @@ def method2(self, m2):
3030
assert c.method1(1) == 3
3131
assert c.method2(1) == 4
3232

33-
# CLASS_DEREF
33+
doc="CLASS_DEREF"
3434

3535
# FIXME corner cases in CLASS_DEREF
3636
def classderef(y):
@@ -47,4 +47,4 @@ def method1(self, x):
4747
assert c.method1(1) == 2
4848

4949
# End with this
50-
finished = True
50+
doc="finished"

0 commit comments

Comments
 (0)