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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -330,24 +328,8 @@ func (v *vmUTScript) UnmarshalJSON(data []byte) error {
|
|||
if b, ok := decodeSingle(ops[i]); ok {
|
||||
script = append(script, b...)
|
||||
} else {
|
||||
const regex = `(?P<hex>(?:0x)?[0-9a-zA-Z]+)\*(?P<num>[0-9]+)`
|
||||
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...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*v = script
|
||||
|
|
|
@ -33,7 +33,8 @@ type Item interface {
|
|||
Dup() Item
|
||||
// TryBool converts Item to a boolean value.
|
||||
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)
|
||||
// TryInteger converts Item to an integer.
|
||||
TryInteger() (*big.Int, error)
|
||||
|
@ -151,8 +152,11 @@ func convertPrimitive(item Item, typ Type) (Item, error) {
|
|||
return nil, err
|
||||
}
|
||||
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
|
||||
case BooleanT:
|
||||
b, err := item.TryBool()
|
||||
|
@ -519,9 +523,7 @@ func (i *ByteArray) TryBool() (bool, error) {
|
|||
|
||||
// TryBytes implements Item interface.
|
||||
func (i *ByteArray) TryBytes() ([]byte, error) {
|
||||
val := make([]byte, len(i.value))
|
||||
copy(val, i.value)
|
||||
return val, nil
|
||||
return i.value, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *Pointer) String() string {
|
||||
return "Pointer"
|
||||
|
@ -970,9 +984,7 @@ func (i *Buffer) TryBool() (bool, error) {
|
|||
|
||||
// TryBytes implements Item interface.
|
||||
func (i *Buffer) TryBytes() ([]byte, error) {
|
||||
val := make([]byte, len(i.value))
|
||||
copy(val, i.value)
|
||||
return val, nil
|
||||
return i.value, nil
|
||||
}
|
||||
|
||||
// TryInteger implements Item interface.
|
||||
|
@ -1079,7 +1091,7 @@ func deepCopy(item Item, seen map[Item]Item) Item {
|
|||
case *Bool:
|
||||
return NewBool(it.value)
|
||||
case *Pointer:
|
||||
return NewPointer(it.pos, it.script)
|
||||
return NewPointerWithHash(it.pos, it.script, it.hash)
|
||||
case *Interop:
|
||||
return NewInterop(it.value)
|
||||
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 = 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
|
||||
// on all stacks at once.
|
||||
MaxStackSize = 2 * 1024
|
||||
|
@ -535,7 +539,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
|
||||
case opcode.PUSHA:
|
||||
n := v.getJumpOffset(ctx, parameter)
|
||||
ptr := stackitem.NewPointer(n, ctx.prog)
|
||||
ptr := stackitem.NewPointerWithHash(n, ctx.prog, ctx.ScriptHash())
|
||||
v.estack.PushVal(ptr)
|
||||
|
||||
case opcode.PUSHNULL:
|
||||
|
@ -663,10 +667,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
case opcode.CAT:
|
||||
b := 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))
|
||||
}
|
||||
ab := append(a, b...)
|
||||
ab := make([]byte, l)
|
||||
copy(ab, a)
|
||||
copy(ab[len(a):], b)
|
||||
v.estack.PushVal(stackitem.NewBuffer(ab))
|
||||
|
||||
case opcode.SUBSTR:
|
||||
|
@ -683,7 +690,9 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
if last > len(s) {
|
||||
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:
|
||||
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 {
|
||||
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:
|
||||
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")
|
||||
}
|
||||
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:
|
||||
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:
|
||||
catchP, finallyP := getTryParams(op, parameter)
|
||||
if ctx.tryStack.Len() >= MaxTryNestingDepth {
|
||||
panic("maximum TRY depth exceeded")
|
||||
}
|
||||
cOffset := v.getJumpOffset(ctx, catchP)
|
||||
fOffset := v.getJumpOffset(ctx, finallyP)
|
||||
if cOffset == 0 && fOffset == 0 {
|
||||
if cOffset == ctx.ip && fOffset == ctx.ip {
|
||||
panic("invalid offset for TRY*")
|
||||
} else if cOffset == ctx.ip {
|
||||
cOffset = -1
|
||||
|
|
|
@ -1296,7 +1296,11 @@ func TestTRY(t *testing.T) {
|
|||
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("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("Throw", getTRYTestFunc(nil, throw, nil, add9))
|
||||
})
|
||||
|
@ -1321,6 +1325,16 @@ func TestTRY(t *testing.T) {
|
|||
inner := getTRYProgram(throw, add5, []byte{byte(opcode.THROW)})
|
||||
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