vm: support exceptions

Implement 3 new instructions: TRY,ENDTRY,ENDFINALLY.
1. TRY marks the start of the block where exceptions are catched.
    It has 2 arguments which are relative offsets of exception handler
    and the end of the whole try/catch construction.
2. ENDTRY denotes either end of try or catch block.
3. ENDFINALLY denotes end of finally block which is executed
    irregardless of whether an exception has occured.
This commit is contained in:
Evgenii Stratonikov 2020-07-22 12:05:46 +03:00
parent c0d7b9d234
commit 797324cb04
6 changed files with 415 additions and 139 deletions

View file

@ -33,6 +33,9 @@ type Context struct {
local *Slot
arguments *Slot
// Exception context stack pointer.
tryStack *Stack
// Script hash of the prog.
scriptHash util.Uint160
@ -103,14 +106,18 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
opcode.ENDTRY,
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
numtoread = 1
case opcode.INITSLOT:
case opcode.INITSLOT, opcode.TRY:
numtoread = 2
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
opcode.ENDTRYL,
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
numtoread = 4
case opcode.TRYL:
numtoread = 8
default:
if instr <= opcode.PUSHINT256 {
numtoread = 1 << instr

85
pkg/vm/exception.go Normal file
View file

@ -0,0 +1,85 @@
package vm
import (
"errors"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// exceptionHandlingState represents state of the exception handling process.
type exceptionHandlingState byte
const (
eTry exceptionHandlingState = iota
eCatch
eFinally
)
// exceptionHandlingContext represents context of the exception handling process.
type exceptionHandlingContext struct {
CatchOffset int
FinallyOffset int
EndOffset int
State exceptionHandlingState
}
func newExceptionHandlingContext(cOffset, fOffset int) *exceptionHandlingContext {
return &exceptionHandlingContext{
CatchOffset: cOffset,
FinallyOffset: fOffset,
EndOffset: -1,
State: eTry,
}
}
// HasCatch returns true iff context has `catch` block.
func (c *exceptionHandlingContext) HasCatch() bool { return c.CatchOffset >= 0 }
// HasFinally returns true iff context has `finally` block.
func (c *exceptionHandlingContext) HasFinally() bool { return c.FinallyOffset >= 0 }
// String implements stackitem.Item interface.
func (c *exceptionHandlingContext) String() string {
return "exception handling context"
}
// Value implements stackitem.Item interface.
func (c *exceptionHandlingContext) Value() interface{} {
return c
}
// Dup implements stackitem.Item interface.
func (c *exceptionHandlingContext) Dup() stackitem.Item {
return c
}
// Bool implements stackitem.Item interface.
func (c *exceptionHandlingContext) Bool() bool {
panic("can't convert exceptionHandlingContext to Bool")
}
// TryBytes implements stackitem.Item interface.
func (c *exceptionHandlingContext) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert exceptionHandlingContext to ByteArray")
}
// TryInteger implements stackitem.Item interface.
func (c *exceptionHandlingContext) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert exceptionHandlingContext to Integer")
}
// Type implements stackitem.Item interface.
func (c *exceptionHandlingContext) Type() stackitem.Type {
panic("exceptionHandlingContext cannot appear on evaluation stack")
}
// Convert implements stackitem.Item interface.
func (c *exceptionHandlingContext) Convert(_ stackitem.Type) (stackitem.Item, error) {
panic("exceptionHandlingContext cannot be converted to anything")
}
// Equals implements stackitem.Item interface.
func (c *exceptionHandlingContext) Equals(s stackitem.Item) bool {
return c == s
}

View file

@ -71,6 +71,11 @@ const (
ABORT Opcode = 0x37
ASSERT Opcode = 0x38
THROW Opcode = 0x3A
TRY Opcode = 0x3B
TRYL Opcode = 0x3C // TRY_L
ENDTRY Opcode = 0x3D
ENDTRYL Opcode = 0x3E // ENDTRY_L
ENDFINALLY Opcode = 0x3F
RET Opcode = 0x40
SYSCALL Opcode = 0x41

View file

@ -64,6 +64,11 @@ func _() {
_ = x[ABORT-55]
_ = x[ASSERT-56]
_ = x[THROW-58]
_ = x[TRY-59]
_ = x[TRYL-60]
_ = x[ENDTRY-61]
_ = x[ENDTRYL-62]
_ = x[ENDFINALLY-63]
_ = x[RET-64]
_ = x[SYSCALL-65]
_ = x[DEPTH-67]
@ -191,7 +196,7 @@ func _() {
_ = x[CONVERT-219]
}
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LCALLAABORTASSERTTHROWRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPROTROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGNEWBUFFERMEMCPYCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAY_TNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
const _Opcode_name = "PUSHINT8PUSHINT16PUSHINT32PUSHINT64PUSHINT128PUSHINT256PUSHAPUSHNULLPUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMP_LJMPIFJMPIF_LJMPIFNOTJMPIFNOT_LJMPEQJMPEQ_LJMPNEJMPNE_LJMPGTJMPGT_LJMPGEJMPGE_LJMPLTJMPLT_LJMPLEJMPLE_LCALLCALL_LCALLAABORTASSERTTHROWTRYTRY_LENDTRYENDTRY_LENDFINALLYRETSYSCALLDEPTHDROPNIPXDROPCLEARDUPOVERPICKTUCKSWAPROTROLLREVERSE3REVERSE4REVERSENINITSSLOTINITSLOTLDSFLD0LDSFLD1LDSFLD2LDSFLD3LDSFLD4LDSFLD5LDSFLD6LDSFLDSTSFLD0STSFLD1STSFLD2STSFLD3STSFLD4STSFLD5STSFLD6STSFLDLDLOC0LDLOC1LDLOC2LDLOC3LDLOC4LDLOC5LDLOC6LDLOCSTLOC0STLOC1STLOC2STLOC3STLOC4STLOC5STLOC6STLOCLDARG0LDARG1LDARG2LDARG3LDARG4LDARG5LDARG6LDARGSTARG0STARG1STARG2STARG3STARG4STARG5STARG6STARGNEWBUFFERMEMCPYCATSUBSTRLEFTRIGHTINVERTANDORXOREQUALNOTEQUALSIGNABSNEGATEINCDECADDSUBMULDIVMODSHLSHRNOTBOOLANDBOOLORNZNUMEQUALNUMNOTEQUALLTLTEGTGTEMINMAXWITHINPACKUNPACKNEWARRAY0NEWARRAYNEWARRAY_TNEWSTRUCT0NEWSTRUCTNEWMAPSIZEHASKEYKEYSVALUESPICKITEMAPPENDSETITEMREVERSEITEMSREMOVECLEARITEMSISNULLISTYPECONVERT"
var _Opcode_map = map[Opcode]string{
0: _Opcode_name[0:8],
@ -248,131 +253,136 @@ var _Opcode_map = map[Opcode]string{
55: _Opcode_name[321:326],
56: _Opcode_name[326:332],
58: _Opcode_name[332:337],
64: _Opcode_name[337:340],
65: _Opcode_name[340:347],
67: _Opcode_name[347:352],
69: _Opcode_name[352:356],
70: _Opcode_name[356:359],
72: _Opcode_name[359:364],
73: _Opcode_name[364:369],
74: _Opcode_name[369:372],
75: _Opcode_name[372:376],
77: _Opcode_name[376:380],
78: _Opcode_name[380:384],
80: _Opcode_name[384:388],
81: _Opcode_name[388:391],
82: _Opcode_name[391:395],
83: _Opcode_name[395:403],
84: _Opcode_name[403:411],
85: _Opcode_name[411:419],
86: _Opcode_name[419:428],
87: _Opcode_name[428:436],
88: _Opcode_name[436:443],
89: _Opcode_name[443:450],
90: _Opcode_name[450:457],
91: _Opcode_name[457:464],
92: _Opcode_name[464:471],
93: _Opcode_name[471:478],
94: _Opcode_name[478:485],
95: _Opcode_name[485:491],
96: _Opcode_name[491:498],
97: _Opcode_name[498:505],
98: _Opcode_name[505:512],
99: _Opcode_name[512:519],
100: _Opcode_name[519:526],
101: _Opcode_name[526:533],
102: _Opcode_name[533:540],
103: _Opcode_name[540:546],
104: _Opcode_name[546:552],
105: _Opcode_name[552:558],
106: _Opcode_name[558:564],
107: _Opcode_name[564:570],
108: _Opcode_name[570:576],
109: _Opcode_name[576:582],
110: _Opcode_name[582:588],
111: _Opcode_name[588:593],
112: _Opcode_name[593:599],
113: _Opcode_name[599:605],
114: _Opcode_name[605:611],
115: _Opcode_name[611:617],
116: _Opcode_name[617:623],
117: _Opcode_name[623:629],
118: _Opcode_name[629:635],
119: _Opcode_name[635:640],
120: _Opcode_name[640:646],
121: _Opcode_name[646:652],
122: _Opcode_name[652:658],
123: _Opcode_name[658:664],
124: _Opcode_name[664:670],
125: _Opcode_name[670:676],
126: _Opcode_name[676:682],
127: _Opcode_name[682:687],
128: _Opcode_name[687:693],
129: _Opcode_name[693:699],
130: _Opcode_name[699:705],
131: _Opcode_name[705:711],
132: _Opcode_name[711:717],
133: _Opcode_name[717:723],
134: _Opcode_name[723:729],
135: _Opcode_name[729:734],
136: _Opcode_name[734:743],
137: _Opcode_name[743:749],
139: _Opcode_name[749:752],
140: _Opcode_name[752:758],
141: _Opcode_name[758:762],
142: _Opcode_name[762:767],
144: _Opcode_name[767:773],
145: _Opcode_name[773:776],
146: _Opcode_name[776:778],
147: _Opcode_name[778:781],
151: _Opcode_name[781:786],
152: _Opcode_name[786:794],
153: _Opcode_name[794:798],
154: _Opcode_name[798:801],
155: _Opcode_name[801:807],
156: _Opcode_name[807:810],
157: _Opcode_name[810:813],
158: _Opcode_name[813:816],
159: _Opcode_name[816:819],
160: _Opcode_name[819:822],
161: _Opcode_name[822:825],
162: _Opcode_name[825:828],
168: _Opcode_name[828:831],
169: _Opcode_name[831:834],
170: _Opcode_name[834:837],
171: _Opcode_name[837:844],
172: _Opcode_name[844:850],
177: _Opcode_name[850:852],
179: _Opcode_name[852:860],
180: _Opcode_name[860:871],
181: _Opcode_name[871:873],
182: _Opcode_name[873:876],
183: _Opcode_name[876:878],
184: _Opcode_name[878:881],
185: _Opcode_name[881:884],
186: _Opcode_name[884:887],
187: _Opcode_name[887:893],
192: _Opcode_name[893:897],
193: _Opcode_name[897:903],
194: _Opcode_name[903:912],
195: _Opcode_name[912:920],
196: _Opcode_name[920:930],
197: _Opcode_name[930:940],
198: _Opcode_name[940:949],
200: _Opcode_name[949:955],
202: _Opcode_name[955:959],
203: _Opcode_name[959:965],
204: _Opcode_name[965:969],
205: _Opcode_name[969:975],
206: _Opcode_name[975:983],
207: _Opcode_name[983:989],
208: _Opcode_name[989:996],
209: _Opcode_name[996:1008],
210: _Opcode_name[1008:1014],
211: _Opcode_name[1014:1024],
216: _Opcode_name[1024:1030],
217: _Opcode_name[1030:1036],
219: _Opcode_name[1036:1043],
59: _Opcode_name[337:340],
60: _Opcode_name[340:345],
61: _Opcode_name[345:351],
62: _Opcode_name[351:359],
63: _Opcode_name[359:369],
64: _Opcode_name[369:372],
65: _Opcode_name[372:379],
67: _Opcode_name[379:384],
69: _Opcode_name[384:388],
70: _Opcode_name[388:391],
72: _Opcode_name[391:396],
73: _Opcode_name[396:401],
74: _Opcode_name[401:404],
75: _Opcode_name[404:408],
77: _Opcode_name[408:412],
78: _Opcode_name[412:416],
80: _Opcode_name[416:420],
81: _Opcode_name[420:423],
82: _Opcode_name[423:427],
83: _Opcode_name[427:435],
84: _Opcode_name[435:443],
85: _Opcode_name[443:451],
86: _Opcode_name[451:460],
87: _Opcode_name[460:468],
88: _Opcode_name[468:475],
89: _Opcode_name[475:482],
90: _Opcode_name[482:489],
91: _Opcode_name[489:496],
92: _Opcode_name[496:503],
93: _Opcode_name[503:510],
94: _Opcode_name[510:517],
95: _Opcode_name[517:523],
96: _Opcode_name[523:530],
97: _Opcode_name[530:537],
98: _Opcode_name[537:544],
99: _Opcode_name[544:551],
100: _Opcode_name[551:558],
101: _Opcode_name[558:565],
102: _Opcode_name[565:572],
103: _Opcode_name[572:578],
104: _Opcode_name[578:584],
105: _Opcode_name[584:590],
106: _Opcode_name[590:596],
107: _Opcode_name[596:602],
108: _Opcode_name[602:608],
109: _Opcode_name[608:614],
110: _Opcode_name[614:620],
111: _Opcode_name[620:625],
112: _Opcode_name[625:631],
113: _Opcode_name[631:637],
114: _Opcode_name[637:643],
115: _Opcode_name[643:649],
116: _Opcode_name[649:655],
117: _Opcode_name[655:661],
118: _Opcode_name[661:667],
119: _Opcode_name[667:672],
120: _Opcode_name[672:678],
121: _Opcode_name[678:684],
122: _Opcode_name[684:690],
123: _Opcode_name[690:696],
124: _Opcode_name[696:702],
125: _Opcode_name[702:708],
126: _Opcode_name[708:714],
127: _Opcode_name[714:719],
128: _Opcode_name[719:725],
129: _Opcode_name[725:731],
130: _Opcode_name[731:737],
131: _Opcode_name[737:743],
132: _Opcode_name[743:749],
133: _Opcode_name[749:755],
134: _Opcode_name[755:761],
135: _Opcode_name[761:766],
136: _Opcode_name[766:775],
137: _Opcode_name[775:781],
139: _Opcode_name[781:784],
140: _Opcode_name[784:790],
141: _Opcode_name[790:794],
142: _Opcode_name[794:799],
144: _Opcode_name[799:805],
145: _Opcode_name[805:808],
146: _Opcode_name[808:810],
147: _Opcode_name[810:813],
151: _Opcode_name[813:818],
152: _Opcode_name[818:826],
153: _Opcode_name[826:830],
154: _Opcode_name[830:833],
155: _Opcode_name[833:839],
156: _Opcode_name[839:842],
157: _Opcode_name[842:845],
158: _Opcode_name[845:848],
159: _Opcode_name[848:851],
160: _Opcode_name[851:854],
161: _Opcode_name[854:857],
162: _Opcode_name[857:860],
168: _Opcode_name[860:863],
169: _Opcode_name[863:866],
170: _Opcode_name[866:869],
171: _Opcode_name[869:876],
172: _Opcode_name[876:882],
177: _Opcode_name[882:884],
179: _Opcode_name[884:892],
180: _Opcode_name[892:903],
181: _Opcode_name[903:905],
182: _Opcode_name[905:908],
183: _Opcode_name[908:910],
184: _Opcode_name[910:913],
185: _Opcode_name[913:916],
186: _Opcode_name[916:919],
187: _Opcode_name[919:925],
192: _Opcode_name[925:929],
193: _Opcode_name[929:935],
194: _Opcode_name[935:944],
195: _Opcode_name[944:952],
196: _Opcode_name[952:962],
197: _Opcode_name[962:972],
198: _Opcode_name[972:981],
200: _Opcode_name[981:987],
202: _Opcode_name[987:991],
203: _Opcode_name[991:997],
204: _Opcode_name[997:1001],
205: _Opcode_name[1001:1007],
206: _Opcode_name[1007:1015],
207: _Opcode_name[1015:1021],
208: _Opcode_name[1021:1028],
209: _Opcode_name[1028:1040],
210: _Opcode_name[1040:1046],
211: _Opcode_name[1046:1056],
216: _Opcode_name[1056:1062],
217: _Opcode_name[1062:1068],
219: _Opcode_name[1068:1075],
}
func (i Opcode) String() string {

View file

@ -71,6 +71,8 @@ type VM struct {
istack *Stack // invocation stack.
estack *Stack // execution stack.
uncaughtException stackitem.Item // exception being handled
refs *refCounter
gasConsumed int64
@ -193,13 +195,12 @@ func (v *VM) PrintOps() {
opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.CALLL,
opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
opcode.PUSHA:
offset, rOffset, err := v.calcJumpOffset(ctx, parameter)
if err != nil {
desc = fmt.Sprintf("ERROR: %v", err)
} else {
desc = fmt.Sprintf("%d (%d/%x)", offset, rOffset, parameter)
}
opcode.PUSHA, opcode.ENDTRY, opcode.ENDTRYL:
desc = v.getOffsetDesc(ctx, parameter)
case opcode.TRY, opcode.TRYL:
catchP, finallyP := getTryParams(instr, parameter)
desc = fmt.Sprintf("catch %s, finally %s",
v.getOffsetDesc(ctx, catchP), v.getOffsetDesc(ctx, finallyP))
case opcode.INITSSLOT:
desc = fmt.Sprint(parameter[0])
case opcode.INITSLOT:
@ -223,6 +224,14 @@ func (v *VM) PrintOps() {
w.Flush()
}
func (v *VM) getOffsetDesc(ctx *Context, parameter []byte) string {
offset, rOffset, err := v.calcJumpOffset(ctx, parameter)
if err != nil {
return fmt.Sprintf("ERROR: %v", err)
}
return fmt.Sprintf("%d (%d/%x)", offset, rOffset, parameter)
}
// AddBreakPoint adds a breakpoint to the current context.
func (v *VM) AddBreakPoint(n int) {
ctx := v.Context()
@ -271,6 +280,7 @@ func (v *VM) LoadScript(b []byte) {
func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) {
ctx := NewContext(b)
ctx.estack = v.estack
ctx.tryStack = NewStack("exception")
ctx.callFlag = f
v.istack.PushVal(ctx)
}
@ -1357,7 +1367,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
// unlucky ^^
case opcode.THROW:
panic("THROW")
v.throw(v.estack.Pop().Item())
case opcode.ABORT:
panic("ABORT")
@ -1367,6 +1377,43 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
panic("ASSERT failed")
}
case opcode.TRY, opcode.TRYL:
catchP, finallyP := getTryParams(op, parameter)
cOffset := v.getJumpOffset(ctx, catchP)
fOffset := v.getJumpOffset(ctx, finallyP)
if cOffset == 0 && fOffset == 0 {
panic("invalid offset for TRY*")
} else if cOffset == ctx.ip {
cOffset = -1
} else if fOffset == ctx.ip {
fOffset = -1
}
eCtx := newExceptionHandlingContext(cOffset, fOffset)
ctx.tryStack.PushVal(eCtx)
case opcode.ENDTRY, opcode.ENDTRYL:
eCtx := ctx.tryStack.Peek(0).Value().(*exceptionHandlingContext)
if eCtx.State == eFinally {
panic("invalid exception handling state during ENDTRY*")
}
eOffset := v.getJumpOffset(ctx, parameter)
if eCtx.HasFinally() {
eCtx.State = eFinally
eCtx.EndOffset = eOffset
eOffset = eCtx.FinallyOffset
} else {
ctx.tryStack.Pop()
}
v.jump(ctx, eOffset)
case opcode.ENDFINALLY:
if v.uncaughtException != nil {
v.handleException()
return
}
eCtx := ctx.tryStack.Pop().Value().(*exceptionHandlingContext)
v.jump(ctx, eCtx.EndOffset)
default:
panic(fmt.Sprintf("unknown opcode %s", op.String()))
}
@ -1386,6 +1433,15 @@ func (v *VM) unloadContext(ctx *Context) {
}
}
// getTryParams splits TRY(L) instruction parameter into offsets for catch and finally blocks.
func getTryParams(op opcode.Opcode, p []byte) ([]byte, []byte) {
i := 1
if op == opcode.TRYL {
i = 4
}
return p[:i], p[i:]
}
// getJumpCondition performs opcode specific comparison of a and b
func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
cmp := a.Cmp(b)
@ -1407,6 +1463,11 @@ func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
}
}
func (v *VM) throw(item stackitem.Item) {
v.uncaughtException = item
v.handleException()
}
// jump performs jump to the offset.
func (v *VM) jump(ctx *Context, offset int) {
ctx.nextip = offset
@ -1424,6 +1485,8 @@ func (v *VM) getJumpOffset(ctx *Context, parameter []byte) int {
return offset
}
// calcJumpOffset returns absolute and relative offset of JMP/CALL/TRY instructions
// either in short (1-byte) or long (4-byte) form.
func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
var rOffset int32
switch l := len(parameter); l {
@ -1432,7 +1495,8 @@ func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
case 4:
rOffset = int32(binary.LittleEndian.Uint32(parameter))
default:
return 0, 0, fmt.Errorf("invalid JMP* parameter length: %d", l)
_, curr := ctx.CurrInstr()
return 0, 0, fmt.Errorf("invalid %s parameter length: %d", curr, l)
}
offset := ctx.ip + int(rOffset)
if offset < 0 || offset > len(ctx.prog) {
@ -1442,6 +1506,38 @@ func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
return offset, int(rOffset), nil
}
func (v *VM) handleException() {
pop := 0
ictx := v.istack.Peek(0).Value().(*Context)
for ictx != nil {
e := ictx.tryStack.Peek(pop)
for e != nil {
ectx := e.Value().(*exceptionHandlingContext)
if ectx.State == eFinally || (ectx.State == eCatch && !ectx.HasFinally()) {
ictx.tryStack.Pop()
e = ictx.tryStack.Peek(0)
continue
}
for i := 0; i < pop; i++ {
ctx := v.istack.Pop().Value().(*Context)
v.unloadContext(ctx)
}
if ectx.State == eTry && ectx.HasCatch() {
ectx.State = eCatch
v.estack.PushVal(v.uncaughtException)
v.uncaughtException = nil
v.jump(ictx, ectx.CatchOffset)
} else {
ectx.State = eFinally
v.jump(ictx, ectx.FinallyOffset)
}
return
}
pop++
ictx = v.istack.Peek(pop).Value().(*Context)
}
}
// CheckMultisigPar checks if sigs contains sufficient valid signatures.
func CheckMultisigPar(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sigs [][]byte) bool {
if len(sigs) == 1 {

View file

@ -1241,6 +1241,79 @@ func TestNEWBUFFER(t *testing.T) {
t.Run("TooBig", getTestFuncForVM(prog, nil, stackitem.MaxSize+1))
}
func getTRYProgram(tryBlock, catchBlock, finallyBlock []byte) []byte {
hasCatch := catchBlock != nil
hasFinally := finallyBlock != nil
tryOffset := len(tryBlock) + 3 + 2 // try args + endtry args
var (
catchLen, finallyLen int
catchOffset, finallyOffset int
)
if hasCatch {
catchLen = len(catchBlock) + 2 // endtry args
catchOffset = tryOffset
}
if hasFinally {
finallyLen = len(finallyBlock) + 1 // endfinally
finallyOffset = tryOffset + catchLen
}
prog := []byte{byte(opcode.TRY), byte(catchOffset), byte(finallyOffset)}
prog = append(prog, tryBlock...)
prog = append(prog, byte(opcode.ENDTRY), byte(catchLen+finallyLen+2))
if hasCatch {
prog = append(prog, catchBlock...)
prog = append(prog, byte(opcode.ENDTRY), byte(finallyLen+2))
}
if hasFinally {
prog = append(prog, finallyBlock...)
prog = append(prog, byte(opcode.ENDFINALLY))
}
prog = append(prog, byte(opcode.RET))
return prog
}
func getTRYTestFunc(result interface{}, tryBlock, catchBlock, finallyBlock []byte) func(t *testing.T) {
return func(t *testing.T) {
prog := getTRYProgram(tryBlock, catchBlock, finallyBlock)
runWithArgs(t, prog, result)
}
}
func TestTRY(t *testing.T) {
throw := []byte{byte(opcode.PUSH13), byte(opcode.THROW)}
push1 := []byte{byte(opcode.PUSH1)}
add5 := []byte{byte(opcode.PUSH5), byte(opcode.ADD)}
add9 := []byte{byte(opcode.PUSH9), byte(opcode.ADD)}
t.Run("NoCatch", func(t *testing.T) {
t.Run("NoFinally", getTRYTestFunc(nil, push1, nil, nil))
t.Run("WithFinally", getTRYTestFunc(10, push1, nil, add9))
t.Run("Throw", getTRYTestFunc(nil, throw, nil, add9))
})
t.Run("WithCatch", func(t *testing.T) {
t.Run("NoFinally", func(t *testing.T) {
t.Run("Simple", getTRYTestFunc(1, push1, add5, nil))
t.Run("Throw", getTRYTestFunc(18, throw, add5, nil))
t.Run("Abort", getTRYTestFunc(nil, []byte{byte(opcode.ABORT)}, push1, nil))
t.Run("ThrowInCatch", getTRYTestFunc(nil, throw, throw, nil))
})
t.Run("WithFinally", func(t *testing.T) {
t.Run("Simple", getTRYTestFunc(10, push1, add5, add9))
t.Run("Throw", getTRYTestFunc(27, throw, add5, add9))
})
})
t.Run("Nested", func(t *testing.T) {
t.Run("ReThrowInTry", func(t *testing.T) {
inner := getTRYProgram(throw, []byte{byte(opcode.THROW)}, nil)
getTRYTestFunc(27, inner, add5, add9)(t)
})
t.Run("ThrowInFinally", func(t *testing.T) {
inner := getTRYProgram(throw, add5, []byte{byte(opcode.THROW)})
getTRYTestFunc(32, inner, add5, add9)(t)
})
})
}
func TestMEMCPY(t *testing.T) {
prog := makeProgram(opcode.MEMCPY)
t.Run("Good", func(t *testing.T) {