forked from TrueCloudLab/neoneo-go
c9257c3de4
Creating a new BinReader for every instruction is a bit too much and it adds about 1% overhead on block import (and actually is quite visible in the VM profiling statistics). So use a bit more ugly but efficient method.
173 lines
3.8 KiB
Go
173 lines
3.8 KiB
Go
package vm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
|
"github.com/CityOfZion/neo-go/pkg/util"
|
|
"github.com/CityOfZion/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
|
|
|
|
// Script hash of the prog.
|
|
scriptHash util.Uint160
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
// 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.PUSHDATA1, opcode.SYSCALL:
|
|
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.CALL, opcode.CALLED, opcode.CALLEDT:
|
|
numtoread = 2
|
|
case opcode.CALLI:
|
|
numtoread = 4
|
|
case opcode.APPCALL, opcode.TAILCALL:
|
|
numtoread = 20
|
|
case opcode.CALLE, opcode.CALLET:
|
|
numtoread = 22
|
|
default:
|
|
if instr >= opcode.PUSHBYTES1 && instr <= opcode.PUSHBYTES75 {
|
|
numtoread = int(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
|
|
}
|
|
|
|
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"
|
|
}
|