forked from TrueCloudLab/neoneo-go
Merge pull request #1219 from nspcc-dev/feature/exceptions
vm: implement exceptions
This commit is contained in:
commit
249de599a0
9 changed files with 499 additions and 153 deletions
|
@ -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
85
pkg/vm/exception.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
2
pkg/vm/testdata/neo-vm
vendored
2
pkg/vm/testdata/neo-vm
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 45928090908bea777962c6df5dec1dd1bbafd7a7
|
||||
Subproject commit 359e8631ee2ddfefe8261afcec1a5bab9d9bddf9
|
144
pkg/vm/vm.go
144
pkg/vm/vm.go
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue