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 local *Slot
arguments *Slot arguments *Slot
// Exception context stack pointer.
tryStack *Stack
// Script hash of the prog. // Script hash of the prog.
scriptHash util.Uint160 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, case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE, opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT, opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
opcode.ENDTRY,
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC: opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
numtoread = 1 numtoread = 1
case opcode.INITSLOT: case opcode.INITSLOT, opcode.TRY:
numtoread = 2 numtoread = 2
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL, case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
opcode.ENDTRYL,
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA: opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
numtoread = 4 numtoread = 4
case opcode.TRYL:
numtoread = 8
default: default:
if instr <= opcode.PUSHINT256 { if instr <= opcode.PUSHINT256 {
numtoread = 1 << instr 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 ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -117,17 +116,24 @@ func getTestingInterop(id uint32) *InteropFuncPrice {
return nil return nil
} }
switch id { switch id {
case binary.LittleEndian.Uint32([]byte{0x77, 0x77, 0x77, 0x77}): case 0x77777777:
return &InteropFuncPrice{Func: f} return &InteropFuncPrice{Func: f}
case binary.LittleEndian.Uint32([]byte{0x66, 0x66, 0x66, 0x66}): case 0x66666666:
return &InteropFuncPrice{ return &InteropFuncPrice{
Func: f, Func: f,
RequiredFlags: smartcontract.ReadOnly, RequiredFlags: smartcontract.ReadOnly,
} }
case binary.LittleEndian.Uint32([]byte{0x55, 0x55, 0x55, 0x55}): case 0x55555555:
return &InteropFuncPrice{ return &InteropFuncPrice{
Func: f, Func: f,
} }
case 0xADDEADDE:
return &InteropFuncPrice{
Func: func(v *VM) error {
v.throw(stackitem.Make("error"))
return nil
},
}
} }
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) { t.Run(ut.Category+":"+ut.Name, func(t *testing.T) {
for i := range ut.Tests { for i := range ut.Tests {
test := ut.Tests[i] 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) { t.Run(ut.Tests[i].Name, func(t *testing.T) {
prog := []byte(test.Script) prog := []byte(test.Script)
vm := load(prog) vm := load(prog)

View file

@ -68,9 +68,14 @@ const (
CALLA Opcode = 0x36 CALLA Opcode = 0x36
// Exceptions // Exceptions
ABORT Opcode = 0x37 ABORT Opcode = 0x37
ASSERT Opcode = 0x38 ASSERT Opcode = 0x38
THROW Opcode = 0x3A 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 RET Opcode = 0x40
SYSCALL Opcode = 0x41 SYSCALL Opcode = 0x41

View file

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

View file

@ -45,3 +45,10 @@ func (s *Slot) Get(i int) stackitem.Item {
// Size returns slot size. // Size returns slot size.
func (s *Slot) Size() int { return len(s.storage) } 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. istack *Stack // invocation stack.
estack *Stack // execution stack. estack *Stack // execution stack.
uncaughtException stackitem.Item // exception being handled
refs *refCounter refs *refCounter
gasConsumed int64 gasConsumed int64
@ -193,13 +195,12 @@ func (v *VM) PrintOps() {
opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.CALLL, opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.CALLL,
opcode.JMPEQL, opcode.JMPNEL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
opcode.PUSHA: opcode.PUSHA, opcode.ENDTRY, opcode.ENDTRYL:
offset, rOffset, err := v.calcJumpOffset(ctx, parameter) desc = v.getOffsetDesc(ctx, parameter)
if err != nil { case opcode.TRY, opcode.TRYL:
desc = fmt.Sprintf("ERROR: %v", err) catchP, finallyP := getTryParams(instr, parameter)
} else { desc = fmt.Sprintf("catch %s, finally %s",
desc = fmt.Sprintf("%d (%d/%x)", offset, rOffset, parameter) v.getOffsetDesc(ctx, catchP), v.getOffsetDesc(ctx, finallyP))
}
case opcode.INITSSLOT: case opcode.INITSSLOT:
desc = fmt.Sprint(parameter[0]) desc = fmt.Sprint(parameter[0])
case opcode.INITSLOT: case opcode.INITSLOT:
@ -223,6 +224,14 @@ func (v *VM) PrintOps() {
w.Flush() 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. // AddBreakPoint adds a breakpoint to the current context.
func (v *VM) AddBreakPoint(n int) { func (v *VM) AddBreakPoint(n int) {
ctx := v.Context() ctx := v.Context()
@ -271,6 +280,7 @@ func (v *VM) LoadScript(b []byte) {
func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) { func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) {
ctx := NewContext(b) ctx := NewContext(b)
ctx.estack = v.estack ctx.estack = v.estack
ctx.tryStack = NewStack("exception")
ctx.callFlag = f ctx.callFlag = f
v.istack.PushVal(ctx) 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) cond = getJumpCondition(op, a, b)
} }
v.jumpIf(ctx, offset, cond) if cond {
v.jump(ctx, offset)
}
case opcode.CALL, opcode.CALLL: case opcode.CALL, opcode.CALLL:
v.checkInvocationStackSize() v.checkInvocationStackSize()
@ -1231,7 +1243,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.istack.PushVal(newCtx) v.istack.PushVal(newCtx)
offset := v.getJumpOffset(newCtx, parameter) offset := v.getJumpOffset(newCtx, parameter)
v.jumpIf(newCtx, offset, true) v.jump(newCtx, offset)
case opcode.CALLA: case opcode.CALLA:
ptr := v.estack.Pop().Item().(*stackitem.Pointer) 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.local = nil
newCtx.arguments = nil newCtx.arguments = nil
v.istack.PushVal(newCtx) v.istack.PushVal(newCtx)
v.jumpIf(newCtx, ptr.Position(), true) v.jump(newCtx, ptr.Position())
case opcode.SYSCALL: case opcode.SYSCALL:
interopID := GetInteropID(parameter) interopID := GetInteropID(parameter)
@ -1260,9 +1272,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
} }
case opcode.RET: case opcode.RET:
v.istack.Pop() oldCtx := v.istack.Pop().Value().(*Context)
oldEstack := v.estack oldEstack := v.estack
v.unloadContext(oldCtx)
if v.istack.Len() == 0 { if v.istack.Len() == 0 {
v.state = haltState v.state = haltState
break break
@ -1354,7 +1367,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
// unlucky ^^ // unlucky ^^
case opcode.THROW: case opcode.THROW:
panic("THROW") v.throw(v.estack.Pop().Item())
case opcode.ABORT: case opcode.ABORT:
panic("ABORT") panic("ABORT")
@ -1364,12 +1377,71 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
panic("ASSERT failed") 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: default:
panic(fmt.Sprintf("unknown opcode %s", op.String())) panic(fmt.Sprintf("unknown opcode %s", op.String()))
} }
return 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 // getJumpCondition performs opcode specific comparison of a and b
func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool { func getJumpCondition(op opcode.Opcode, a, b *big.Int) bool {
cmp := a.Cmp(b) 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) throw(item stackitem.Item) {
func (v *VM) jumpIf(ctx *Context, offset int, cond bool) { v.uncaughtException = item
if cond { v.handleException()
ctx.nextip = offset }
}
// 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 // getJumpOffset returns instruction number in a current context
@ -1410,6 +1485,8 @@ func (v *VM) getJumpOffset(ctx *Context, parameter []byte) int {
return offset 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) { func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
var rOffset int32 var rOffset int32
switch l := len(parameter); l { switch l := len(parameter); l {
@ -1418,7 +1495,8 @@ func (v *VM) calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
case 4: case 4:
rOffset = int32(binary.LittleEndian.Uint32(parameter)) rOffset = int32(binary.LittleEndian.Uint32(parameter))
default: 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) offset := ctx.ip + int(rOffset)
if offset < 0 || offset > len(ctx.prog) { 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 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. // CheckMultisigPar checks if sigs contains sufficient valid signatures.
func CheckMultisigPar(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sigs [][]byte) bool { func CheckMultisigPar(v *VM, curve elliptic.Curve, h []byte, pkeys [][]byte, sigs [][]byte) bool {
if len(sigs) == 1 { if len(sigs) == 1 {

View file

@ -1142,6 +1142,46 @@ func getTestFuncForVM(prog []byte, result interface{}, args ...interface{}) func
return getCustomTestFuncForVM(prog, f, args...) 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) { func TestNOTEQUALByteArray(t *testing.T) {
prog := makeProgram(opcode.NOTEQUAL) prog := makeProgram(opcode.NOTEQUAL)
t.Run("True", getTestFuncForVM(prog, true, []byte{1, 2}, []byte{0, 1, 2})) 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)) 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) { func TestMEMCPY(t *testing.T) {
prog := makeProgram(opcode.MEMCPY) prog := makeProgram(opcode.MEMCPY)
t.Run("Good", func(t *testing.T) { t.Run("Good", func(t *testing.T) {