0cb6dc47e4
1. Slot is a new mechanism for storing variables during execution which is more convenient than alt.stack. This commit implements support for slot opcodes in both vm and compiler. 2. Remove old alt.stack opcodes. 3. Do not process globals at the start of every function, but instead load them single time at main.
254 lines
6.1 KiB
Go
254 lines
6.1 KiB
Go
package vm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"math/big"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
)
|
|
|
|
// 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
|
|
|
|
// Return value count, -1 is unspecified.
|
|
rvcount int
|
|
|
|
// Evaluation stack pointer.
|
|
estack *Stack
|
|
|
|
// Alt stack pointer.
|
|
astack *Stack
|
|
|
|
local *Slot
|
|
arguments *Slot
|
|
|
|
// Script hash of the prog.
|
|
scriptHash util.Uint160
|
|
|
|
// Whether it's allowed to make dynamic calls from this context.
|
|
hasDynamicInvoke bool
|
|
}
|
|
|
|
var errNoInstParam = errors.New("failed to read instruction parameter")
|
|
|
|
// NewContext returns a new Context object.
|
|
func NewContext(b []byte) *Context {
|
|
return &Context{
|
|
prog: b,
|
|
breakPoints: []int{},
|
|
rvcount: -1,
|
|
}
|
|
}
|
|
|
|
// 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. 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)
|
|
c.nextip++
|
|
|
|
var numtoread int
|
|
switch instr {
|
|
case opcode.OLDPUSH1:
|
|
// OLDPUSH1 is used during transition to NEO3 in verification scripts.
|
|
// FIXME remove #927
|
|
if len(c.prog) == 1 {
|
|
return opcode.PUSH1, nil, nil
|
|
}
|
|
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 > MaxItemSize {
|
|
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.INITSSLOT, opcode.LDSFLD, opcode.STSFLD, opcode.LDARG, opcode.STARG, opcode.LDLOC, opcode.STLOC:
|
|
numtoread = 1
|
|
case opcode.INITSLOT:
|
|
numtoread = 2
|
|
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, opcode.JMPEQL, opcode.JMPNEL,
|
|
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLTL, opcode.JMPLEL,
|
|
opcode.CALLL, opcode.SYSCALL, opcode.PUSHA:
|
|
numtoread = 4
|
|
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 := make([]byte, numtoread)
|
|
copy(parameter, c.prog[c.nextip:c.nextip+numtoread])
|
|
c.nextip += numtoread
|
|
return instr, parameter, nil
|
|
}
|
|
|
|
// IP returns the absolute instruction without taking 0 into account.
|
|
// If that program starts the ip = 0 but IP() will return 1, cause its
|
|
// the first instruction.
|
|
func (c *Context) IP() int {
|
|
return c.ip + 1
|
|
}
|
|
|
|
// 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])
|
|
}
|
|
|
|
// Copy returns an new exact copy of c.
|
|
func (c *Context) Copy() *Context {
|
|
ctx := new(Context)
|
|
*ctx = *c
|
|
return ctx
|
|
}
|
|
|
|
// 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 interface.
|
|
func (c *Context) Value() interface{} {
|
|
return c
|
|
}
|
|
|
|
// Dup implements StackItem interface.
|
|
func (c *Context) Dup() StackItem {
|
|
return c
|
|
}
|
|
|
|
// Bool implements StackItem interface.
|
|
func (c *Context) Bool() bool { panic("can't convert Context to Bool") }
|
|
|
|
// TryBytes implements StackItem interface.
|
|
func (c *Context) TryBytes() ([]byte, error) {
|
|
return nil, errors.New("can't convert Context to ByteArray")
|
|
}
|
|
|
|
// TryInteger implements StackItem interface.
|
|
func (c *Context) TryInteger() (*big.Int, error) {
|
|
return nil, errors.New("can't convert Context to Integer")
|
|
}
|
|
|
|
// Type implements StackItem interface.
|
|
func (c *Context) Type() StackItemType { panic("Context cannot appear on evaluation stack") }
|
|
|
|
// Convert implements StackItem interface.
|
|
func (c *Context) Convert(_ StackItemType) (StackItem, error) {
|
|
panic("Context cannot be converted to anything")
|
|
}
|
|
|
|
// Equals implements StackItem interface.
|
|
func (c *Context) Equals(s StackItem) bool {
|
|
return c == s
|
|
}
|
|
|
|
// ToContractParameter implements StackItem interface.
|
|
func (c *Context) ToContractParameter(map[StackItem]bool) smartcontract.Parameter {
|
|
return smartcontract.Parameter{
|
|
Type: smartcontract.StringType,
|
|
Value: c.String(),
|
|
}
|
|
}
|
|
|
|
func (c *Context) atBreakPoint() bool {
|
|
for _, n := range c.breakPoints {
|
|
if n == c.ip {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Context) String() string {
|
|
return "execution context"
|
|
}
|
|
|
|
// 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 {
|
|
return util.Uint160{}
|
|
}
|
|
ctxIface := element.Value()
|
|
ctx := ctxIface.(*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().PushVal(h.BytesBE())
|
|
return nil
|
|
}
|