land/vendor/github.com/go-interpreter/wagon/validate/vm.go

223 lines
5.5 KiB
Go

// Copyright 2017 The go-interpreter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package validate
import (
"bytes"
"encoding/binary"
"io"
"github.com/go-interpreter/wagon/wasm"
"github.com/go-interpreter/wagon/wasm/leb128"
ops "github.com/go-interpreter/wagon/wasm/operators"
)
// mockVM is a minimal implementation of a virtual machine to
// validate WebAssembly code
type mockVM struct {
stack []operand
stackTop int // the top of the operand stack
origLength int // the original length of the bytecode stream
code *bytes.Reader
polymorphic bool // whether the base implict block has a polymorphic stack
blocks []block // a stack of encountered blocks
curFunc *wasm.FunctionSig
}
// a block reprsents an instruction sequence preceeded by a control flow operator
// it is used to verify that the block signature set by the operator is the correct
// one when the block ends
type block struct {
pc int // the pc where the control flow operator starting the block is located
stackTop int // stack top when the block started
blockType wasm.BlockType // block_type signature of the control operator
op byte // opcode for the operator starting the new block
polymorphic bool // whether the block has a polymorphic stack
loop bool // whether the block is the body of a loop instruction
}
func (vm *mockVM) fetchVarUint() (uint32, error) {
return leb128.ReadVarUint32(vm.code)
}
func (vm *mockVM) fetchVarInt() (int32, error) {
return leb128.ReadVarint32(vm.code)
}
func (vm *mockVM) fetchVarInt64() (int64, error) {
return leb128.ReadVarint64(vm.code)
}
func (vm *mockVM) fetchUint32() (uint32, error) {
var buf [4]byte
_, err := io.ReadFull(vm.code, buf[:])
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint32(buf[:]), nil
}
func (vm *mockVM) fetchUint64() (uint64, error) {
var buf [8]byte
_, err := io.ReadFull(vm.code, buf[:])
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint64(buf[:]), nil
}
func (vm *mockVM) pushBlock(op byte, blockType wasm.BlockType) {
logger.Printf("Pushing block %v", blockType)
vm.blocks = append(vm.blocks, block{
pc: vm.pc(),
stackTop: vm.stackTop,
blockType: blockType,
polymorphic: vm.isPolymorphic(),
op: op,
loop: op == ops.Loop,
})
}
// Get a block from it's relative nesting depth
func (vm *mockVM) getBlockFromDepth(depth int) *block {
if depth >= len(vm.blocks) {
return nil
}
return &vm.blocks[len(vm.blocks)-1-depth]
}
// Returns nil if depth is a valid nesting depth value that can be
// branched to.
func (vm *mockVM) canBranch(depth int) error {
blockType := wasm.BlockTypeEmpty
block := vm.getBlockFromDepth(depth)
// jumping to the start of a loop block doesn't push a value
// on the stack.
if block == nil {
if depth == len(vm.blocks) {
//equivalent to a `return', as the function
//body is an "implicit" block
if len(vm.curFunc.ReturnTypes) != 0 {
blockType = wasm.BlockType(vm.curFunc.ReturnTypes[0])
}
} else {
return InvalidLabelError(uint32(depth))
}
} else if !block.loop {
blockType = block.blockType
}
if blockType != wasm.BlockTypeEmpty {
top, under := vm.topOperand()
if under || top.Type != wasm.ValueType(blockType) {
return InvalidTypeError{wasm.ValueType(blockType), top.Type}
}
}
return nil
}
// returns nil in case of an underflow
func (vm *mockVM) popBlock() *block {
if len(vm.blocks) == 0 {
return nil
}
stackTop := len(vm.blocks) - 1
block := vm.blocks[stackTop]
vm.blocks = append(vm.blocks[:stackTop], vm.blocks[stackTop+1:]...)
return &block
}
func (vm *mockVM) topBlock() *block {
if len(vm.blocks) == 0 {
return nil
}
return &vm.blocks[len(vm.blocks)-1]
}
func (vm *mockVM) topOperand() (o operand, under bool) {
stackTop := vm.stackTop - 1
if stackTop == -1 {
under = true
return
}
o = vm.stack[stackTop]
return
}
func (vm *mockVM) popOperand() (operand, bool) {
var o operand
stackTop := vm.stackTop - 1
if stackTop == -1 {
return o, true
}
o = vm.stack[stackTop]
vm.stackTop--
logger.Printf("Stack after pop is %v. Popped %v", vm.stack[:vm.stackTop], o)
return o, false
}
func (vm *mockVM) pushOperand(t wasm.ValueType) {
o := operand{t}
logger.Printf("Stack top: %d, Len of stack :%d", vm.stackTop, len(vm.stack))
if vm.stackTop == len(vm.stack) {
vm.stack = append(vm.stack, o)
} else {
vm.stack[vm.stackTop] = o
}
vm.stackTop++
logger.Printf("Stack after push is %v. Pushed %v", vm.stack[:vm.stackTop], o)
}
func (vm *mockVM) adjustStack(op ops.Op) error {
for _, t := range op.Args {
op, under := vm.popOperand()
if !vm.isPolymorphic() && (under || op.Type != t) {
return InvalidTypeError{t, op.Type}
}
}
if op.Returns != wasm.ValueType(wasm.BlockTypeEmpty) {
vm.pushOperand(op.Returns)
}
return nil
}
// setPolymorphic sets the current block as having a polymorphic stack
// blocks created under it will be polymorphic too. All type-checking
// is ignored in a polymorhpic stack.
// (See https://github.com/WebAssembly/design/blob/27ac254c854994103c24834a994be16f74f54186/Semantics.md#validation)
func (vm *mockVM) setPolymorphic() {
if len(vm.blocks) == 0 {
vm.polymorphic = true
} else {
vm.blocks[len(vm.blocks)-1].polymorphic = true
}
}
func (vm *mockVM) isPolymorphic() bool {
if len(vm.blocks) == 0 {
return vm.polymorphic
}
return vm.topBlock().polymorphic
}
func (vm *mockVM) pc() int {
return vm.origLength - vm.code.Len()
}