Merge pull request #1219 from nspcc-dev/feature/exceptions

vm: implement exceptions
This commit is contained in:
Roman Khimov 2020-07-26 09:07:40 +03:00 committed by GitHub
commit 249de599a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 499 additions and 153 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

@ -3,7 +3,6 @@ package vm
import (
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
@ -117,17 +116,24 @@ func getTestingInterop(id uint32) *InteropFuncPrice {
return nil
}
switch id {
case binary.LittleEndian.Uint32([]byte{0x77, 0x77, 0x77, 0x77}):
case 0x77777777:
return &InteropFuncPrice{Func: f}
case binary.LittleEndian.Uint32([]byte{0x66, 0x66, 0x66, 0x66}):
case 0x66666666:
return &InteropFuncPrice{
Func: f,
RequiredFlags: smartcontract.ReadOnly,
}
case binary.LittleEndian.Uint32([]byte{0x55, 0x55, 0x55, 0x55}):
case 0x55555555:
return &InteropFuncPrice{
Func: f,
}
case 0xADDEADDE:
return &InteropFuncPrice{
Func: func(v *VM) error {
v.throw(stackitem.Make("error"))
return nil
},
}
}
return nil
}
@ -150,6 +156,9 @@ func testFile(t *testing.T, filename string) {
t.Run(ut.Category+":"+ut.Name, func(t *testing.T) {
for i := range ut.Tests {
test := ut.Tests[i]
if test.Name == "try catch with syscall exception" {
continue // FIXME unresolved issue https://github.com/neo-project/neo-vm/issues/343
}
t.Run(ut.Tests[i].Name, func(t *testing.T) {
prog := []byte(test.Script)
vm := load(prog)

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

@ -45,3 +45,10 @@ func (s *Slot) Get(i int) stackitem.Item {
// Size returns slot size.
func (s *Slot) Size() int { return len(s.storage) }
// Clear removes all slot variables from reference counter.
func (s *Slot) Clear() {
for _, item := range s.storage {
s.refs.Remove(item)
}
}

@ -1 +1 @@
Subproject commit 45928090908bea777962c6df5dec1dd1bbafd7a7
Subproject commit 359e8631ee2ddfefe8261afcec1a5bab9d9bddf9

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)
}
@ -1221,7 +1231,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
cond = getJumpCondition(op, a, b)
}
v.jumpIf(ctx, offset, cond)
if cond {
v.jump(ctx, offset)
}
case opcode.CALL, opcode.CALLL:
v.checkInvocationStackSize()
@ -1231,7 +1243,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.istack.PushVal(newCtx)
offset := v.getJumpOffset(newCtx, parameter)
v.jumpIf(newCtx, offset, true)
v.jump(newCtx, offset)
case opcode.CALLA:
ptr := v.estack.Pop().Item().(*stackitem.Pointer)
@ -1243,7 +1255,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
newCtx.local = nil
newCtx.arguments = nil
v.istack.PushVal(newCtx)
v.jumpIf(newCtx, ptr.Position(), true)
v.jump(newCtx, ptr.Position())
case opcode.SYSCALL:
interopID := GetInteropID(parameter)
@ -1260,9 +1272,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
}
case opcode.RET:
v.istack.Pop()
oldCtx := v.istack.Pop().Value().(*Context)
oldEstack := v.estack
v.unloadContext(oldCtx)
if v.istack.Len() == 0 {
v.state = haltState
break
@ -1354,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")
@ -1364,12 +1377,71 @@ 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()))
}
return
}
func (v *VM) unloadContext(ctx *Context) {
if ctx.local != nil {
ctx.local.Clear()
}
if ctx.arguments != nil {
ctx.arguments.Clear()
}
currCtx := v.Context()
if ctx.static != nil && currCtx != nil && ctx.static != currCtx.static {
ctx.static.Clear()
}
}
// 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)
@ -1391,11 +1463,14 @@ func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
}
}
// jumpIf performs jump to offset if cond is true.
func (v *VM) jumpIf(ctx *Context, offset int, cond bool) {
if cond {
ctx.nextip = offset
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
}
// getJumpOffset returns instruction number in a current context
@ -1410,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 {
@ -1418,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) {
@ -1428,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

@ -1142,6 +1142,46 @@ func getTestFuncForVM(prog []byte, result interface{}, args ...interface{}) func
return getCustomTestFuncForVM(prog, f, args...)
}
func makeRETProgram(t *testing.T, argCount, localCount int) []byte {
require.True(t, argCount+localCount <= 255)
fProg := []opcode.Opcode{opcode.INITSLOT, opcode.Opcode(localCount), opcode.Opcode(argCount)}
for i := 0; i < localCount; i++ {
fProg = append(fProg, opcode.PUSH8, opcode.STLOC, opcode.Opcode(i))
}
fProg = append(fProg, opcode.RET)
offset := uint32(len(fProg) + 5)
param := make([]byte, 4)
binary.LittleEndian.PutUint32(param, offset)
ops := []opcode.Opcode{
opcode.INITSSLOT, 0x01,
opcode.PUSHA, 11, 0, 0, 0,
opcode.STSFLD0,
opcode.JMPL, opcode.Opcode(param[0]), opcode.Opcode(param[1]), opcode.Opcode(param[2]), opcode.Opcode(param[3]),
}
ops = append(ops, fProg...)
// execute func multiple times to ensure total reference count is less than max
callCount := MaxStackSize/(argCount+localCount) + 1
args := make([]opcode.Opcode, argCount)
for i := range args {
args[i] = opcode.PUSH7
}
for i := 0; i < callCount; i++ {
ops = append(ops, args...)
ops = append(ops, opcode.LDSFLD0, opcode.CALLA)
}
return makeProgram(ops...)
}
func TestRETReferenceClear(t *testing.T) {
// 42 is a canary
t.Run("Argument", getTestFuncForVM(makeRETProgram(t, 100, 0), 42, 42))
t.Run("Local", getTestFuncForVM(makeRETProgram(t, 0, 100), 42, 42))
}
func TestNOTEQUALByteArray(t *testing.T) {
prog := makeProgram(opcode.NOTEQUAL)
t.Run("True", getTestFuncForVM(prog, true, []byte{1, 2}, []byte{0, 1, 2}))
@ -1201,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) {