vm: add bufBinWriter to emit functions in order to catch errors
This commit is contained in:
parent
821c9b2851
commit
d02673c112
6 changed files with 93 additions and 57 deletions
|
@ -19,6 +19,11 @@ func NewBufBinWriter() *BufBinWriter {
|
||||||
return &BufBinWriter{BinWriter: NewBinWriterFromIO(b), buf: b}
|
return &BufBinWriter{BinWriter: NewBinWriterFromIO(b), buf: b}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Len returns the number of bytes of the unread portion of the buffer.
|
||||||
|
func (bw *BufBinWriter) Len() int {
|
||||||
|
return bw.buf.Len()
|
||||||
|
}
|
||||||
|
|
||||||
// Bytes returns resulting buffer and makes future writes return an error.
|
// Bytes returns resulting buffer and makes future writes return an error.
|
||||||
func (bw *BufBinWriter) Bytes() []byte {
|
func (bw *BufBinWriter) Bytes() []byte {
|
||||||
if bw.Err != nil {
|
if bw.Err != nil {
|
||||||
|
|
|
@ -93,6 +93,11 @@ func (w *BinWriter) WriteVarUint(val uint64) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteVarBytes writes a variable byte into the underlying io.Writer without prefix.
|
||||||
|
func (w *BinWriter) WriteVarBytes(b []byte) {
|
||||||
|
w.WriteLE(b)
|
||||||
|
}
|
||||||
|
|
||||||
// WriteBytes writes a variable length byte array into the underlying io.Writer.
|
// WriteBytes writes a variable length byte array into the underlying io.Writer.
|
||||||
func (w *BinWriter) WriteBytes(b []byte) {
|
func (w *BinWriter) WriteBytes(b []byte) {
|
||||||
w.WriteVarUint(uint64(len(b)))
|
w.WriteVarUint(uint64(len(b)))
|
||||||
|
|
|
@ -53,6 +53,13 @@ func TestWriteBE(t *testing.T) {
|
||||||
assert.Equal(t, val, readval)
|
assert.Equal(t, val, readval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBufBinWriter_Len(t *testing.T) {
|
||||||
|
val := []byte{0xde}
|
||||||
|
bw := NewBufBinWriter()
|
||||||
|
bw.WriteLE(val)
|
||||||
|
require.Equal(t, 1, bw.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestWriterErrHandling(t *testing.T) {
|
func TestWriterErrHandling(t *testing.T) {
|
||||||
var badio = &badRW{}
|
var badio = &badRW{}
|
||||||
bw := NewBinWriterFromIO(badio)
|
bw := NewBinWriterFromIO(badio)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/constant"
|
"go/constant"
|
||||||
|
@ -13,6 +12,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto"
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ type codegen struct {
|
||||||
buildInfo *buildInfo
|
buildInfo *buildInfo
|
||||||
|
|
||||||
// prog holds the output buffer.
|
// prog holds the output buffer.
|
||||||
prog *bytes.Buffer
|
prog *io.BufBinWriter
|
||||||
|
|
||||||
// Type information.
|
// Type information.
|
||||||
typeInfo *types.Info
|
typeInfo *types.Info
|
||||||
|
@ -56,6 +56,10 @@ func (c *codegen) pc() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
func (c *codegen) emitLoadConst(t types.TypeAndValue) {
|
||||||
|
if c.prog.Err != nil {
|
||||||
|
log.Fatal(c.prog.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
switch typ := t.Type.Underlying().(type) {
|
switch typ := t.Type.Underlying().(type) {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
|
@ -201,6 +205,10 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
if c.prog.Err != nil {
|
||||||
|
log.Fatal(c.prog.Err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
|
|
||||||
// General declarations.
|
// General declarations.
|
||||||
|
@ -761,11 +769,11 @@ func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeGen compiles the program to bytecode.
|
// CodeGen compiles the program to bytecode.
|
||||||
func CodeGen(info *buildInfo) (*bytes.Buffer, error) {
|
func CodeGen(info *buildInfo) ([]byte, error) {
|
||||||
pkg := info.program.Package(info.initialPackage)
|
pkg := info.program.Package(info.initialPackage)
|
||||||
c := &codegen{
|
c := &codegen{
|
||||||
buildInfo: info,
|
buildInfo: info,
|
||||||
prog: new(bytes.Buffer),
|
prog: io.NewBufBinWriter(),
|
||||||
l: []int{},
|
l: []int{},
|
||||||
funcs: map[string]*funcScope{},
|
funcs: map[string]*funcScope{},
|
||||||
typeInfo: &pkg.Info,
|
typeInfo: &pkg.Info,
|
||||||
|
@ -815,9 +823,12 @@ func CodeGen(info *buildInfo) (*bytes.Buffer, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.writeJumps()
|
if c.prog.Err != nil {
|
||||||
|
return nil, c.prog.Err
|
||||||
return c.prog, nil
|
}
|
||||||
|
buf := c.prog.Bytes()
|
||||||
|
c.writeJumps(buf)
|
||||||
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) resolveFuncDecls(f *ast.File) {
|
func (c *codegen) resolveFuncDecls(f *ast.File) {
|
||||||
|
@ -831,8 +842,7 @@ func (c *codegen) resolveFuncDecls(f *ast.File) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) writeJumps() {
|
func (c *codegen) writeJumps(b []byte) {
|
||||||
b := c.prog.Bytes()
|
|
||||||
for i, op := range b {
|
for i, op := range b {
|
||||||
j := i + 1
|
j := i + 1
|
||||||
switch vm.Instruction(op) {
|
switch vm.Instruction(op) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ func Compile(r io.Reader) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type archive struct {
|
type archive struct {
|
||||||
|
|
|
@ -1,105 +1,114 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func emit(w *bytes.Buffer, instr vm.Instruction, b []byte) error {
|
// emit a VM Instruction with data to the given buffer.
|
||||||
if err := w.WriteByte(byte(instr)); err != nil {
|
func emit(w *io.BufBinWriter, instr vm.Instruction, b []byte) {
|
||||||
return err
|
w.WriteLE(byte(instr))
|
||||||
}
|
w.WriteVarBytes(b)
|
||||||
_, err := w.Write(b)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitOpcode(w io.ByteWriter, instr vm.Instruction) error {
|
// emitOpcode emits a single VM Instruction the given buffer.
|
||||||
return w.WriteByte(byte(instr))
|
func emitOpcode(w *io.BufBinWriter, instr vm.Instruction) {
|
||||||
|
w.WriteLE(byte(instr))
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitBool(w io.ByteWriter, ok bool) error {
|
// emitBool emits a bool type the given buffer.
|
||||||
|
func emitBool(w *io.BufBinWriter, ok bool) {
|
||||||
if ok {
|
if ok {
|
||||||
return emitOpcode(w, vm.PUSHT)
|
emitOpcode(w, vm.PUSHT)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return emitOpcode(w, vm.PUSHF)
|
emitOpcode(w, vm.PUSHF)
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitInt(w *bytes.Buffer, i int64) error {
|
// emitInt emits a int type to the given buffer.
|
||||||
if i == -1 {
|
func emitInt(w *io.BufBinWriter, i int64) {
|
||||||
return emitOpcode(w, vm.PUSHM1)
|
switch {
|
||||||
}
|
case i == -1:
|
||||||
if i == 0 {
|
emitOpcode(w, vm.PUSHM1)
|
||||||
return emitOpcode(w, vm.PUSHF)
|
return
|
||||||
}
|
case i == 0:
|
||||||
if i > 0 && i < 16 {
|
emitOpcode(w, vm.PUSHF)
|
||||||
|
return
|
||||||
|
case i > 0 && i < 16:
|
||||||
val := vm.Instruction(int(vm.PUSH1) - 1 + int(i))
|
val := vm.Instruction(int(vm.PUSH1) - 1 + int(i))
|
||||||
return emitOpcode(w, val)
|
emitOpcode(w, val)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bInt := big.NewInt(i)
|
bInt := big.NewInt(i)
|
||||||
val := util.ArrayReverse(bInt.Bytes())
|
val := util.ArrayReverse(bInt.Bytes())
|
||||||
return emitBytes(w, val)
|
emitBytes(w, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitString(w *bytes.Buffer, s string) error {
|
// emitString emits a string to the given buffer.
|
||||||
return emitBytes(w, []byte(s))
|
func emitString(w *io.BufBinWriter, s string) {
|
||||||
|
emitBytes(w, []byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitBytes(w *bytes.Buffer, b []byte) error {
|
// emitBytes emits a byte array to the given buffer.
|
||||||
var (
|
func emitBytes(w *io.BufBinWriter, b []byte) {
|
||||||
err error
|
n := len(b)
|
||||||
n = len(b)
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case n <= int(vm.PUSHBYTES75):
|
case n <= int(vm.PUSHBYTES75):
|
||||||
return emit(w, vm.Instruction(n), b)
|
emit(w, vm.Instruction(n), b)
|
||||||
|
return
|
||||||
case n < 0x100:
|
case n < 0x100:
|
||||||
err = emit(w, vm.PUSHDATA1, []byte{byte(n)})
|
emit(w, vm.PUSHDATA1, []byte{byte(n)})
|
||||||
case n < 0x10000:
|
case n < 0x10000:
|
||||||
buf := make([]byte, 2)
|
buf := make([]byte, 2)
|
||||||
binary.LittleEndian.PutUint16(buf, uint16(n))
|
binary.LittleEndian.PutUint16(buf, uint16(n))
|
||||||
err = emit(w, vm.PUSHDATA2, buf)
|
emit(w, vm.PUSHDATA2, buf)
|
||||||
default:
|
default:
|
||||||
buf := make([]byte, 4)
|
buf := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(buf, uint32(n))
|
binary.LittleEndian.PutUint32(buf, uint32(n))
|
||||||
err = emit(w, vm.PUSHDATA4, buf)
|
emit(w, vm.PUSHDATA4, buf)
|
||||||
|
if w.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
w.WriteBytes(b)
|
||||||
}
|
|
||||||
_, err = w.Write(b)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitSyscall(w *bytes.Buffer, api string) error {
|
// emitSyscall emits the syscall API to the given buffer.
|
||||||
|
// Syscall API string cannot be 0.
|
||||||
|
func emitSyscall(w *io.BufBinWriter, api string) {
|
||||||
if len(api) == 0 {
|
if len(api) == 0 {
|
||||||
return errors.New("syscall api cannot be of length 0")
|
w.Err = errors.New("syscall api cannot be of length 0")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
buf := make([]byte, len(api)+1)
|
buf := make([]byte, len(api)+1)
|
||||||
buf[0] = byte(len(api))
|
buf[0] = byte(len(api))
|
||||||
copy(buf[1:], api)
|
copy(buf[1:], api)
|
||||||
return emit(w, vm.SYSCALL, buf)
|
emit(w, vm.SYSCALL, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitCall(w *bytes.Buffer, instr vm.Instruction, label int16) error {
|
// emitCall emits a call Instruction with label to the given buffer.
|
||||||
return emitJmp(w, instr, label)
|
func emitCall(w *io.BufBinWriter, instr vm.Instruction, label int16) {
|
||||||
|
emitJmp(w, instr, label)
|
||||||
}
|
}
|
||||||
|
|
||||||
func emitJmp(w *bytes.Buffer, instr vm.Instruction, label int16) error {
|
// emitJmp emits a jump Instruction along with label to the given buffer.
|
||||||
|
func emitJmp(w *io.BufBinWriter, instr vm.Instruction, label int16) {
|
||||||
if !isInstrJmp(instr) {
|
if !isInstrJmp(instr) {
|
||||||
return fmt.Errorf("opcode %s is not a jump or call type", instr)
|
w.Err = fmt.Errorf("opcode %s is not a jump or call type", instr)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
buf := make([]byte, 2)
|
buf := make([]byte, 2)
|
||||||
binary.LittleEndian.PutUint16(buf, uint16(label))
|
binary.LittleEndian.PutUint16(buf, uint16(label))
|
||||||
return emit(w, instr, buf)
|
emit(w, instr, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isInstrJmp(instr vm.Instruction) bool {
|
func isInstrJmp(instr vm.Instruction) bool {
|
||||||
|
|
Loading…
Reference in a new issue