package scparser import ( "encoding/binary" "errors" "fmt" "github.com/tutus-one/tutus-chain/pkg/vm/opcode" "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" ) var errNoInstParam = errors.New("failed to read instruction parameter") // Context represents bytecode parser context. type Context struct { // Instruction pointer. ip int // The next instruction pointer. nextip int // Program. prog []byte } // NewContext creates a new parsing context for the given program with the // given initial offset. func NewContext(b []byte, pos int) *Context { return &Context{ nextip: pos, prog: b, } } // NextIP returns the next instruction pointer. func (c *Context) NextIP() int { return c.nextip } // Jump unconditionally moves the next instruction pointer to the specified location. func (c *Context) Jump(pos int) { if pos < 0 || pos >= len(c.prog) { panic("instruction offset is out of range") } c.nextip = pos } // 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 returned. func (c *Context) Next() (opcode.Opcode, []byte, error) { var err error c.ip = c.nextip prog := c.prog if c.ip >= len(prog) { return opcode.RET, nil, nil } var instrbyte = 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(prog) { err = errNoInstParam } else { numtoread = int(prog[c.nextip]) c.nextip++ } case opcode.PUSHDATA2: if c.nextip+1 >= len(prog) { err = errNoInstParam } else { numtoread = int(binary.LittleEndian.Uint16(prog[c.nextip : c.nextip+2])) c.nextip += 2 } case opcode.PUSHDATA4: if c.nextip+3 >= len(prog) { err = errNoInstParam } else { var n = binary.LittleEndian.Uint32(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(prog) { err = errNoInstParam } if err != nil { return instr, nil, err } parameter := prog[c.nextip : c.nextip+numtoread] c.nextip += numtoread return instr, parameter, nil } // IP returns the 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 offset and opcode at that position. // If the next instruction offset points past the end of the program the opcode // returned is [opcode.RET]. 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 } // CalcJumpOffset returns an absolute (wrt bytecode loaded into [Context]) // and a relative (wrt current [Context.IP]) offset of JMP/CALL/TRY // instructions (and their long forms). 1 or 4 byte parameters are accepted. func (c *Context) CalcJumpOffset(parameter []byte) (int, int, error) { var rOffset int32 switch l := len(parameter); l { case 1: rOffset = int32(int8(parameter[0])) case 4: rOffset = int32(binary.LittleEndian.Uint32(parameter)) default: _, curr := c.CurrInstr() return 0, 0, fmt.Errorf("invalid %s parameter length: %d", curr, l) } offset := c.IP() + int(rOffset) if offset < 0 || offset > c.LenInstr() { return 0, 0, fmt.Errorf("invalid offset %d ip at %d", offset, c.IP()) } return offset, int(rOffset), nil }