neo-go/pkg/vm/stack.go
Roman Khimov 7243754a0d *: drop open-coded slice reversing
Less code, same performance.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2024-08-24 22:41:48 +03:00

377 lines
9.2 KiB
Go

package vm
import (
"encoding/json"
"errors"
"fmt"
"math/big"
"slices"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Stack implementation for the neo-go virtual machine. The stack with its LIFO
// semantics is emulated from a 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 on the stack. Technically, it's a wrapper around
// stackitem.Item interface to provide some API simplification for the VM.
type Element struct {
value stackitem.Item
}
// NewElement returns a new Element object, with its underlying value inferred
// to the corresponding type.
func NewElement(v any) Element {
return Element{stackitem.Make(v)}
}
// Item returns the Item contained in the element.
func (e Element) Item() stackitem.Item {
return e.value
}
// Value returns the value of the Item contained in the element.
func (e Element) Value() any {
return e.value.Value()
}
// BigInt attempts to get the underlying value of the element as a big integer.
// It will panic if the assertion has failed, which will be caught by the VM.
func (e Element) BigInt() *big.Int {
val, err := e.value.TryInteger()
if err != nil {
panic(err)
}
return val
}
// Bool converts the underlying value of the element to a boolean if it's
// possible to do so. Otherwise, it will panic.
func (e Element) Bool() bool {
b, err := e.value.TryBool()
if err != nil {
panic(err)
}
return b
}
// Bytes attempts to get the underlying value of the element as a byte array.
// It will panic if the assertion has failed, which will be caught by the VM.
func (e Element) Bytes() []byte {
bs, err := e.value.TryBytes()
if err != nil {
panic(err)
}
return bs
}
// BytesOrNil attempts to get the underlying value of the element as a byte array or nil.
// It will panic if the assertion has failed, which will be caught by the VM.
func (e Element) BytesOrNil() []byte {
if _, ok := e.value.(stackitem.Null); ok {
return nil
}
bs, err := e.value.TryBytes()
if err != nil {
panic(err)
}
return bs
}
// String attempts to get a string from the element value.
// It is assumed to be used in interops and panics if the string is not a valid UTF-8 byte sequence.
func (e Element) String() string {
s, err := stackitem.ToString(e.value)
if err != nil {
panic(err)
}
return s
}
// Array attempts to get the underlying value of the element as an array of
// other items. It will panic if the item type is different, which will be caught
// by the VM.
func (e Element) Array() []stackitem.Item {
switch t := e.value.(type) {
case *stackitem.Array:
return t.Value().([]stackitem.Item)
case *stackitem.Struct:
return t.Value().([]stackitem.Item)
default:
panic("element is not an array")
}
}
// Interop attempts to get the underlying value of the element
// as an interop item.
func (e Element) Interop() *stackitem.Interop {
switch t := e.value.(type) {
case *stackitem.Interop:
return t
default:
panic("element is not an interop")
}
}
// Stack represents a Stack backed by a slice of Elements.
type Stack struct {
elems []Element
name string
refs *refCounter
}
// NewStack returns a new stack name by the given name.
func NewStack(n string) *Stack {
return newStack(n, newRefCounter())
}
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 subStack(old *Stack) *Stack {
s := new(Stack)
*s = *old
s.elems = s.elems[len(s.elems):]
return s
}
func initStack(s *Stack, n string, refc *refCounter) {
s.name = n
s.refs = refc
s.Clear()
}
// Clear clears all elements on the stack and set its length to 0.
func (s *Stack) Clear() {
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 len(s.elems)
}
// InsertAt inserts the given item (n) deep on the stack.
// 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.elems = append(s.elems, e)
s.refs.Add(e.value)
}
// PushItem pushes an Item to the stack.
func (s *Stack) PushItem(i stackitem.Item) {
s.Push(Element{i})
}
// PushVal pushes the given value on the stack. It will infer the
// underlying Item to its corresponding type.
func (s *Stack) PushVal(v any) {
s.Push(NewElement(v))
}
// Pop removes and returns the element on top of the stack. It panics if the 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 len(s.elems) == 0 {
return Element{}
}
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 len(s.elems) == 0 {
return Element{}
}
return s.elems[0]
}
// Peek returns the element (n) far in the stack beginning from
// 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. It 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
}
// Dup duplicates and returns the element at position n.
// Dup is used for copying elements on the top of its own stack.
//
// s.Push(s.Peek(0)) // will result in unexpected behavior.
// s.Push(s.Dup(0)) // is the correct approach.
func (s *Stack) Dup(n int) Element {
e := s.Peek(n)
return Element{e.value.Dup()}
}
// Iter iterates over all elements int the stack, starting from the top
// of the stack.
//
// s.Iter(func(elem *Element) {
// // do something with the element.
// })
func (s *Stack) Iter(f func(Element)) {
for i := len(s.elems) - 1; i >= 0; i-- {
f(s.elems[i])
}
}
// IterBack iterates over all elements of the stack, starting from the bottom
// of the stack.
//
// s.IterBack(func(elem *Element) {
// // do something with the element.
// })
func (s *Stack) IterBack(f func(Element)) {
for i := 0; i < len(s.elems); i++ {
f(s.elems[i])
}
}
// Swap swaps two elements on the stack without popping and pushing them.
func (s *Stack) Swap(n1, n2 int) error {
if n1 < 0 || n2 < 0 {
return errors.New("negative index")
}
l := len(s.elems)
if n1 >= l || n2 >= l {
return errors.New("too big index")
}
s.elems[l-n1-1], s.elems[l-n2-1] = s.elems[l-n2-1], s.elems[l-n1-1]
return nil
}
// 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 > l {
return errors.New("too big index")
} else if n <= 1 {
return nil
}
slices.Reverse(s.elems[l-n : l])
return nil
}
// Roll brings an item with the given index to the top of the stack moving all
// other elements down accordingly. It does all of that without popping and
// pushing elements.
func (s *Stack) Roll(n int) error {
if n < 0 {
return errors.New("negative index")
}
l := len(s.elems)
if n >= l {
return errors.New("too big index")
}
if n == 0 {
return nil
}
e := s.elems[l-1-n]
copy(s.elems[l-1-n:], s.elems[l-n:])
s.elems[l-1] = e
return nil
}
// PopSigElements pops keys or signatures from the stack as needed for
// CHECKMULTISIG.
func (s *Stack) PopSigElements() ([][]byte, error) {
var num int
var elems [][]byte
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())
if num < 1 {
return nil, fmt.Errorf("less than one element in the array")
}
elems = make([][]byte, num)
for k, v := range item.Array() {
b, ok := v.Value().([]byte)
if !ok {
return nil, fmt.Errorf("bad element %s", v.String())
}
elems[k] = b
}
default:
num = int(item.BigInt().Int64())
if num < 1 || num > s.Len() {
return nil, fmt.Errorf("wrong number of elements: need %d, have %d", num, s.Len())
}
elems = make([][]byte, num)
for i := 0; i < num; i++ {
elems[i] = s.Pop().Bytes()
}
}
return elems, nil
}
// ToArray converts the stack to an array of stackitems with the top item being the last.
func (s *Stack) ToArray() []stackitem.Item {
items := make([]stackitem.Item, 0, len(s.elems))
s.IterBack(func(e Element) {
items = append(items, e.Item())
})
return items
}
// MarshalJSON implements the JSON marshalling interface.
func (s *Stack) MarshalJSON() ([]byte, error) {
items := s.ToArray()
arr := make([]json.RawMessage, len(items))
for i := range items {
data, err := stackitem.ToJSONWithTypes(items[i])
if err == nil {
arr[i] = data
}
}
return json.Marshal(arr)
}