neo-go/pkg/vm/context.go
Evgenii Stratonikov 0cb6dc47e4 vm: implement slot-related opcodes
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.
2020-05-12 16:23:08 +03:00

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
}