8000 Make try/except/else/finally exception handling work · go-python/gpython@4a525b9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4a525b9

Browse files
committed
Make try/except/else/finally exception handling work
* Use ExceptionInfo as return from Run so can re-raise it * Catch ExceptionInfo as a propagaged exception * Implement POP_EXCEPT, END_FINALLY, DELETE_FAST * Add unbound checking to LOAD_FAST
1 parent ca73609 commit 4a525b9

File tree

3 files changed

+141
-46
lines changed

3 files changed

+141
-46
lines changed

notes.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ BoundMethods should be run straight away in the vm not make a new vm
7777
ObjectType in *Type is probably redundant
7878
- except that Base can be nil
7979

80+
Todo
81+
====
82+
83+
* Closures
84+
* With statements
85+
* Import
86+
8087
Difference between gpython and cpython
8188
======================================
8289

vm/eval.go

Lines changed: 130 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ import (
3434
"runtime/debug"
3535
)
3636

37+
const (
38+
nameErrorMsg = "name '%s' is not defined"
39+
globalNameErrorMsg = "global name '%s' is not defined"
40+
unboundLocalErrorMsg = "local variable '%s' referenced before assignment"
41+
unboundFreeErrorMsg = "free variable '%s 8000 ' referenced before assignment in enclosing scope"
42+
cannotCatchMsg = "catching '%s' that does not inherit from BaseException is not allowed"
43+
)
44+
3745
// Stack operations
3846
func (vm *Vm) STACK_LEVEL() int { return len(vm.frame.Stack) }
3947
func (vm *Vm) EMPTY() bool { return len(vm.frame.Stack) == 0 }
@@ -63,20 +71,48 @@ func (vm *Vm) PUSH(obj py.Object) {
6371
vm.frame.Stack = append(vm.frame.Stack, obj)
6472
}
6573

