Merge pull request #1347 from nspcc-dev/vm-updates

Post-preview3 VM updates
This commit is contained in:
Roman Khimov 2020-08-24 16:37:12 +03:00 committed by GitHub
commit 1809076dc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 37 deletions

View file

@ -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...)
}
} }
} }

View file

@ -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:

@ -1 +1 @@
Subproject commit 377464ed475a3de108e1bf9c834bd2279b72624e Subproject commit e3f1584b1953dcc13075a57803240858c8a480de

View file

@ -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

View file

@ -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)
})
}) })
} }