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

Lines changed: 97 additions & 57 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 10 additions & 10 deletions
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

Lines changed: 4 additions & 4 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy