a3892aa662
PushVal is very convenient, but type switch is somewhat expensive. name old time/op new time/op delta ScriptFibonacci-8 736µs ± 1% 602µs ± 1% -18.21% (p=0.000 n=9+10) ScriptNestedRefCount-8 1.08ms ± 2% 0.96ms ± 1% -11.13% (p=0.000 n=10+10) ScriptPushPop/4-8 1.48µs ± 3% 1.35µs ± 2% -9.14% (p=0.000 n=10+9) ScriptPushPop/16-8 3.59µs ± 1% 3.38µs ± 1% -6.01% (p=0.000 n=10+10) ScriptPushPop/128-8 23.7µs ± 1% 22.6µs ± 1% -4.39% (p=0.000 n=10+8) ScriptPushPop/1024-8 176µs ± 2% 167µs ± 3% -5.24% (p=0.000 n=9+10) name old alloc/op new alloc/op delta ScriptFibonacci-8 123kB ± 0% 114kB ± 0% -6.88% (p=0.000 n=10+9) ScriptNestedRefCount-8 266kB ± 0% 241kB ± 0% -9.23% (p=0.000 n=8+10) ScriptPushPop/4-8 160B ± 0% 160B ± 0% ~ (all equal) ScriptPushPop/16-8 640B ± 0% 640B ± 0% ~ (all equal) ScriptPushPop/128-8 8.70kB ± 0% 8.70kB ± 0% ~ (all equal) ScriptPushPop/1024-8 73.2kB ± 0% 73.2kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta ScriptFibonacci-8 3.53k ± 0% 3.17k ± 0% -9.98% (p=0.000 n=10+10) ScriptNestedRefCount-8 11.8k ± 0% 10.7k ± 0% -8.70% (p=0.000 n=10+10) ScriptPushPop/4-8 8.00 ± 0% 8.00 ± 0% ~ (all equal) ScriptPushPop/16-8 32.0 ± 0% 32.0 ± 0% ~ (all equal) ScriptPushPop/128-8 259 ± 0% 259 ± 0% ~ (all equal) ScriptPushPop/1024-8 2.05k ± 0% 2.05k ± 0% ~ (all equal)
299 lines
7.6 KiB
Go
299 lines
7.6 KiB
Go
package vm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// Context represents the current execution context of the VM.
|
|
type Context struct {
|
|
// Instruction pointer.
|
|
ip int
|
|
|
|
// The next instruction pointer.
|
|
nextip int
|
|
|
|
// The raw program script.
|
|
prog []byte
|
|
|
|
// Breakpoints.
|
|
breakPoints []int
|
|
|
|
// Evaluation stack pointer.
|
|
estack *Stack
|
|
|
|
static *Slot
|
|
local *Slot
|
|
arguments *Slot
|
|
|
|
// Exception context stack.
|
|
tryStack Stack
|
|
|
|
// Script hash of the prog.
|
|
scriptHash util.Uint160
|
|
|
|
// Caller's contract script hash.
|
|
callingScriptHash util.Uint160
|
|
|
|
// Call flags this context was created with.
|
|
callFlag callflag.CallFlag
|
|
|
|
// ParamCount specifies number of parameters.
|
|
ParamCount int
|
|
// RetCount specifies number of return values.
|
|
RetCount int
|
|
// NEF represents NEF file for the current contract.
|
|
NEF *nef.File
|
|
}
|
|
|
|
// CheckReturnState represents possible states of stack after opcode.RET was processed.
|
|
type CheckReturnState byte
|
|
|
|
const (
|
|
// NoCheck performs no return values check.
|
|
NoCheck CheckReturnState = 0
|
|
// EnsureIsEmpty checks that stack is empty and panics if not.
|
|
EnsureIsEmpty CheckReturnState = 1
|
|
// EnsureNotEmpty checks that stack contains not more than 1 element and panics if not.
|
|
// It pushes stackitem.Null on stack in case if there's no elements.
|
|
EnsureNotEmpty CheckReturnState = 2
|
|
)
|
|
|
|
var errNoInstParam = errors.New("failed to read instruction parameter")
|
|
|
|
// NewContext returns a new Context object.
|
|
func NewContext(b []byte) *Context {
|
|
return NewContextWithParams(b, 0, -1, 0)
|
|
}
|
|
|
|
// NewContextWithParams creates new Context objects using script, parameter count,
|
|
// return value count and initial position in script.
|
|
func NewContextWithParams(b []byte, pcount int, rvcount int, pos int) *Context {
|
|
return &Context{
|
|
prog: b,
|
|
ParamCount: pcount,
|
|
RetCount: rvcount,
|
|
nextip: pos,
|
|
}
|
|
}
|
|
|
|
// Estack returns the evaluation stack of c.
|
|
func (c *Context) Estack() *Stack {
|
|
return c.estack
|
|
}
|
|
|
|
// NextIP returns next instruction pointer.
|
|
func (c *Context) NextIP() int {
|
|
return c.nextip
|
|
}
|
|
|
|
// Next returns the next instruction to execute with its parameter if any.
|
|
// The parameter is not copied and shouldn't be written to. After its invocation
|
|
// the instruction pointer points to the instruction being returned.
|
|
func (c *Context) Next() (opcode.Opcode, []byte, error) {
|
|
var err error
|
|
|
|
c.ip = c.nextip
|
|
if c.ip >= len(c.prog) {
|
|
return opcode.RET, nil, nil
|
|
}
|
|
|
|
var instrbyte = c.prog[c.ip]
|
|
instr := opcode.Opcode(instrbyte)
|
|
if !opcode.IsValid(instr) {
|
|
return instr, nil, fmt.Errorf("incorrect opcode %s", instr.String())
|
|
}
|
|
c.nextip++
|
|
|
|
var numtoread int
|
|
switch instr {
|
|
case opcode.PUSHDATA1:
|
|
if c.nextip >= len(c.prog) {
|
|
err = errNoInstParam
|
|
} else {
|
|
numtoread = int(c.prog[c.nextip])
|
|
c.nextip++
|
|
}
|
|
case opcode.PUSHDATA2:
|
|
if c.nextip+1 >= len(c.prog) {
|
|
err = errNoInstParam
|
|
} else {
|
|
numtoread = int(binary.LittleEndian.Uint16(c.prog[c.nextip : c.nextip+2]))
|
|
c.nextip += 2
|
|
}
|
|
case opcode.PUSHDATA4:
|
|
if c.nextip+3 >= len(c.prog) {
|
|
err = errNoInstParam
|
|
} else {
|
|
var n = binary.LittleEndian.Uint32(c.prog[c.nextip : c.nextip+4])
|
|
if n > stackitem.MaxSize {
|
|
return instr, nil, errors.New("parameter is too big")
|
|
}
|
|
numtoread = int(n)
|
|
c.nextip += 4
|
|
}
|
|
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.JMPEQ, opcode.JMPNE,
|
|
opcode.JMPGT, opcode.JMPGE, opcode.JMPLT, opcode.JMPLE,
|
|
opcode.CALL, opcode.ISTYPE, opcode.CONVERT, opcode.NEWARRAYT,
|
|
opcode.ENDTRY,
|
|
opcode.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
|
|
numtoread = 1
|
|
case opcode.INITSLOT, opcode.TRY, opcode.CALLT:
|
|
numtoread = 2
|
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
|
opcode.ENDTRYL,
|
|
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
|
|
numtoread = 4
|
|
case opcode.TRYL:
|
|
numtoread = 8
|
|
default:
|
|
if instr <= opcode.PUSHINT256 {
|
|
numtoread = 1 << instr
|
|
} else {
|
|
// No parameters, can just return.
|
|
return instr, nil, nil
|
|
}
|
|
}
|
|
if c.nextip+numtoread-1 >= len(c.prog) {
|
|
err = errNoInstParam
|
|
}
|
|
if err != nil {
|
|
return instr, nil, err
|
|
}
|
|
parameter := c.prog[c.nextip : c.nextip+numtoread]
|
|
c.nextip += numtoread
|
|
return instr, parameter, nil
|
|
}
|
|
|
|
// IP returns current instruction offset in the context script.
|
|
func (c *Context) IP() int {
|
|
return c.ip
|
|
}
|
|
|
|
// LenInstr returns the number of instructions loaded.
|
|
func (c *Context) LenInstr() int {
|
|
return len(c.prog)
|
|
}
|
|
|
|
// CurrInstr returns the current instruction and opcode.
|
|
func (c *Context) CurrInstr() (int, opcode.Opcode) {
|
|
return c.ip, opcode.Opcode(c.prog[c.ip])
|
|
}
|
|
|
|
// NextInstr returns the next instruction and opcode.
|
|
func (c *Context) NextInstr() (int, opcode.Opcode) {
|
|
op := opcode.RET
|
|
if c.nextip < len(c.prog) {
|
|
op = opcode.Opcode(c.prog[c.nextip])
|
|
}
|
|
return c.nextip, op
|
|
}
|
|
|
|
// Copy returns an new exact copy of c.
|
|
func (c *Context) Copy() *Context {
|
|
ctx := new(Context)
|
|
*ctx = *c
|
|
return ctx
|
|
}
|
|
|
|
// GetCallFlags returns calling flags context was created with.
|
|
func (c *Context) GetCallFlags() callflag.CallFlag {
|
|
return c.callFlag
|
|
}
|
|
|
|
// Program returns the loaded program.
|
|
func (c *Context) Program() []byte {
|
|
return c.prog
|
|
}
|
|
|
|
// ScriptHash returns a hash of the script in the current context.
|
|
func (c *Context) ScriptHash() util.Uint160 {
|
|
if c.scriptHash.Equals(util.Uint160{}) {
|
|
c.scriptHash = hash.Hash160(c.prog)
|
|
}
|
|
return c.scriptHash
|
|
}
|
|
|
|
// Value implements stackitem.Item interface.
|
|
func (c *Context) Value() interface{} {
|
|
return c
|
|
}
|
|
|
|
// Dup implements stackitem.Item interface.
|
|
func (c *Context) Dup() stackitem.Item {
|
|
return c
|
|
}
|
|
|
|
// TryBool implements stackitem.Item interface.
|
|
func (c *Context) TryBool() (bool, error) { panic("can't convert Context to Bool") }
|
|
|
|
// TryBytes implements stackitem.Item interface.
|
|
func (c *Context) TryBytes() ([]byte, error) {
|
|
return nil, errors.New("can't convert Context to ByteArray")
|
|
}
|
|
|
|
// TryInteger implements stackitem.Item interface.
|
|
func (c *Context) TryInteger() (*big.Int, error) {
|
|
return nil, errors.New("can't convert Context to Integer")
|
|
}
|
|
|
|
// Type implements stackitem.Item interface.
|
|
func (c *Context) Type() stackitem.Type { panic("Context cannot appear on evaluation stack") }
|
|
|
|
// Convert implements stackitem.Item interface.
|
|
func (c *Context) Convert(_ stackitem.Type) (stackitem.Item, error) {
|
|
panic("Context cannot be converted to anything")
|
|
}
|
|
|
|
// Equals implements stackitem.Item interface.
|
|
func (c *Context) Equals(s stackitem.Item) bool {
|
|
return c == s
|
|
}
|
|
|
|
func (c *Context) atBreakPoint() bool {
|
|
for _, n := range c.breakPoints {
|
|
if n == c.nextip {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Context) String() string {
|
|
return "execution context"
|
|
}
|
|
|
|
// IsDeployed returns whether this context contains deployed contract.
|
|
func (c *Context) IsDeployed() bool {
|
|
return c.NEF != nil
|
|
}
|
|
|
|
// getContextScriptHash returns script hash of the invocation stack element
|
|
// number n.
|
|
func (v *VM) getContextScriptHash(n int) util.Uint160 {
|
|
istack := v.Istack()
|
|
if istack.Len() <= n {
|
|
return util.Uint160{}
|
|
}
|
|
element := istack.Peek(n)
|
|
ctx := element.value.(*Context)
|
|
return ctx.ScriptHash()
|
|
}
|
|
|
|
// PushContextScriptHash pushes to evaluation stack the script hash of the
|
|
// invocation stack element number n.
|
|
func (v *VM) PushContextScriptHash(n int) error {
|
|
h := v.getContextScriptHash(n)
|
|
v.Estack().PushItem(stackitem.NewByteArray(h.BytesBE()))
|
|
return nil
|
|
}
|