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
|
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
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 (
|
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
pkg/vm/testdata/neo-vm
vendored
2
pkg/vm/testdata/neo-vm
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 45928090908bea777962c6df5dec1dd1bbafd7a7
|
Subproject commit 359e8631ee2ddfefe8261afcec1a5bab9d9bddf9
|
146
pkg/vm/vm.go
146
pkg/vm/vm.go
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue