Skip to content

Commit 0d4a6d4

Browse files
committed
vm: tests and fixes for exceptions
* Add IsSet method to ExceptionInfo and use it to fix bad tests * Correct raise handling * Correct exception handling * Add finished marker to the tests to detect silent early exit * Still some exceptions tests failing
1 parent 5ceac9c commit 0d4a6d4

File tree

10 files changed

+249
-37
lines changed

10 files changed

+249
-37
lines changed

py/exception.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ func (exc *ExceptionInfo) TracebackDump(w io.Writer) {
119119
fmt.Fprintf(w, "%v: %v\n", exc.Type.Name, exc.Value)
120120
}
121121

122+
// Test for being set
123+
func (exc *ExceptionInfo) IsSet() bool {
124+
return exc.Type != nil
125+
}
126+
122127
// ExceptionNew
123128
func ExceptionNew(metatype *Type, args Tuple, kwargs StringDict) Object {
124129
if len(kwargs) != 0 {
@@ -229,7 +234,7 @@ func ExceptionGivenMatches(err, exc Object) bool {
229234

230235
// Test the tuple case recursively
231236
if excTuple, ok := exc.(Tuple); ok {
232-
for i := 0; i < len(excTuple); i++ {
237+
for i := range excTuple {
233238
if ExceptionGivenMatches(err, excTuple[i]) {
234239
return true
235240
}

vm/eval.go

+16-20
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,8 @@ func do_END_FINALLY(vm *Vm, arg int32) {
660660
switch vm.exit {
661661
case exitYield:
662662
panic("Unexpected exitYield in END_FINALLY")
663+
case exitException:
664+
panic("Unexpected exitException in END_FINALLY")
663665
case exitReturn, exitContinue:
664666
vm.result = vm.POP()
665667
case exitSilenced:
@@ -689,7 +691,7 @@ func do_END_FINALLY(vm *Vm, arg int32) {
689691
} else {
690692
vm.ClearException()
691693
}
692-
694+
debugf("END_FINALLY: vm.exit = %v\n", vm.exit)
693695
}
694696

695697
// Loads the __build_class__ helper function to the stack which
@@ -1201,7 +1203,7 @@ func do_DELETE_DEREF(vm *Vm, i int32) {
12011203
func (vm *Vm) raise(exc, cause py.Object) {
12021204
if exc == nil {
12031205
// raise (with no parameters == re-raise)
1204-
if vm.exc.Value == nil {
1206+
if !vm.exc.IsSet() {
12051207
vm.SetException(py.ExceptionNewf(py.RuntimeError, "No active exception to reraise"))
12061208
} else {
12071209
// Signal the existing exception again
@@ -1211,6 +1213,7 @@ func (vm *Vm) raise(exc, cause py.Object) {
12111213
// raise <instance>
12121214
// raise <type>
12131215
excException := py.MakeException(exc)
1216+
debugf("raise: excException = %v\n", excException)
12141217
vm.SetException(excException)
12151218
if cause != nil {
12161219
excException.Cause = py.MakeException(cause)
@@ -1431,24 +1434,17 @@ func (vm *Vm) UnwindBlock(frame *py.Frame, block *py.TryBlock) {
14311434

14321435
// Unwinds the stack in the presence of an exception
14331436
func (vm *Vm) UnwindExceptHandler(frame *py.Frame, block *py.TryBlock) {
1437+
debugf("** UnwindExceptHandler stack depth %v\n", vm.STACK_LEVEL())
14341438
if vm.STACK_LEVEL() < block.Level+3 {
14351439
panic("Couldn't find traceback on stack")
14361440
} else {
14371441
frame.Stack = frame.Stack[:block.Level+3]
14381442
}
1439-
// If have just raised an exception, don't overwrite it
1440-
//
1441-
// FIXME if have two exceptions python shows both tracebacks
1442-
//
1443-
// FIXME this is a departure from python's way not sure it is
1444-
// correct
1445-
if vm.exc.Value != nil {
1446-
vm.DROPN(3)
1447-
} else {
1448-
vm.exc.Type = vm.POP().(*py.Type)
1449-
vm.exc.Value = vm.POP()
1450-
vm.exc.Traceback = vm.POP().(*py.Traceback)
1451-
}
1443+
debugf("** UnwindExceptHandler stack depth now %v\n", vm.STACK_LEVEL())
1444+
vm.exc.Type = vm.POP().(*py.Type)
1445+
vm.exc.Value = vm.POP()
1446+
vm.exc.Traceback = vm.POP().(*py.Traceback)
1447+
debugf("** UnwindExceptHandler exc = (type: %v, value: %v, traceback: %v)\n", vm.exc.Type, vm.exc.Value, vm.exc.Traceback)
14521448
}
14531449

14541450
// Run the virtual machine on a Frame object
@@ -1534,13 +1530,13 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
15341530
frame.Lasti = b.Handler
15351531
break
15361532
}
1537-
if vm.exit&(exitException|exitReraise) != 0 && (b.Type == SETUP_EXCEPT || b.Type == SETUP_FINALLY) {
1533+
if (vm.exit == exitException || vm.exit == exitReraise) && (b.Type == SETUP_EXCEPT || b.Type == SETUP_FINALLY) {
15381534
debugf("*** Exception\n")
15391535
handler := b.Handler
15401536
// This invalidates b
15411537
frame.PushBlock(EXCEPT_HANDLER, -1, vm.STACK_LEVEL())
1542-
vm.PUSH(vm.old_exc.Traceback)
1543-
vm.PUSH(vm.old_exc.Value)
1538+
vm.PUSH(vm.exc.Traceback)
1539+
vm.PUSH(vm.exc.Value)
15441540
vm.PUSH(vm.exc.Type) // can be nil
15451541
// FIXME PyErr_Fetch(&exc, &val, &tb)
15461542
exc := vm.exc.Type
@@ -1563,7 +1559,7 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
15631559
break
15641560
}
15651561
if b.Type == SETUP_FINALLY {
1566-
if vm.exit&(exitReturn|exitContinue) != 0 {
1562+
if vm.exit == exitReturn || vm.exit == exitContinue {
15671563
vm.PUSH(vm.result)
15681564
}
15691565
vm.PUSH(py.Int(vm.exit))
@@ -1573,7 +1569,7 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
15731569
}
15741570
}
15751571
}
1576-
if vm.exc.Value != nil {
1572+
if vm.exc.IsSet() {
15771573
return vm.result, vm.exc
15781574
}
15791575
return vm.result, nil

vm/stringer.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// generated by stringer -type=vmExit -output stringer.go; DO NOT EDIT
2+
3+
package vm
4+
5+
import "fmt"
6+
7+
const _vmExit_name = "exitNotexitExceptionexitReraiseexitReturnexitBreakexitContinueexitYieldexitSilenced"
8+
9+
var _vmExit_index = [...]uint8{0, 7, 20, 31, 41, 50, 62, 71, 83}
10+
11+
func (i vmExit) String() string {
12+
if i+1 >= vmExit(len(_vmExit_index)) {
13+
return fmt.Sprintf("vmExit(%d)", i)
14+
}
15+
return _vmExit_name[_vmExit_index[i]:_vmExit_index[i+1]]
16+
}

vm/tests/class.py

+2
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ def method1(self, x):
4646
c = x()
4747
assert c.method1(1) == 2
4848

49+
# End with this
50+
finished = True

vm/tests/comprehensions.py

+3
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@
2525
A = {"a":1, "b":2, "c":3}
2626
B = { k:k for k in ("a","b","c") }
2727
assert B["b"] == "b"
28+
29+
# End with this
30+
finished = True

vm/tests/exceptions.py

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env python3.4
2+
3+
# Test exceptions
4+
5+
# Straight forward
6+
ok = False
7+
try:
8+
raise ValueError
9+
except ValueError:
10+
ok = True
11+
assert ok
12+
13+
ok = False
14+
try:
15+
raise ValueError
16+
except ValueError as e:
17+
ok = True
18+
assert ok
19+
20+
ok = False
21+
try:
22+
raise ValueError
23+
except ValueError as e:
24+
ok = True
25+
assert ok
26+
27+
ok = False
28+
try:
29+
raise ValueError
30+
except (IOError, ValueError) as e:
31+
ok = True
32+
assert ok
33+
34+
ok = False
35+
try:
36+
raise ValueError("Potato")
37+
except (IOError, ValueError) as e:
38+
ok = True
39+
assert ok
40+
41+
# hierarchy
42+
# FIXME doesn't work because IsSubtype is broken ValueError.IsSubtype(Exception) == false
43+
# ok = False
44+
# try:
45+
# raise ValueError("potato")
46+
# except Exception:
47+
# ok = True
48+
# assert ok
49+
50+
ok = False
51+
try:
52+
raise ValueError
53+
except IOError:
54+
assert False, "Not expecting IO Error"
55+
except ValueError:
56+
ok = True
57+
assert ok
58+
59+
# no exception
60+
ok = False
61+
try:
62+
pass
63+
except ValueError:
64+
assert False, "Not expecting ValueError"
65+
else:
66+
ok = True
67+
assert ok
68+
69+
# nested
70+
ok = False
71+
try:
72+
try:
73+
raise ValueError("potato")
74+
except IOError as e:
75+
assert False, "Not expecting IOError"
76+
else:
77+
assert False, "Expecting ValueError"
78+
except ValueError:
79+
ok = True
80+
else:
81+
assert False, "Expecting ValueError (outer)"
82+
assert ok
83+
84+
ok1 = False
85+
ok2 = False
86+
try:
87+
try:
88+
raise IOError("potato")
89+
except IOError as e:
90+
ok1 = True
91+
else:
92+
assert False, "Expecting ValueError"
93+
except ValueError:
94+
assert False, "Expecting IOError"
95+
except IOError:
96+
assert False, "Expecting IOError"
97+
else:
98+
ok2 = True
99+
assert ok
100+
101+
# re-raise
102+
ok1 = False
103+
ok2 = False
104+
try:
105+
try:
106+
raise ValueError("potato")
107+
except ValueError as e:
108+
ok2 = True
109+
raise
110+
else:
111+
assert False, "Expecting ValueError (inner)"
112+
except ValueError:
113+
ok1 = True
114+
else:
115+
assert False, "Expecting ValueError (outer)"
116+
assert ok1 and ok2
117+
118+
# try/finally
119+
ok1 = False
120+
ok2 = False
121+
ok3 = False
122+
try:
123+
try:
124+
ok1 = True
125+
finally:
126+
ok2 = True
127+
except ValueError:
128+
assert False, "Not expecting ValueError (outer)"
129+
else:
130+
ok3 = True
131+
assert ok1 and ok2 and ok3
132+
133+
# FIXME
134+
# ok1 = False
135+
# ok2 = False
136+
# try:
137+
# try:
138+
# raise ValueError()
139+
# finally:
140+
# ok1 = True
141+
# except ValueError:
142+
# ok2 = True
143+
# else:
144+
# assert False, "Expecting ValueError (outer)"
145+
# assert ok1 and ok2
146+
147+
# End with this
148+
finished = True

vm/tests/functions.py

+4
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,7 @@ def fn12(*args,a=2,b=3,**kwargs) -> "RET":
153153

154154

155155
#FIXME decorators
156+
157+
158+
# End with this
159+
finished = True

vm/tests/ops.py

+34
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,37 @@
9292
assert True is True
9393
assert True is not False
9494
# FIXME EXC_MATCH
95+
96+
# logical
97+
t = True
98+
f = False
99+
assert (f and f) == False
100+
assert (f and t) == False
101+
assert (t and f) == False
102+
assert (t and t) == True
103+
104+
assert (f and f and f) == False
105+
assert (f and f and t) == False
106+
assert (f and t and f) == False
107+
assert (f and t and t) == False
108+
assert (t and f and f) == False
109+
assert (t and f and t) == False
110+
assert (t and t and f) == False
111+
assert (t and t and t) == True
112+
113+
assert (f or f) == False
114+
assert (f or t) == True
115+
assert (t or f) == True
116+
assert (t or t) == True
117+
118+
assert (f or f or f) == False
119+
assert (f or f or t) == True
120+
assert (f or t or f) == True
121+
assert (f or t or t) == True
122+
assert (t or f or f) == True
123+
assert (t or f or t) == True
124+
assert (t or t or f) == True
125+
assert (t or t or t) == True
126+
127+
# End with this
128+
finished = True

vm/vm.go

+10-8
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ import (
55
"github.com/ncw/gpython/py"
66
)
77

8+
//go:generate stringer -type=vmExit -output stringer.go
9+
810
// VM exit type
911
type vmExit byte
1012

1113
// VM exit values
1214
const (
13-
exitNot = vmExit(iota) // No error
14-
exitException // Exception occurred
15-
exitReraise // Exception re-raised by 'finally'
16-
exitReturn // 'return' statement
17-
exitBreak // 'break' statement
18-
exitContinue // 'continue' statement
19-
exitYield // 'yield' operator
20-
exitSilenced // Exception silenced by 'with'
15+
exitNot vmExit = iota // No error
16+
exitException // Exception occurred
17+
exitReraise // Exception re-raised by 'finally'
18+
exitReturn // 'return' statement
19+
exitBreak // 'break' statement
20+
exitContinue // 'continue' statement
21+
exitYield // 'yield' operator
22+
exitSilenced // Exception silenced by 'with'
2123
)
2224

2325
// Virtual machine state

0 commit comments

Comments
 (0)