Merge pull request #2140 from nspcc-dev/vm-optimize-stack

Optimize VM stack
This commit is contained in:
Roman Khimov 2021-08-26 10:31:31 +03:00 committed by GitHub
commit 40c6c065d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 252 additions and 281 deletions

View file

@ -1917,8 +1917,9 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
if vm.HasFailed() {
return 0, fmt.Errorf("%w: vm execution has failed: %v", ErrVerificationFailed, err)
}
resEl := vm.Estack().Pop()
if resEl != nil {
estack := vm.Estack()
if estack.Len() > 0 {
resEl := estack.Pop()
res, err := resEl.Item().TryBool()
if err != nil {
return 0, fmt.Errorf("%w: invalid return value", ErrVerificationFailed)

50
pkg/vm/bench_test.go Normal file
View file

@ -0,0 +1,50 @@
package vm
import (
"encoding/base64"
"strconv"
"testing"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require"
)
func benchScript(t *testing.B, script []byte) {
for n := 0; n < t.N; n++ {
t.StopTimer()
vm := load(script)
t.StartTimer()
err := vm.Run()
t.StopTimer()
require.NoError(t, err)
t.StartTimer()
}
}
// Shared as is by @ixje once upon a time (compiled from Python).
func BenchmarkScriptFibonacci(t *testing.B) {
var script = []byte{87, 5, 0, 16, 112, 17, 113, 105, 104, 18, 192, 114, 16, 115, 34, 28, 104, 105, 158, 116, 106, 108, 75,
217, 48, 38, 5, 139, 34, 5, 207, 34, 3, 114, 105, 112, 108, 113, 107, 17, 158, 115, 107, 12, 2, 94, 1,
219, 33, 181, 36, 222, 106, 64}
benchScript(t, script)
}
func BenchmarkScriptNestedRefCount(t *testing.B) {
b64script := "whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUVFRQ=="
script, err := base64.StdEncoding.DecodeString(b64script)
require.NoError(t, err)
benchScript(t, script)
}
func BenchmarkScriptPushPop(t *testing.B) {
for _, i := range []int{4, 16, 128, 1024} {
t.Run(strconv.Itoa(i), func(t *testing.B) {
var script = make([]byte, i*2)
for p := 0; p < i; p++ {
script[p] = byte(opcode.PUSH1)
script[i+p] = byte(opcode.DROP)
}
benchScript(t, script)
})
}
}

View file

@ -35,8 +35,8 @@ type Context struct {
local *Slot
arguments *Slot
// Exception context stack pointer.
tryStack *Stack
// Exception context stack.
tryStack Stack
// Script hash of the prog.
scriptHash util.Uint160
@ -282,10 +282,11 @@ func (c *Context) IsDeployed() bool {
// getContextScriptHash returns script hash of the invocation stack element
// number n.
func (v *VM) getContextScriptHash(n int) util.Uint160 {
element := v.Istack().Peek(n)
if element == nil {
istack := v.Istack()
if istack.Len() <= n {
return util.Uint160{}
}
element := istack.Peek(n)
ctxIface := element.Value()
ctx := ctxIface.(*Context)
return ctx.ScriptHash()

View file

@ -20,7 +20,7 @@ func TestVM_Debug(t *testing.T) {
require.NoError(t, v.Run())
require.Equal(t, 5, v.Context().NextIP())
require.NoError(t, v.Run())
require.Equal(t, 1, v.estack.len)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
})
t.Run("StepInto", func(t *testing.T) {
@ -29,14 +29,14 @@ func TestVM_Debug(t *testing.T) {
require.Equal(t, 3, v.Context().NextIP())
require.NoError(t, v.StepOut())
require.Equal(t, 2, v.Context().NextIP())
require.Equal(t, 1, v.estack.len)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
})
t.Run("StepOver", func(t *testing.T) {
v := load(prog)
require.NoError(t, v.StepOver())
require.Equal(t, 2, v.Context().NextIP())
require.Equal(t, 1, v.estack.len)
require.Equal(t, 1, v.estack.Len())
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
})
}

View file

@ -9,70 +9,36 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Stack implementation for the neo-go virtual machine. The stack implements
// a double linked list where its semantics are first in first out.
// To simplify the implementation, internally a Stack s is implemented as a
// ring, such that &s.top is both the next element of the last element s.Back()
// and the previous element of the first element s.Top().
//
// s.Push(0)
// s.Push(1)
// s.Push(2)
//
// [ 2 ] > top
// [ 1 ]
// [ 0 ] > back
//
// s.Pop() > 2
//
// [ 1 ]
// [ 0 ]
// Stack implementation for the neo-go virtual machine. The stack with its LIFO
// semantics is emulated from simple slice where the top of the stack corresponds
// to the latest element of this slice. Pushes are appends to this slice, pops are
// slice resizes.
// Element represents an element in the double linked list (the stack),
// which will hold the underlying stackitem.Item.
// Element represents an element on the stack, technically it's a wrapper around
// stackitem.Item interface to provide some API simplification for VM.
type Element struct {
value stackitem.Item
next, prev *Element
stack *Stack
value stackitem.Item
}
// NewElement returns a new Element object, with its underlying value inferred
// to the corresponding type.
func NewElement(v interface{}) *Element {
return &Element{
value: stackitem.Make(v),
}
}
// Next returns the next element in the stack.
func (e *Element) Next() *Element {
if elem := e.next; e.stack != nil && elem != &e.stack.top {
return elem
}
return nil
}
// Prev returns the previous element in the stack.
func (e *Element) Prev() *Element {
if elem := e.prev; e.stack != nil && elem != &e.stack.top {
return elem
}
return nil
func NewElement(v interface{}) Element {
return Element{stackitem.Make(v)}
}
// Item returns Item contained in the element.
func (e *Element) Item() stackitem.Item {
func (e Element) Item() stackitem.Item {
return e.value
}
// Value returns value of the Item contained in the element.
func (e *Element) Value() interface{} {
func (e Element) Value() interface{} {
return e.value.Value()
}
// BigInt attempts to get the underlying value of the element as a big integer.
// Will panic if the assertion failed which will be caught by the VM.
func (e *Element) BigInt() *big.Int {
func (e Element) BigInt() *big.Int {
val, err := e.value.TryInteger()
if err != nil {
panic(err)
@ -82,7 +48,7 @@ func (e *Element) BigInt() *big.Int {
// Bool converts an underlying value of the element to a boolean if it's
// possible to do so, it will panic otherwise.
func (e *Element) Bool() bool {
func (e Element) Bool() bool {
b, err := e.value.TryBool()
if err != nil {
panic(err)
@ -92,7 +58,7 @@ func (e *Element) Bool() bool {
// Bytes attempts to get the underlying value of the element as a byte array.
// Will panic if the assertion failed which will be caught by the VM.
func (e *Element) Bytes() []byte {
func (e Element) Bytes() []byte {
bs, err := e.value.TryBytes()
if err != nil {
panic(err)
@ -102,7 +68,7 @@ func (e *Element) Bytes() []byte {
// BytesOrNil attempts to get the underlying value of the element as a byte array or nil.
// Will panic if the assertion failed which will be caught by the VM.
func (e *Element) BytesOrNil() []byte {
func (e Element) BytesOrNil() []byte {
if _, ok := e.value.(stackitem.Null); ok {
return nil
}
@ -115,7 +81,7 @@ func (e *Element) BytesOrNil() []byte {
// String attempts to get string from the element value.
// It is assumed to be use in interops and panics if string is not a valid UTF-8 byte sequence.
func (e *Element) String() string {
func (e Element) String() string {
s, err := stackitem.ToString(e.value)
if err != nil {
panic(err)
@ -126,7 +92,7 @@ func (e *Element) String() string {
// Array attempts to get the underlying value of the element as an array of
// other items. Will panic if the item type is different which will be caught
// by the VM.
func (e *Element) Array() []stackitem.Item {
func (e Element) Array() []stackitem.Item {
switch t := e.value.(type) {
case *stackitem.Array:
return t.Value().([]stackitem.Item)
@ -139,7 +105,7 @@ func (e *Element) Array() []stackitem.Item {
// Interop attempts to get the underlying value of the element
// as an interop item.
func (e *Element) Interop() *stackitem.Interop {
func (e Element) Interop() *stackitem.Interop {
switch t := e.value.(type) {
case *stackitem.Interop:
return t
@ -148,12 +114,11 @@ func (e *Element) Interop() *stackitem.Interop {
}
}
// Stack represents a Stack backed by a double linked list.
// Stack represents a Stack backed by a slice of Elements.
type Stack struct {
top Element
name string
len int
refs *refCounter
elems []Element
name string
refs *refCounter
}
// NewStack returns a new stack name by the given name.
@ -163,63 +128,46 @@ func NewStack(n string) *Stack {
func newStack(n string, refc *refCounter) *Stack {
s := new(Stack)
s.elems = make([]Element, 0, 16) // Most of uses are expected to fit into 16 elements.
initStack(s, n, refc)
return s
}
func initStack(s *Stack, n string, refc *refCounter) {
s.name = n
s.refs = refc
s.top.next = &s.top
s.top.prev = &s.top
s.Clear()
}
// Clear clears all elements on the stack and set its length to 0.
func (s *Stack) Clear() {
s.top.next = &s.top
s.top.prev = &s.top
s.len = 0
if s.elems != nil {
for _, el := range s.elems {
s.refs.Remove(el.value)
}
s.elems = s.elems[:0]
}
}
// Len returns the number of elements that are on the stack.
func (s *Stack) Len() int {
return s.len
}
// insert inserts the element after element (at) on the stack.
func (s *Stack) insert(e, at *Element) *Element {
// If we insert an element that is already popped from this stack,
// we need to clean it up, there are still pointers referencing to it.
if e.stack == s {
e = NewElement(e.value)
}
n := at.next
at.next = e
e.prev = at
e.next = n
n.prev = e
e.stack = s
s.len++
s.refs.Add(e.value)
return e
return len(s.elems)
}
// InsertAt inserts the given item (n) deep on the stack.
// Be very careful using it and _always_ check both e and n before invocation
// as it will silently do wrong things otherwise.
func (s *Stack) InsertAt(e *Element, n int) *Element {
before := s.Peek(n - 1)
if before == nil {
return nil
}
return s.insert(e, before)
// Be very careful using it and _always_ check n before invocation
// as it will panic otherwise.
func (s *Stack) InsertAt(e Element, n int) {
l := len(s.elems)
s.elems = append(s.elems, e)
copy(s.elems[l-n+1:], s.elems[l-n:l])
s.elems[l-n] = e
s.refs.Add(e.value)
}
// Push pushes the given element on the stack.
func (s *Stack) Push(e *Element) {
s.insert(e, &s.top)
func (s *Stack) Push(e Element) {
s.elems = append(s.elems, e)
s.refs.Add(e.value)
}
// PushVal pushes the given value on the stack. It will infer the
@ -228,63 +176,49 @@ func (s *Stack) PushVal(v interface{}) {
s.Push(NewElement(v))
}
// Pop removes and returns the element on top of the stack.
func (s *Stack) Pop() *Element {
return s.Remove(s.Top())
// Pop removes and returns the element on top of the stack. Panics if stack is
// empty.
func (s *Stack) Pop() Element {
l := len(s.elems)
e := s.elems[l-1]
s.elems = s.elems[:l-1]
s.refs.Remove(e.value)
return e
}
// Top returns the element on top of the stack. Nil if the stack
// is empty.
func (s *Stack) Top() *Element {
if s.len == 0 {
return nil
func (s *Stack) Top() Element {
if len(s.elems) == 0 {
return Element{}
}
return s.top.next
return s.elems[len(s.elems)-1]
}
// Back returns the element at the end of the stack. Nil if the stack
// is empty.
func (s *Stack) Back() *Element {
if s.len == 0 {
return nil
func (s *Stack) Back() Element {
if len(s.elems) == 0 {
return Element{}
}
return s.top.prev
return s.elems[0]
}
// Peek returns the element (n) far in the stack beginning from
// the top of the stack.
// n = 0 => will return the element on top of the stack.
func (s *Stack) Peek(n int) *Element {
i := 0
for e := s.Top(); e != nil; e = e.Next() {
if n == i {
return e
}
i++
}
return nil
// the top of the stack. For n == 0 it's effectively the same as Top,
// but it'll panic if the stack is empty.
func (s *Stack) Peek(n int) Element {
n = len(s.elems) - n - 1
return s.elems[n]
}
// RemoveAt removes the element (n) deep on the stack beginning
// from the top of the stack.
func (s *Stack) RemoveAt(n int) *Element {
return s.Remove(s.Peek(n))
}
// Remove removes and returns the given element from the stack.
func (s *Stack) Remove(e *Element) *Element {
if e == nil {
return nil
}
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks.
e.prev = nil // avoid memory leaks.
e.stack = nil
s.len--
// from the top of the stack. Panics if called with out of bounds n.
func (s *Stack) RemoveAt(n int) Element {
l := len(s.elems)
e := s.elems[l-1-n]
s.elems = append(s.elems[:l-1-n], s.elems[l-n:]...)
s.refs.Remove(e.value)
return e
}
@ -292,15 +226,9 @@ func (s *Stack) Remove(e *Element) *Element {
// Dup is used for copying elements on to the top of its own stack.
// s.Push(s.Peek(0)) // will result in unexpected behaviour.
// s.Push(s.Dup(0)) // is the correct approach.
func (s *Stack) Dup(n int) *Element {
func (s *Stack) Dup(n int) Element {
e := s.Peek(n)
if e == nil {
return nil
}
return &Element{
value: e.value.Dup(),
}
return Element{e.value.Dup()}
}
// Iter iterates over all the elements int the stack, starting from the top
@ -308,9 +236,9 @@ func (s *Stack) Dup(n int) *Element {
// s.Iter(func(elem *Element) {
// // do something with the element.
// })
func (s *Stack) Iter(f func(*Element)) {
for e := s.Top(); e != nil; e = e.Next() {
f(e)
func (s *Stack) Iter(f func(Element)) {
for i := len(s.elems) - 1; i >= 0; i-- {
f(s.elems[i])
}
}
@ -319,9 +247,9 @@ func (s *Stack) Iter(f func(*Element)) {
// s.IterBack(func(elem *Element) {
// // do something with the element.
// })
func (s *Stack) IterBack(f func(*Element)) {
for e := s.Back(); e != nil; e = e.Prev() {
f(e)
func (s *Stack) IterBack(f func(Element)) {
for i := 0; i < len(s.elems); i++ {
f(s.elems[i])
}
}
@ -330,37 +258,27 @@ func (s *Stack) Swap(n1, n2 int) error {
if n1 < 0 || n2 < 0 {
return errors.New("negative index")
}
if n1 >= s.len || n2 >= s.len {
l := len(s.elems)
if n1 >= l || n2 >= l {
return errors.New("too big index")
}
if n1 == n2 {
return nil
}
s.swap(n1, n2)
s.elems[l-n1-1], s.elems[l-n2-1] = s.elems[l-n2-1], s.elems[l-n1-1]
return nil
}
func (s *Stack) swap(n1, n2 int) {
a := s.Peek(n1)
b := s.Peek(n2)
a.value, b.value = b.value, a.value
}
// ReverseTop reverses top n items of the stack.
func (s *Stack) ReverseTop(n int) error {
l := len(s.elems)
if n < 0 {
return errors.New("negative index")
} else if n > s.len {
} else if n > l {
return errors.New("too big index")
} else if n <= 1 {
return nil
}
a, b := s.Peek(0), s.Peek(n-1)
for i := 0; i < n/2; i++ {
a.value, b.value = b.value, a.value
a = a.Next()
b = b.Prev()
for i, j := l-n, l-1; i <= j; i, j = i+1, j-1 {
s.elems[i], s.elems[j] = s.elems[j], s.elems[i]
}
return nil
}
@ -372,24 +290,16 @@ func (s *Stack) Roll(n int) error {
if n < 0 {
return errors.New("negative index")
}
if n >= s.len {
l := len(s.elems)
if n >= l {
return errors.New("too big index")
}
if n == 0 {
return nil
}
top := s.Peek(0)
e := s.Peek(n)
e.prev.next = e.next
e.next.prev = e.prev
top.prev = e
e.next = top
e.prev = &s.top
s.top.next = e
e := s.elems[l-1-n]
copy(s.elems[l-1-n:], s.elems[l-n:])
s.elems[l-1] = e
return nil
}
@ -398,10 +308,10 @@ func (s *Stack) Roll(n int) error {
func (s *Stack) PopSigElements() ([][]byte, error) {
var num int
var elems [][]byte
item := s.Pop()
if item == nil {
if s.Len() == 0 {
return nil, fmt.Errorf("nothing on the stack")
}
item := s.Pop()
switch item.value.(type) {
case *stackitem.Array:
num = len(item.Array())
@ -431,8 +341,8 @@ func (s *Stack) PopSigElements() ([][]byte, error) {
// ToArray converts stack to an array of stackitems with top item being the last.
func (s *Stack) ToArray() []stackitem.Item {
items := make([]stackitem.Item, 0, s.len)
s.IterBack(func(e *Element) {
items := make([]stackitem.Item, 0, len(s.elems))
s.IterBack(func(e Element) {
items = append(items, e.Item())
})
return items

View file

@ -76,9 +76,6 @@ func TestRemoveAt(t *testing.T) {
elem := s.RemoveAt(8)
assert.Equal(t, elems[1], elem)
assert.Nil(t, elem.prev)
assert.Nil(t, elem.next)
assert.Nil(t, elem.stack)
// Test if the pointers are moved.
assert.Equal(t, elems[0], s.Peek(8))
@ -147,8 +144,6 @@ func TestRemoveLastElement(t *testing.T) {
}
elem := s.RemoveAt(1)
assert.Equal(t, elems[0], elem)
assert.Nil(t, elem.prev)
assert.Nil(t, elem.next)
assert.Equal(t, 1, s.Len())
}
@ -163,7 +158,7 @@ func TestIterAfterRemove(t *testing.T) {
s.RemoveAt(0)
i := 0
s.Iter(func(elem *Element) {
s.Iter(func(_ Element) {
i++
})
assert.Equal(t, len(elems)-1, i)
@ -180,15 +175,16 @@ func TestIteration(t *testing.T) {
}
assert.Equal(t, len(elems), s.Len())
iteratedElems := make([]*Element, 0)
iteratedElems := make([]Element, 0)
s.Iter(func(elem *Element) {
s.Iter(func(elem Element) {
iteratedElems = append(iteratedElems, elem)
})
// Top to bottom order of iteration.
poppedElems := make([]*Element, 0)
for elem := s.Pop(); elem != nil; elem = s.Pop() {
poppedElems = append(poppedElems, elem)
poppedElems := make([]Element, 0)
for s.Len() != 0 {
poppedElems = append(poppedElems, s.Pop())
}
assert.Equal(t, poppedElems, iteratedElems)
}
@ -204,9 +200,9 @@ func TestBackIteration(t *testing.T) {
}
assert.Equal(t, len(elems), s.Len())
iteratedElems := make([]*Element, 0)
iteratedElems := make([]Element, 0)
s.IterBack(func(elem *Element) {
s.IterBack(func(elem Element) {
iteratedElems = append(iteratedElems, elem)
})
// Bottom to the top order of iteration.
@ -331,6 +327,25 @@ func TestRoll(t *testing.T) {
assert.Equal(t, int64(1), s.Pop().BigInt().Int64())
}
func TestInsertAt(t *testing.T) {
s := NewStack("stack")
s.PushVal(1)
s.PushVal(2)
s.PushVal(3)
s.PushVal(4)
s.PushVal(5)
e := s.Dup(1) // it's `4`
s.InsertAt(e, 3)
assert.Equal(t, int64(5), s.Peek(0).BigInt().Int64())
assert.Equal(t, int64(4), s.Peek(1).BigInt().Int64())
assert.Equal(t, int64(3), s.Peek(2).BigInt().Int64())
assert.Equal(t, int64(4), s.Peek(3).BigInt().Int64())
assert.Equal(t, int64(2), s.Peek(4).BigInt().Int64())
assert.Equal(t, int64(1), s.Peek(5).BigInt().Int64())
}
func TestPopSigElements(t *testing.T) {
s := NewStack("test")
@ -369,8 +384,8 @@ func TestPopSigElements(t *testing.T) {
assert.Equal(t, z, [][]byte{b1, b2})
}
func makeElements(n int) []*Element {
elems := make([]*Element, n)
func makeElements(n int) []Element {
elems := make([]Element, n)
for i := 0; i < n; i++ {
elems[i] = NewElement(i)
}

View file

@ -95,14 +95,12 @@ func (w *serContext) serialize(item Item) error {
switch t := item.(type) {
case *ByteArray:
w.data = append(w.data, byte(ByteArrayT))
data := t.Value().([]byte)
w.appendVarUint(uint64(len(data)))
w.data = append(w.data, data...)
w.appendVarUint(uint64(len(*t)))
w.data = append(w.data, *t...)
case *Buffer:
w.data = append(w.data, byte(BufferT))
data := t.Value().([]byte)
w.appendVarUint(uint64(len(data)))
w.data = append(w.data, data...)
w.appendVarUint(uint64(len(*t)))
w.data = append(w.data, *t...)
case Bool:
w.data = append(w.data, byte(BooleanT))
if t {
@ -112,10 +110,9 @@ func (w *serContext) serialize(item Item) error {
}
case *BigInteger:
w.data = append(w.data, byte(IntegerT))
v := t.Value().(*big.Int)
ln := len(w.data)
w.data = append(w.data, 0)
data := bigint.ToPreallocatedBytes(v, w.data[len(w.data):])
data := bigint.ToPreallocatedBytes((*big.Int)(t), w.data[len(w.data):])
w.data[ln] = byte(len(data))
w.data = append(w.data, data...)
case *Interop:

View file

@ -207,3 +207,15 @@ func BenchmarkEncodeBinary(b *testing.B) {
}
}
}
func BenchmarkSerializeSimple(b *testing.B) {
s := NewStruct(nil)
s.Append(Make(100500))
s.Append(Make("1aada0032aba1ef6d1f0")) // Mimicking uint160.
for i := 0; i < b.N; i++ {
_, err := Serialize(s)
if err != nil {
b.FailNow()
}
}
}

View file

@ -282,7 +282,7 @@ func (v *VM) LoadScriptWithFlags(b []byte, f callflag.CallFlag) {
ctx := NewContextWithParams(b, 0, -1, 0)
v.estack = newStack("evaluation", &v.refs)
ctx.estack = v.estack
ctx.tryStack = newStack("exception", nil)
initStack(&ctx.tryStack, "exception", nil)
ctx.callFlag = f
ctx.static = newSlot(&v.refs)
ctx.callingScriptHash = v.GetCurrentScriptHash()
@ -328,11 +328,10 @@ func (v *VM) Context() *Context {
// PopResult is used to pop the first item of the evaluation stack. This allows
// us to test compiler and vm in a bi-directional way.
func (v *VM) PopResult() interface{} {
e := v.estack.Pop()
if e != nil {
return e.Value()
if v.estack.Len() == 0 {
return nil
}
return nil
return v.estack.Pop().Value()
}
// Stack returns json formatted representation of the given stack.
@ -448,8 +447,8 @@ func (v *VM) StepOut() error {
v.state = NoneState
}
expSize := v.istack.len
for v.state == NoneState && v.istack.len >= expSize {
expSize := v.istack.Len()
for v.state == NoneState && v.istack.Len() >= expSize {
err = v.StepInto()
}
if v.state == NoneState {
@ -470,10 +469,10 @@ func (v *VM) StepOver() error {
v.state = NoneState
}
expSize := v.istack.len
expSize := v.istack.Len()
for {
err = v.StepInto()
if !(v.state == NoneState && v.istack.len > expSize) {
if !(v.state == NoneState && v.istack.Len() > expSize) {
break
}
}
@ -739,20 +738,20 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.Pop()
case opcode.NIP:
elem := v.estack.RemoveAt(1)
if elem == nil {
if v.estack.Len() < 2 {
panic("no second element found")
}
_ = v.estack.RemoveAt(1)
case opcode.XDROP:
n := int(v.estack.Pop().BigInt().Int64())
if n < 0 {
panic("invalid length")
}
e := v.estack.RemoveAt(n)
if e == nil {
if v.estack.Len() < n+1 {
panic("bad index")
}
_ = v.estack.RemoveAt(n)
case opcode.CLEAR:
v.estack.Clear()
@ -761,10 +760,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.Push(v.estack.Dup(0))
case opcode.OVER:
a := v.estack.Dup(1)
if a == nil {
if v.estack.Len() < 2 {
panic("no second element found")
}
a := v.estack.Dup(1)
v.estack.Push(a)
case opcode.PICK:
@ -772,20 +771,17 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if n < 0 {
panic("negative stack item returned")
}
a := v.estack.Dup(n)
if a == nil {
if v.estack.Len() < n+1 {
panic("no nth element found")
}
a := v.estack.Dup(n)
v.estack.Push(a)
case opcode.TUCK:
a := v.estack.Dup(0)
if a == nil {
panic("no top-level element found")
}
if v.estack.Len() < 2 {
panic("can't TUCK with a one-element stack")
panic("too short stack to TUCK")
}
a := v.estack.Dup(0)
v.estack.InsertAt(a, 2)
case opcode.SWAP:
@ -821,10 +817,8 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
// Bit operations.
case opcode.INVERT:
// inplace
e := v.estack.Peek(0)
i := e.BigInt()
e.value = stackitem.Make(new(big.Int).Not(i))
i := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Not(i))
case opcode.AND:
b := v.estack.Pop().BigInt()
@ -842,14 +836,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.PushVal(new(big.Int).Xor(b, a))
case opcode.EQUAL, opcode.NOTEQUAL:
if v.estack.Len() < 2 {
panic("need a pair of elements on the stack")
}
b := v.estack.Pop()
if b == nil {
panic("no top-level element found")
}
a := v.estack.Pop()
if a == nil {
panic("no second-to-the-top element found")
}
v.estack.PushVal(a.value.Equals(b.value) == (op == opcode.EQUAL))
// Numeric operations.
@ -1100,7 +1091,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
if index < 0 {
panic("invalid key")
}
v.estack.Push(&Element{value: t.Value().([]stackitem.MapElement)[index].Value.Dup()})
v.estack.Push(Element{value: t.Value().([]stackitem.MapElement)[index].Value.Dup()})
default:
arr := obj.Bytes()
if index < 0 || index >= len(arr) {
@ -1318,13 +1309,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
}
case opcode.NEWMAP:
v.estack.Push(&Element{value: stackitem.NewMap()})
v.estack.Push(Element{value: stackitem.NewMap()})
case opcode.KEYS:
item := v.estack.Pop()
if item == nil {
if v.estack.Len() == 0 {
panic("no argument")
}
item := v.estack.Pop()
m, ok := item.value.(*stackitem.Map)
if !ok {
@ -1338,10 +1329,10 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.PushVal(arr)
case opcode.VALUES:
item := v.estack.Pop()
if item == nil {
if v.estack.Len() == 0 {
panic("no argument")
}
item := v.estack.Pop()
var arr []stackitem.Item
switch t := item.value.(type) {
@ -1363,13 +1354,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.PushVal(arr)
case opcode.HASKEY:
if v.estack.Len() < 2 {
panic("not enough arguments")
}
key := v.estack.Pop()
validateMapKey(key)
c := v.estack.Pop()
if c == nil {
panic("no value found")
}
switch t := c.value.(type) {
case *stackitem.Array, *stackitem.Struct:
index := key.BigInt().Int64()
@ -1519,7 +1510,7 @@ func (v *VM) call(ctx *Context, offset int) {
newCtx.RetCount = -1
newCtx.local = nil
newCtx.arguments = nil
newCtx.tryStack = newStack("exception", nil)
initStack(&newCtx.tryStack, "exception", nil)
newCtx.NEF = ctx.NEF
v.istack.PushVal(newCtx)
v.Jump(newCtx, offset)
@ -1559,16 +1550,15 @@ func calcJumpOffset(ctx *Context, parameter []byte) (int, int, error) {
}
func (v *VM) handleException() {
pop := 0
ictxv := v.istack.Peek(0)
ictx := ictxv.Value().(*Context)
for ictx != nil {
e := ictx.tryStack.Peek(0)
for e != nil {
for pop := 0; pop < v.istack.Len(); pop++ {
ictxv := v.istack.Peek(pop)
ictx := ictxv.Value().(*Context)
for j := 0; j < ictx.tryStack.Len(); j++ {
e := ictx.tryStack.Peek(j)
ectx := e.Value().(*exceptionHandlingContext)
if ectx.State == eFinally || (ectx.State == eCatch && !ectx.HasFinally()) {
ictx.tryStack.Pop()
e = ictx.tryStack.Peek(0)
j = -1
continue
}
for i := 0; i < pop; i++ {
@ -1586,12 +1576,6 @@ func (v *VM) handleException() {
}
return
}
pop++
ictxv = ictxv.Next()
if ictxv == nil {
break
}
ictx = ictxv.Value().(*Context)
}
throwUnhandledException(v.uncaughtException)
}
@ -1753,17 +1737,18 @@ func makeArrayOfType(n int, typ stackitem.Type) []stackitem.Item {
return items
}
func validateMapKey(key *Element) {
if key == nil {
func validateMapKey(key Element) {
item := key.Item()
if item == nil {
panic("no key found")
}
if err := stackitem.IsValidMapKey(key.Item()); err != nil {
if err := stackitem.IsValidMapKey(item); err != nil {
panic(err)
}
}
func (v *VM) checkInvocationStackSize() {
if v.istack.len >= MaxInvocationStackSize {
if v.istack.Len() >= MaxInvocationStackSize {
panic("invocation stack is too big")
}
}
@ -1785,7 +1770,7 @@ func (v *VM) GetCallingScriptHash() util.Uint160 {
// GetEntryScriptHash implements ScriptHashGetter interface.
func (v *VM) GetEntryScriptHash() util.Uint160 {
return v.getContextScriptHash(v.istack.len - 1)
return v.getContextScriptHash(v.istack.Len() - 1)
}
// GetCurrentScriptHash implements ScriptHashGetter interface.

View file

@ -1176,7 +1176,7 @@ func TestPICKITEMDupMap(t *testing.T) {
vm := load(prog)
m := stackitem.NewMap()
m.Add(stackitem.Make(42), stackitem.Make(-1))
vm.estack.Push(&Element{value: m})
vm.estack.Push(Element{value: m})
runVM(t, vm)
assert.Equal(t, 2, vm.estack.Len())
assert.Equal(t, int64(1), vm.estack.Pop().BigInt().Int64())
@ -1245,7 +1245,7 @@ func TestSETITEMBigMapGood(t *testing.T) {
for i := 0; i < MaxStackSize-3; i++ {
m.Add(stackitem.Make(i), stackitem.Make(i))
}
vm.estack.Push(&Element{value: m})
vm.estack.Push(Element{value: m})
vm.estack.PushVal(0)
vm.estack.PushVal(0)
@ -1274,7 +1274,7 @@ func TestKEYSMap(t *testing.T) {
m := stackitem.NewMap()
m.Add(stackitem.Make(5), stackitem.Make(6))
m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make(6))
vm.estack.Push(&Element{value: m})
vm.estack.Push(Element{value: m})
runVM(t, vm)
assert.Equal(t, 1, vm.estack.Len())
@ -1298,7 +1298,7 @@ func TestVALUESMap(t *testing.T) {
m := stackitem.NewMap()
m.Add(stackitem.Make(5), stackitem.Make([]byte{2, 3}))
m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make([]stackitem.Item{}))
vm.estack.Push(&Element{value: m})
vm.estack.Push(Element{value: m})
runVM(t, vm)
assert.Equal(t, 1, vm.estack.Len())
@ -1880,7 +1880,7 @@ func TestREVERSEITEMSGoodStruct(t *testing.T) {
for i := range elements {
arr[i] = stackitem.Make(elements[i])
}
vm.estack.Push(&Element{value: stackitem.NewStruct(arr)})
vm.estack.Push(Element{value: stackitem.NewStruct(arr)})
runVM(t, vm)
assert.Equal(t, 2, vm.estack.Len())
@ -1944,8 +1944,8 @@ func TestREMOVEMap(t *testing.T) {
m := stackitem.NewMap()
m.Add(stackitem.Make(5), stackitem.Make(3))
m.Add(stackitem.Make([]byte{0, 1}), stackitem.Make([]byte{2, 3}))
vm.estack.Push(&Element{value: m})
vm.estack.Push(&Element{value: m})
vm.estack.Push(Element{value: m})
vm.estack.Push(Element{value: m})
vm.estack.PushVal(stackitem.Make(5))
runVM(t, vm)