74+
// Set an exception in the VM
75+
//
76+
// The exception must be a valid exception instance (eg as returned by
77+
// py.MakeException)
78+
//
79+
// It sets vm.exc.* and sets vm.exit to exitException
80+
func (vm *Vm) SetException(exception py.Object) {
81+
vm.old_exc = vm.exc
82+
vm.exc.Value = exception
83+
vm.exc.Type = exception.Type()
84+
vm.exc.Traceback = py.None // FIXME start the traceback
85+
vm.exit = exitException
86+
}
87+
6688
// Check for an exception (panic)
6789
//
68-
// Must be called as a defer function
69-
func (vm *Vm) CheckException() {
70-
if r := recover(); r != nil {
71-
// Coerce whatever was raised into a *Exception
72-
vm.exception = py.MakeException(r)
90+
// Should be called with the result of recover
91+
func (vm *Vm) CheckExceptionRecover(r interface{}) {
92+
// If what was raised was an ExceptionInfo the stuff this into the current vm
93+
if exc, ok := r.(py.ExceptionInfo); ok {
94+
vm.old_exc = vm.exc
95+
vm.exc = exc
7396
vm.exit = exitException
97+
fmt.Printf("*** Propagating exception: %s\n", exc.Error())
98+
} else {
99+
// Coerce whatever was raised into a *Exception
100+
vm.SetException(py.MakeException(r))
74101
fmt.Printf("*** Exception raised %v\n", r)
75102
// Dump the goroutine stack
76103
debug.PrintStack()
77104
}
78105
}
79106

107+
// Check for an exception (panic)
108+
//
109+
// Must be called as a defer function
110+
func (vm *Vm) CheckException() {
111+
if r := recover(); r != nil {
112+
vm.CheckExceptionRecover(r)
113+
}
114+
}
115+
80116
// Illegal instruction
81117
func do_ILLEGAL(vm *Vm, arg int32) {
82118
defer vm.CheckException()
@@ -517,15 +553,56 @@ func do_POP_BLOCK(vm *Vm, arg int32) {
517553
// exception state.
518554
func do_POP_EXCEPT(vm *Vm, arg int32) {
519555
defer vm.CheckException()
520-
vm.NotImplemented("POP_EXCEPT", arg)
556+
frame := vm.frame
557+
b := vm.frame.Block
558+
frame.PopBlock()
559+
if b.Type != EXCEPT_HANDLER {
560+
vm.SetException(py.ExceptionNewf(py.SystemError, "popped block is not an except handler"))
561+
} else {
562+
vm.UnwindExceptHandler(frame, b)
563+
}
521564
}
522565

523566
// Terminates a finally clause. The interpreter recalls whether the
524567
// exception has to be re-raised, or whether the function returns, and
525568
// continues with the outer-next block.
526569
func do_END_FINALLY(vm *Vm, arg int32) {
527570
defer vm.CheckException()
528-
vm.NotImplemented("END_FINALLY", arg)
571+
v := vm.POP()
572+
if vInt, ok := v.(py.Int); ok {
573+
vm.exit = vmExit(vInt)
574+
if vm.exit == exitYield {
575+
panic("Unexpected exitYield in END_FINALLY")
576+
}
577+
if vm.exit == exitReturn || vm.exit == exitContinue {
578+
// Leave return value on the stack
579+
// retval = vm.POP()
580+
}
581+
if vm.exit == exitSilenced {
582+
// An exception was silenced by 'with', we must
583+
// manually unwind the EXCEPT_HANDLER block which was
584+
// created when the exception was caught, otherwise
585+
// the stack will be in an inconsistent state.
586+
frame := vm.frame
587+
b := vm.frame.Block
588+
frame.PopBlock()
589+
if b.Type != EXCEPT_HANDLER {
590+
panic("Expecting EXCEPT_HANDLER in END_FINALLY")
591+
}
592+
vm.UnwindExceptHandler(frame, b)
593+
vm.exit = exitNot
594+
}
595+
} else if py.ExceptionClassCheck(v) {
596+
w := vm.POP()
597+
u := vm.POP()
598+
// FIXME PyErr_Restore(v, w, u)
599+
vm.exc.Type = v
600+
vm.exc.Value = w
601+
vm.exc.Traceback = u
602+
vm.exit = exitReraise
603+
} else if v != py.None {
604+
vm.SetException(py.ExceptionNewf(py.SystemError, "'finally' pops bad exception %#v", v))
605+
}
529606
}
530607

531608
// Loads the __build_class__ helper function to the stack which
@@ -719,15 +796,13 @@ func do_COMPARE_OP(vm *Vm, opname int32) {
719796
if bTuple, ok := b.(py.Tuple); ok {
720797
for _, exc := range bTuple {
721798
if !py.ExceptionClassCheck(exc) {
722-
vm.exception = py.ExceptionNewf(py.TypeError, "Catching '%s' that does not inherit from BaseException is not allowed", exc.Type().Name)
723-
vm.exit = exitException
799+
vm.SetException(py.ExceptionNewf(py.TypeError, cannotCatchMsg, exc.Type().Name))
724800
goto finished
725801
}
726802
}
727803
} else {
728804
if !py.ExceptionClassCheck(b) {
729-
vm.exception = py.ExceptionNewf(py.TypeError, "Catching '%s' that does not inherit from BaseException is not allowed", b.Type().Name)
730-
vm.exit = exitException
805+
vm.SetException(py.ExceptionNewf(py.TypeError, cannotCatchMsg, b.Type().Name))
731806
goto finished
732807
}
733808
}
@@ -815,7 +890,6 @@ func do_JUMP_ABSOLUTE(vm *Vm, target int32) {
815890
// iterator indicates it is exhausted TOS is popped, and the bytecode
816891
// counter is incremented by delta.
817892
func do_FOR_ITER(vm *Vm, delta int32) {
818-
defer vm.CheckException()
819893
defer func() {
820894
if r := recover(); r != nil {
821895
// FIXME match subclasses of StopIteration too?
@@ -824,8 +898,9 @@ func do_FOR_ITER(vm *Vm, delta int32) {
824898
} else if ex, ok := r.(*py.Type); ok && ex == py.StopIteration {
825899
// StopIteration raised
826900
} else {
827-
// re-raise the panic
828-
panic(r)
901+
// Deal with the exception as normal
902+
vm.CheckExceptionRecover(r)
903+
return
829904
}
830905
vm.DROP()
831906
vm.frame.Lasti += delta
@@ -875,8 +950,13 @@ func do_STORE_MAP(vm *Vm, arg int32) {
875950
// Pushes a reference to the local co_varnames[var_num] onto the stack.
876951
func do_LOAD_FAST(vm *Vm, var_num int32) {
877952
defer vm.CheckException()
878-
fmt.Printf("LOAD_FAST %q\n", vm.frame.Code.Varnames[var_num])
879-
vm.PUSH(vm.frame.Locals[vm.frame.Code.Varnames[var_num]])
953+
varname := vm.frame.Code.Varnames[var_num]
954+
fmt.Printf("LOAD_FAST %q\n", varname)
955+
if value, ok := vm.frame.Locals[varname]; ok {
956+
vm.PUSH(value)
957+
} else {
958+
vm.SetException(py.ExceptionNewf(py.UnboundLocalError, unboundLocalErrorMsg, varname))
959+
}
880960
}
881961

882962
// Stores TOS into the local co_varnames[var_num].
@@ -888,7 +968,12 @@ func do_STORE_FAST(vm *Vm, var_num int32) {
888968
// Deletes local co_varnames[var_num].
889969
func do_DELETE_FAST(vm *Vm, var_num int32) {
890970
defer vm.CheckException()
891-
vm.NotImplemented("DELETE_FAST", var_num)
971+
varname := vm.frame.Code.Varnames[var_num]
972+
if _, ok := vm.frame.Locals[varname]; ok {
973+
delete(vm.frame.Locals, varname)
974+
} else {
975+
vm.SetException(py.ExceptionNewf(py.UnboundLocalError, unboundLocalErrorMsg, varname))
976+
}
892977
}
893978

894979
// Pushes a reference to the cell contained in slot i of the cell and
@@ -934,18 +1019,21 @@ func do_DELETE_DEREF(vm *Vm, i int32) {
9341019
func (vm *Vm) raise(exc, cause py.Object) {
9351020
if exc == nil {
9361021
// raise (with no parameters == re-raise)
937-
if vm.exception == nil {
938-
vm.exception = py.ExceptionNewf(py.RuntimeError, "No active exception to reraise")
1022+
if vm.exc.Value == nil {
1023+
vm.SetException(py.ExceptionNewf(py.RuntimeError, "No active exception to reraise"))
1024+
} else {
1025+
// Signal the existing exception again
1026+
vm.exit = exitReraise
9391027
}
9401028
} else {
9411029
// raise <instance>
9421030
// raise <type>
943-
vm.exception = py.MakeException(exc)
1031+
excException := py.MakeException(exc)
1032+
vm.SetException(excException)
9441033
if cause != nil {
945-
vm.exception.Cause = py.MakeException(cause)
1034+
excException.Cause = py.MakeException(cause)
9461035
}
9471036
}
948-
vm.exit = exitException
9491037
}
9501038

9511039
// Raises an exception. argc indicates the number of parameters to the
@@ -1151,16 +1239,16 @@ func (vm *Vm) UnwindExceptHandler(frame *py.Frame, block *py.TryBlock) {
11511239
} else {
11521240
frame.Stack = frame.Stack[:block.Level+3]
11531241
}
1154-
vm.exc_type = vm.POP()
1155-
vm.exc_value = vm.POP()
1156-
vm.exc_traceback = vm.POP()
1242+
vm.exc.Type = vm.POP()
1243+
vm.exc.Value = vm.POP()
1244+
vm.exc.Traceback = vm.POP()
11571245
}
11581246

11591247
// Run the virtual machine on a Frame object
11601248
//
11611249
// FIXME figure out how we are going to signal exceptions!
11621250
//
1163-
// Returns an Object and an error
1251+
// Returns an Object and an error. The error will be a py.ExceptionInfo
11641252
func RunFrame(frame *py.Frame) (res py.Object, err error) {
11651253
vm := NewVm(frame)
11661254
// defer func() {
@@ -1237,32 +1325,32 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
12371325
frame.Lasti = b.Handler
12381326
break
12391327
}
1240-
if vm.exit == exitException && (b.Type == SETUP_EXCEPT || b.Type == SETUP_FINALLY) {
1328+
if vm.exit&(exitException|exitReraise) != 0 && (b.Type == SETUP_EXCEPT || b.Type == SETUP_FINALLY) {
12411329
fmt.Printf("*** Exception\n")
12421330
var exc, val, tb py.Object
12431331
handler := b.Handler
12441332
// This invalidates b
12451333
frame.PushBlock(EXCEPT_HANDLER, -1, vm.STACK_LEVEL())
1246-
vm.PUSH(vm.exc_traceback)
1247-
vm.PUSH(vm.exc_value)
1248-
if vm.exc_type != nil {
1249-
vm.PUSH(vm.exc_type)
1334+
vm.PUSH(vm.old_exc.Traceback)
1335+
vm.PUSH(vm.old_exc.Value)
1336+
if vm.old_exc.Type != nil {
1337+
vm.PUSH(vm.exc.Type)
12501338
} else {
12511339
vm.PUSH(py.None)
12521340
}
12531341
// FIXME PyErr_Fetch(&exc, &val, &tb)
1254-
exc = vm.exc_type
1255-
val = vm.exc_value
1256-
tb = vm.exc_traceback
1342+
exc = vm.exc.Type
1343+
val = vm.exc.Value
1344+
tb = vm.exc.Traceback
12571345
// Make the raw exception data
12581346
// available to the handler,
12591347
// so a program can emulate the
12601348
// Python main loop.
12611349
// FIXME PyErr_NormalizeException(exc, &val, &tb)
12621350
// FIXME PyException_SetTraceback(val, tb)
1263-
vm.exc_type = exc
1264-
vm.exc_value = val
1265-
vm.exc_traceback = tb
1351+
vm.exc.Type = exc
1352+
vm.exc.Value = val
1353+
vm.exc.Traceback = tb
12661354
if tb == nil {
12671355
tb = py.None
12681356
}
@@ -1287,15 +1375,17 @@ func RunFrame(frame *py.Frame) (res py.Object, err error) {
12871375
}
12881376
}
12891377
}
1290-
1291-
return vm.result, vm.exception
1378+
if vm.exc.Value != nil {
1379+
return vm.result, vm.exc
1380+
}
1381+
return vm.result, nil
12921382
}
12931383

12941384
// Run the virtual machine on a Code object
12951385
//
12961386
// Any parameters are expected to have been decoded into locals
12971387
//
1298-
// Returns an Object and an error
1388+
// Returns an Object and an error. The error will be a py.ExceptionInfo
12991389
func Run(globals, locals py.StringDict, code *py.Code) (res py.Object, err error) {
13001390
frame := py.NewFrame(globals, locals, code)
13011391

vm/vm.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@ type Vm struct {
3030
ext int32
3131
// Return value
3232
result py.Object
33-
// Current exception
34-
exception *py.Exception
3533
// Exit value
3634
exit vmExit
37-
// Traceback handling
38-
exc_type py.Object
39-
exc_value py.Object
40-
exc_traceback py.Object
35+
// Current exception type, value and traceback
36+
exc py.ExceptionInfo
37+
// Previous exception type, value and traceback
38+
old_exc py.ExceptionInfo
4139
}
4240

4341
// Make a new VM

0 commit comments

Comments
 (0)
0