forked from TrueCloudLab/neoneo-go
Merge pull request #1347 from nspcc-dev/vm-updates
Post-preview3 VM updates
This commit is contained in:
commit
1809076dc6
5 changed files with 61 additions and 37 deletions
|
@ -11,8 +11,6 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -330,23 +328,7 @@ func (v *vmUTScript) UnmarshalJSON(data []byte) error {
|
||||||
if b, ok := decodeSingle(ops[i]); ok {
|
if b, ok := decodeSingle(ops[i]); ok {
|
||||||
script = append(script, b...)
|
script = append(script, b...)
|
||||||
} else {
|
} else {
|
||||||
const regex = `(?P<hex>(?:0x)?[0-9a-zA-Z]+)\*(?P<num>[0-9]+)`
|
return fmt.Errorf("invalid script part: %s", ops[i])
|
||||||
re := regexp.MustCompile(regex)
|
|
||||||
ss := re.FindStringSubmatch(ops[i])
|
|
||||||
if len(ss) != 3 {
|
|
||||||
return fmt.Errorf("invalid script part: %s", ops[i])
|
|
||||||
}
|
|
||||||
b, ok := decodeSingle(ss[1])
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("invalid script part: %s", ops[i])
|
|
||||||
}
|
|
||||||
num, err := strconv.Atoi(ss[2])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid script part: %s", ops[i])
|
|
||||||
}
|
|
||||||
for i := 0; i < num; i++ {
|
|
||||||
script = append(script, b...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ type Item interface {
|
||||||
Dup() Item
|
Dup() Item
|
||||||
// TryBool converts Item to a boolean value.
|
// TryBool converts Item to a boolean value.
|
||||||
TryBool() (bool, error)
|
TryBool() (bool, error)
|
||||||
// TryBytes converts Item to a byte slice.
|
// TryBytes converts Item to a byte slice. If the underlying type is a
|
||||||
|
// byte slice, it's returned as is without copying.
|
||||||
TryBytes() ([]byte, error)
|
TryBytes() ([]byte, error)
|
||||||
// TryInteger converts Item to an integer.
|
// TryInteger converts Item to an integer.
|
||||||
TryInteger() (*big.Int, error)
|
TryInteger() (*big.Int, error)
|
||||||
|
@ -151,8 +152,11 @@ func convertPrimitive(item Item, typ Type) (Item, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if typ == BufferT {
|
if typ == BufferT {
|
||||||
return NewBuffer(b), nil
|
newb := make([]byte, len(b))
|
||||||
|
copy(newb, b)
|
||||||
|
return NewBuffer(newb), nil
|
||||||
}
|
}
|
||||||
|
// ByteArray can't really be changed, so it's OK to reuse `b`.
|
||||||
return NewByteArray(b), nil
|
return NewByteArray(b), nil
|
||||||
case BooleanT:
|
case BooleanT:
|
||||||
b, err := item.TryBool()
|
b, err := item.TryBool()
|
||||||
|
@ -519,9 +523,7 @@ func (i *ByteArray) TryBool() (bool, error) {
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (i *ByteArray) TryBytes() ([]byte, error) {
|
func (i *ByteArray) TryBytes() ([]byte, error) {
|
||||||
val := make([]byte, len(i.value))
|
return i.value, nil
|
||||||
copy(val, i.value)
|
|
||||||
return val, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryInteger implements Item interface.
|
// TryInteger implements Item interface.
|
||||||
|
@ -871,6 +873,18 @@ func NewPointer(pos int, script []byte) *Pointer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPointerWithHash returns new pointer on the specified position of the
|
||||||
|
// specified script. It differs from NewPointer in that the script hash is being
|
||||||
|
// passed explicitly to save on hash calculcation. This hash is then being used
|
||||||
|
// for pointer comparisons.
|
||||||
|
func NewPointerWithHash(pos int, script []byte, h util.Uint160) *Pointer {
|
||||||
|
return &Pointer{
|
||||||
|
pos: pos,
|
||||||
|
script: script,
|
||||||
|
hash: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// String implements Item interface.
|
// String implements Item interface.
|
||||||
func (p *Pointer) String() string {
|
func (p *Pointer) String() string {
|
||||||
return "Pointer"
|
return "Pointer"
|
||||||
|
@ -970,9 +984,7 @@ func (i *Buffer) TryBool() (bool, error) {
|
||||||
|
|
||||||
// TryBytes implements Item interface.
|
// TryBytes implements Item interface.
|
||||||
func (i *Buffer) TryBytes() ([]byte, error) {
|
func (i *Buffer) TryBytes() ([]byte, error) {
|
||||||
val := make([]byte, len(i.value))
|
return i.value, nil
|
||||||
copy(val, i.value)
|
|
||||||
return val, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryInteger implements Item interface.
|
// TryInteger implements Item interface.
|
||||||
|
@ -1079,7 +1091,7 @@ func deepCopy(item Item, seen map[Item]Item) Item {
|
||||||
case *Bool:
|
case *Bool:
|
||||||
return NewBool(it.value)
|
return NewBool(it.value)
|
||||||
case *Pointer:
|
case *Pointer:
|
||||||
return NewPointer(it.pos, it.script)
|
return NewPointerWithHash(it.pos, it.script, it.hash)
|
||||||
case *Interop:
|
case *Interop:
|
||||||
return NewInterop(it.value)
|
return NewInterop(it.value)
|
||||||
default:
|
default:
|
||||||
|
|
2
pkg/vm/testdata/neo-vm
vendored
2
pkg/vm/testdata/neo-vm
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 377464ed475a3de108e1bf9c834bd2279b72624e
|
Subproject commit e3f1584b1953dcc13075a57803240858c8a480de
|
30
pkg/vm/vm.go
30
pkg/vm/vm.go
|
@ -45,6 +45,10 @@ const (
|
||||||
// MaxInvocationStackSize is the maximum size of an invocation stack.
|
// MaxInvocationStackSize is the maximum size of an invocation stack.
|
||||||
MaxInvocationStackSize = 1024
|
MaxInvocationStackSize = 1024
|
||||||
|
|
||||||
|
// MaxTryNestingDepth is the maximum level of TRY nesting allowed,
|
||||||
|
// that is you can't have more exception handling contexts than this.
|
||||||
|
MaxTryNestingDepth = 16
|
||||||
|
|
||||||
// MaxStackSize is the maximum number of items allowed to be
|
// MaxStackSize is the maximum number of items allowed to be
|
||||||
// on all stacks at once.
|
// on all stacks at once.
|
||||||
MaxStackSize = 2 * 1024
|
MaxStackSize = 2 * 1024
|
||||||
|
@ -535,7 +539,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
|
|
||||||
case opcode.PUSHA:
|
case opcode.PUSHA:
|
||||||
n := v.getJumpOffset(ctx, parameter)
|
n := v.getJumpOffset(ctx, parameter)
|
||||||
ptr := stackitem.NewPointer(n, ctx.prog)
|
ptr := stackitem.NewPointerWithHash(n, ctx.prog, ctx.ScriptHash())
|
||||||
v.estack.PushVal(ptr)
|
v.estack.PushVal(ptr)
|
||||||
|
|
||||||
case opcode.PUSHNULL:
|
case opcode.PUSHNULL:
|
||||||
|
@ -663,10 +667,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
case opcode.CAT:
|
case opcode.CAT:
|
||||||
b := v.estack.Pop().Bytes()
|
b := v.estack.Pop().Bytes()
|
||||||
a := v.estack.Pop().Bytes()
|
a := v.estack.Pop().Bytes()
|
||||||
if l := len(a) + len(b); l > stackitem.MaxSize {
|
l := len(a) + len(b)
|
||||||
|
if l > stackitem.MaxSize {
|
||||||
panic(fmt.Sprintf("too big item: %d", l))
|
panic(fmt.Sprintf("too big item: %d", l))
|
||||||
}
|
}
|
||||||
ab := append(a, b...)
|
ab := make([]byte, l)
|
||||||
|
copy(ab, a)
|
||||||
|
copy(ab[len(a):], b)
|
||||||
v.estack.PushVal(stackitem.NewBuffer(ab))
|
v.estack.PushVal(stackitem.NewBuffer(ab))
|
||||||
|
|
||||||
case opcode.SUBSTR:
|
case opcode.SUBSTR:
|
||||||
|
@ -683,7 +690,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
if last > len(s) {
|
if last > len(s) {
|
||||||
panic("invalid offset")
|
panic("invalid offset")
|
||||||
}
|
}
|
||||||
v.estack.PushVal(stackitem.NewBuffer(s[o:last]))
|
res := make([]byte, l)
|
||||||
|
copy(res, s[o:last])
|
||||||
|
v.estack.PushVal(stackitem.NewBuffer(res))
|
||||||
|
|
||||||
case opcode.LEFT:
|
case opcode.LEFT:
|
||||||
l := int(v.estack.Pop().BigInt().Int64())
|
l := int(v.estack.Pop().BigInt().Int64())
|
||||||
|
@ -694,7 +703,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
if t := len(s); l > t {
|
if t := len(s); l > t {
|
||||||
panic("size is too big")
|
panic("size is too big")
|
||||||
}
|
}
|
||||||
v.estack.PushVal(stackitem.NewBuffer(s[:l]))
|
res := make([]byte, l)
|
||||||
|
copy(res, s[:l])
|
||||||
|
v.estack.PushVal(stackitem.NewBuffer(res))
|
||||||
|
|
||||||
case opcode.RIGHT:
|
case opcode.RIGHT:
|
||||||
l := int(v.estack.Pop().BigInt().Int64())
|
l := int(v.estack.Pop().BigInt().Int64())
|
||||||
|
@ -702,7 +713,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
panic("negative length")
|
panic("negative length")
|
||||||
}
|
}
|
||||||
s := v.estack.Pop().Bytes()
|
s := v.estack.Pop().Bytes()
|
||||||
v.estack.PushVal(stackitem.NewBuffer(s[len(s)-l:]))
|
res := make([]byte, l)
|
||||||
|
copy(res, s[len(s)-l:])
|
||||||
|
v.estack.PushVal(stackitem.NewBuffer(res))
|
||||||
|
|
||||||
case opcode.DEPTH:
|
case opcode.DEPTH:
|
||||||
v.estack.PushVal(v.estack.Len())
|
v.estack.PushVal(v.estack.Len())
|
||||||
|
@ -1352,9 +1365,12 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
||||||
|
|
||||||
case opcode.TRY, opcode.TRYL:
|
case opcode.TRY, opcode.TRYL:
|
||||||
catchP, finallyP := getTryParams(op, parameter)
|
catchP, finallyP := getTryParams(op, parameter)
|
||||||
|
if ctx.tryStack.Len() >= MaxTryNestingDepth {
|
||||||
|
panic("maximum TRY depth exceeded")
|
||||||
|
}
|
||||||
cOffset := v.getJumpOffset(ctx, catchP)
|
cOffset := v.getJumpOffset(ctx, catchP)
|
||||||
fOffset := v.getJumpOffset(ctx, finallyP)
|
fOffset := v.getJumpOffset(ctx, finallyP)
|
||||||
if cOffset == 0 && fOffset == 0 {
|
if cOffset == ctx.ip && fOffset == ctx.ip {
|
||||||
panic("invalid offset for TRY*")
|
panic("invalid offset for TRY*")
|
||||||
} else if cOffset == ctx.ip {
|
} else if cOffset == ctx.ip {
|
||||||
cOffset = -1
|
cOffset = -1
|
||||||
|
|
|
@ -1296,7 +1296,11 @@ func TestTRY(t *testing.T) {
|
||||||
add5 := []byte{byte(opcode.PUSH5), byte(opcode.ADD)}
|
add5 := []byte{byte(opcode.PUSH5), byte(opcode.ADD)}
|
||||||
add9 := []byte{byte(opcode.PUSH9), byte(opcode.ADD)}
|
add9 := []byte{byte(opcode.PUSH9), byte(opcode.ADD)}
|
||||||
t.Run("NoCatch", func(t *testing.T) {
|
t.Run("NoCatch", func(t *testing.T) {
|
||||||
t.Run("NoFinally", getTRYTestFunc(nil, push1, nil, nil))
|
t.Run("NoFinally", func(t *testing.T) {
|
||||||
|
prog := getTRYProgram(push1, nil, nil)
|
||||||
|
vm := load(prog)
|
||||||
|
checkVMFailed(t, vm)
|
||||||
|
})
|
||||||
t.Run("WithFinally", getTRYTestFunc(10, push1, nil, add9))
|
t.Run("WithFinally", getTRYTestFunc(10, push1, nil, add9))
|
||||||
t.Run("Throw", getTRYTestFunc(nil, throw, nil, add9))
|
t.Run("Throw", getTRYTestFunc(nil, throw, nil, add9))
|
||||||
})
|
})
|
||||||
|
@ -1321,6 +1325,16 @@ func TestTRY(t *testing.T) {
|
||||||
inner := getTRYProgram(throw, add5, []byte{byte(opcode.THROW)})
|
inner := getTRYProgram(throw, add5, []byte{byte(opcode.THROW)})
|
||||||
getTRYTestFunc(32, inner, add5, add9)(t)
|
getTRYTestFunc(32, inner, add5, add9)(t)
|
||||||
})
|
})
|
||||||
|
t.Run("TryMaxDepth", func(t *testing.T) {
|
||||||
|
loopTries := []byte{byte(opcode.INITSLOT), 0x01, 0x00,
|
||||||
|
byte(opcode.PUSH16), byte(opcode.INC), byte(opcode.STLOC0),
|
||||||
|
byte(opcode.TRY), 1, 1, // jump target
|
||||||
|
byte(opcode.LDLOC0), byte(opcode.DEC), byte(opcode.DUP),
|
||||||
|
byte(opcode.STLOC0), byte(opcode.PUSH0),
|
||||||
|
byte(opcode.JMPGT), 0xf8, byte(opcode.LDLOC0)}
|
||||||
|
vm := load(loopTries)
|
||||||
|
checkVMFailed(t, vm)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue