forked from TrueCloudLab/neoneo-go
8d4dd2d2e1
This allows easier reuse of opcodes and in some cases allows to eliminate dependencies on the whole vm package, like in compiler that only needs opcodes and doesn't care about VM for any other purpose. And yes, they're opcodes because an instruction is a whole thing with operands, that's what context.Next() returns.
148 lines
3.1 KiB
Go
148 lines
3.1 KiB
Go
package vm
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/io"
|
|
"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
|
|
}
|
|
|
|
// 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) {
|
|
c.ip = c.nextip
|
|
if c.ip >= len(c.prog) {
|
|
return opcode.RET, nil, nil
|
|
}
|
|
r := io.NewBinReaderFromBuf(c.prog[c.ip:])
|
|
|
|
var instrbyte byte
|
|
r.ReadLE(&instrbyte)
|
|
instr := opcode.Opcode(instrbyte)
|
|
c.nextip++
|
|
|
|
var numtoread int
|
|
switch instr {
|
|
case opcode.PUSHDATA1, opcode.SYSCALL:
|
|
var n byte
|
|
r.ReadLE(&n)
|
|
numtoread = int(n)
|
|
c.nextip++
|
|
case opcode.PUSHDATA2:
|
|
var n uint16
|
|
r.ReadLE(&n)
|
|
numtoread = int(n)
|
|
c.nextip += 2
|
|
case opcode.PUSHDATA4:
|
|
var n uint32
|
|
r.ReadLE(&n)
|
|
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
|
|
}
|
|
}
|
|
parameter := make([]byte, numtoread)
|
|
r.ReadLE(parameter)
|
|
if r.Err != nil {
|
|
return instr, nil, errors.New("failed to read instruction parameter")
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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"
|
|
}
|