Add assert_invalid tests and harden against malicious binary. (#30)
This commit is contained in:
7
.github/workflows/commit.yaml
vendored
7
.github/workflows/commit.yaml
vendored
@@ -47,11 +47,4 @@ jobs:
|
|||||||
~/go/pkg/mod
|
~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
|
||||||
- name: Install TinyGo
|
|
||||||
run: |
|
|
||||||
wget https://github.com/tinygo-org/tinygo/releases/download/v0.20.0/tinygo_0.20.0_amd64.deb
|
|
||||||
sudo dpkg -i tinygo_0.20.0_amd64.deb
|
|
||||||
|
|
||||||
- run: make build.examples
|
|
||||||
|
|
||||||
- run: make test
|
- run: make test
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Gasm
|
# Gasm
|
||||||
|
|
||||||
A minimal implementation of Wasm Virtual machine purely written in Go. The VM passes all the [Wasm Spec test suites](https://github.com/WebAssembly/spec/tree/master/test/core) and is fully compatible Wasm v1.0 Specification.
|
A minimal implementation of Wasm Virtual Machine purely written in Go. The VM passes all the [Wasm Spec test suites](https://github.com/WebAssembly/spec/tree/wg-1.0/test/core) and is fully compatible with the WebAssembly v1.0 Specification.
|
||||||
|
|
||||||
The VM can be embedded in your Go program without any dependency like cgo, and enables Gophers to write Wasm host environments easily.
|
The VM can be embedded in your Go program without any dependency like cgo, and enables Gophers to write Wasm host environments easily.
|
||||||
|
|
||||||
|
|||||||
@@ -15,52 +15,58 @@ type ConstantExpression struct {
|
|||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) executeConstExpression(target *ModuleInstance, expr *ConstantExpression) (v interface{}, err error) {
|
func (s *Store) executeConstExpression(target *ModuleInstance, expr *ConstantExpression) (v interface{}, valueType ValueType, err error) {
|
||||||
r := bytes.NewBuffer(expr.Data)
|
r := bytes.NewBuffer(expr.Data)
|
||||||
switch expr.OptCode {
|
switch expr.OptCode {
|
||||||
case OptCodeI32Const:
|
case OptCodeI32Const:
|
||||||
v, _, err = leb128.DecodeInt32(r)
|
v, _, err = leb128.DecodeInt32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read uint32: %w", err)
|
return nil, 0, fmt.Errorf("read uint32: %w", err)
|
||||||
}
|
}
|
||||||
|
return v, ValueTypeI32, nil
|
||||||
case OptCodeI64Const:
|
case OptCodeI64Const:
|
||||||
v, _, err = leb128.DecodeInt32(r)
|
v, _, err = leb128.DecodeInt32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read uint64: %w", err)
|
return nil, 0, fmt.Errorf("read uint64: %w", err)
|
||||||
}
|
}
|
||||||
|
return v, ValueTypeI64, nil
|
||||||
case OptCodeF32Const:
|
case OptCodeF32Const:
|
||||||
v, err = readFloat32(r)
|
v, err = readFloat32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read f34: %w", err)
|
return nil, 0, fmt.Errorf("read f34: %w", err)
|
||||||
}
|
}
|
||||||
|
return v, ValueTypeF32, nil
|
||||||
case OptCodeF64Const:
|
case OptCodeF64Const:
|
||||||
v, err = readFloat64(r)
|
v, err = readFloat64(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read f64: %w", err)
|
return nil, 0, fmt.Errorf("read f64: %w", err)
|
||||||
}
|
}
|
||||||
|
return v, ValueTypeF64, nil
|
||||||
case OptCodeGlobalGet:
|
case OptCodeGlobalGet:
|
||||||
id, _, err := leb128.DecodeUint32(r)
|
id, _, err := leb128.DecodeUint32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read index of global: %w", err)
|
return nil, 0, fmt.Errorf("read index of global: %w", err)
|
||||||
}
|
}
|
||||||
if uint32(len(target.GlobalsAddrs)) <= id {
|
if uint32(len(target.GlobalsAddrs)) <= id {
|
||||||
return nil, fmt.Errorf("global index out of range")
|
return nil, 0, fmt.Errorf("global index out of range")
|
||||||
}
|
}
|
||||||
g := s.Globals[target.GlobalsAddrs[id]]
|
g := s.Globals[target.GlobalsAddrs[id]]
|
||||||
switch g.Type.ValType {
|
switch g.Type.ValType {
|
||||||
case ValueTypeI32:
|
case ValueTypeI32:
|
||||||
v = int32(g.Val)
|
v = int32(g.Val)
|
||||||
|
return v, ValueTypeI32, nil
|
||||||
case ValueTypeI64:
|
case ValueTypeI64:
|
||||||
v = int64(g.Val)
|
v = int64(g.Val)
|
||||||
|
return v, ValueTypeI64, nil
|
||||||
case ValueTypeF32:
|
case ValueTypeF32:
|
||||||
v = math.Float32frombits(uint32(g.Val))
|
v = math.Float32frombits(uint32(g.Val))
|
||||||
|
return v, ValueTypeF32, nil
|
||||||
case ValueTypeF64:
|
case ValueTypeF64:
|
||||||
v = math.Float64frombits(uint64(g.Val))
|
v = math.Float64frombits(uint64(g.Val))
|
||||||
|
return v, ValueTypeF64, nil
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid opt code: %#x", expr.OptCode)
|
|
||||||
}
|
}
|
||||||
return v, nil
|
return nil, 0, fmt.Errorf("invalid opt code: %#x", expr.OptCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readConstantExpression(r io.Reader) (*ConstantExpression, error) {
|
func readConstantExpression(r io.Reader) (*ConstantExpression, error) {
|
||||||
|
|||||||
@@ -56,12 +56,13 @@ const (
|
|||||||
OptCodeMemorySize OptCode = 0x3f
|
OptCodeMemorySize OptCode = 0x3f
|
||||||
OptCodeMemoryGrow OptCode = 0x40
|
OptCodeMemoryGrow OptCode = 0x40
|
||||||
|
|
||||||
// numeric instruction
|
// const instructions.
|
||||||
OptCodeI32Const OptCode = 0x41
|
OptCodeI32Const OptCode = 0x41
|
||||||
OptCodeI64Const OptCode = 0x42
|
OptCodeI64Const OptCode = 0x42
|
||||||
OptCodeF32Const OptCode = 0x43
|
OptCodeF32Const OptCode = 0x43
|
||||||
OptCodeF64Const OptCode = 0x44
|
OptCodeF64Const OptCode = 0x44
|
||||||
|
|
||||||
|
// numeric instructions.
|
||||||
OptCodeI32eqz OptCode = 0x45
|
OptCodeI32eqz OptCode = 0x45
|
||||||
OptCodeI32eq OptCode = 0x46
|
OptCodeI32eq OptCode = 0x46
|
||||||
OptCodeI32ne OptCode = 0x47
|
OptCodeI32ne OptCode = 0x47
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func (m *Module) readSections(r *Reader) error {
|
|||||||
err = errors.New("invalid section id")
|
err = errors.New("invalid section id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sectionContentStart+int(ss) != r.read {
|
if err == nil && sectionContentStart+int(ss) != r.read {
|
||||||
err = fmt.Errorf("invalid section length: expected to be %d but got %d", ss, r.read-sectionContentStart)
|
err = fmt.Errorf("invalid section length: expected to be %d but got %d", ss, r.read-sectionContentStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ func (m *Module) readSectionFunctions(r *Reader) error {
|
|||||||
for i := range m.FunctionSection {
|
for i := range m.FunctionSection {
|
||||||
m.FunctionSection[i], _, err = leb128.DecodeUint32(r)
|
m.FunctionSection[i], _, err = leb128.DecodeUint32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get typeidx: %v", err)
|
return fmt.Errorf("get type index: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -215,7 +215,9 @@ func (m *Module) readSectionExports(r *Reader) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read export: %v", err)
|
return fmt.Errorf("read export: %v", err)
|
||||||
}
|
}
|
||||||
|
if _, ok := m.ExportSection[expDesc.Name]; ok {
|
||||||
|
return fmt.Errorf("duplicate export name: %s", expDesc.Name)
|
||||||
|
}
|
||||||
m.ExportSection[expDesc.Name] = expDesc
|
m.ExportSection[expDesc.Name] = expDesc
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -257,7 +259,7 @@ func (m *Module) readSectionCodes(r *Reader) error {
|
|||||||
for i := range m.CodeSection {
|
for i := range m.CodeSection {
|
||||||
m.CodeSection[i], err = readCodeSegment(r)
|
m.CodeSection[i], err = readCodeSegment(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read code segment: %v", err)
|
return fmt.Errorf("read %d-th code segment: %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ package wasm
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/mathetake/gasm/wasm/leb128"
|
"github.com/mathetake/gasm/wasm/leb128"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ImportKindFunction = 0x00
|
||||||
|
ImportKindTable = 0x01
|
||||||
|
ImportKindMemory = 0x02
|
||||||
|
ImportKindGlobal = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
type ImportDesc struct {
|
type ImportDesc struct {
|
||||||
Kind byte
|
Kind byte
|
||||||
|
|
||||||
@@ -25,41 +31,40 @@ func readImportDesc(r io.Reader) (*ImportDesc, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch b[0] {
|
switch b[0] {
|
||||||
case 0x00:
|
case ImportKindFunction:
|
||||||
tID, _, err := leb128.DecodeUint32(r)
|
tID, _, err := leb128.DecodeUint32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read typeindex: %v", err)
|
return nil, fmt.Errorf("read typeindex: %v", err)
|
||||||
}
|
}
|
||||||
return &ImportDesc{
|
return &ImportDesc{
|
||||||
Kind: 0x00,
|
Kind: ImportKindFunction,
|
||||||
TypeIndexPtr: &tID,
|
TypeIndexPtr: &tID,
|
||||||
}, nil
|
}, nil
|
||||||
case 0x01:
|
case ImportKindTable:
|
||||||
tt, err := readTableType(r)
|
tt, err := readTableType(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read table type: %v", err)
|
return nil, fmt.Errorf("read table type: %v", err)
|
||||||
}
|
}
|
||||||
return &ImportDesc{
|
return &ImportDesc{
|
||||||
Kind: 0x01,
|
Kind: ImportKindTable,
|
||||||
TableTypePtr: tt,
|
TableTypePtr: tt,
|
||||||
}, nil
|
}, nil
|
||||||
case 0x02:
|
case ImportKindMemory:
|
||||||
mt, err := readMemoryType(r)
|
mt, err := readMemoryType(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read table type: %v", err)
|
return nil, fmt.Errorf("read table type: %v", err)
|
||||||
}
|
}
|
||||||
return &ImportDesc{
|
return &ImportDesc{
|
||||||
Kind: 0x02,
|
Kind: ImportKindMemory,
|
||||||
MemTypePtr: mt,
|
MemTypePtr: mt,
|
||||||
}, nil
|
}, nil
|
||||||
case 0x03:
|
case ImportKindGlobal:
|
||||||
gt, err := readGlobalType(r)
|
gt, err := readGlobalType(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read global type: %v", err)
|
return nil, fmt.Errorf("read global type: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ImportDesc{
|
return &ImportDesc{
|
||||||
Kind: 0x03,
|
Kind: ImportKindGlobal,
|
||||||
GlobalTypePtr: gt,
|
GlobalTypePtr: gt,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
@@ -206,8 +211,9 @@ func readElementSegment(r io.Reader) (*ElementSegment, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CodeSegment struct {
|
type CodeSegment struct {
|
||||||
NumLocals uint32
|
NumLocals uint32
|
||||||
Body []byte
|
LocalTypes []ValueType
|
||||||
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func readCodeSegment(r io.Reader) (*CodeSegment, error) {
|
func readCodeSegment(r io.Reader) (*CodeSegment, error) {
|
||||||
@@ -221,29 +227,46 @@ func readCodeSegment(r io.Reader) (*CodeSegment, error) {
|
|||||||
// parse locals
|
// parse locals
|
||||||
ls, _, err := leb128.DecodeUint32(r)
|
ls, _, err := leb128.DecodeUint32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get the size locals: %w", err)
|
return nil, fmt.Errorf("get the size locals: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var numLocals uint64
|
var nums []uint64
|
||||||
|
var types []ValueType
|
||||||
|
var sum uint64
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
for i := uint32(0); i < ls; i++ {
|
for i := uint32(0); i < ls; i++ {
|
||||||
n, _, err := leb128.DecodeUint32(r)
|
n, _, err := leb128.DecodeUint32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read n of locals: %w", err)
|
return nil, fmt.Errorf("read n of locals: %v", err)
|
||||||
}
|
}
|
||||||
numLocals += uint64(n)
|
sum += uint64(n)
|
||||||
|
nums = append(nums, uint64(n))
|
||||||
|
|
||||||
if _, err := io.ReadFull(r, b); err != nil {
|
_, err = io.ReadFull(r, b)
|
||||||
return nil, fmt.Errorf("read type of local")
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read type of local: %v", err)
|
||||||
|
}
|
||||||
|
switch vt := ValueType(b[0]); vt {
|
||||||
|
case ValueTypeI32, ValueTypeF32, ValueTypeI64, ValueTypeF64:
|
||||||
|
types = append(types, vt)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid local type: 0x%x", vt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if numLocals > math.MaxUint32 {
|
if sum > math.MaxUint32 {
|
||||||
return nil, fmt.Errorf("too many locals: %d", numLocals)
|
return nil, fmt.Errorf("too many locals: %d", sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract body
|
var localTypes []ValueType
|
||||||
body, err := ioutil.ReadAll(r)
|
for i, num := range nums {
|
||||||
|
t := types[i]
|
||||||
|
for j := uint64(0); j < num; j++ {
|
||||||
|
localTypes = append(localTypes, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read body: %w", err)
|
return nil, fmt.Errorf("read body: %w", err)
|
||||||
}
|
}
|
||||||
@@ -253,8 +276,9 @@ func readCodeSegment(r io.Reader) (*CodeSegment, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &CodeSegment{
|
return &CodeSegment{
|
||||||
Body: body[:len(body)-1],
|
Body: body,
|
||||||
NumLocals: uint32(numLocals),
|
NumLocals: uint32(sum),
|
||||||
|
LocalTypes: localTypes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
package wasm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReadImportDesc(t *testing.T) {
|
|
||||||
t.Run("ng", func(t *testing.T) {
|
|
||||||
buf := []byte{0x04}
|
|
||||||
_, err := readImportDesc(bytes.NewBuffer(buf))
|
|
||||||
require.True(t, errors.Is(err, ErrInvalidByte))
|
|
||||||
t.Log(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *ImportDesc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
bytes: []byte{0x00, 0x0a},
|
|
||||||
exp: &ImportDesc{
|
|
||||||
Kind: 0,
|
|
||||||
TypeIndexPtr: uint32Ptr(10),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x01, 0x70, 0x0, 0x0a},
|
|
||||||
exp: &ImportDesc{
|
|
||||||
Kind: 1,
|
|
||||||
TableTypePtr: &TableType{
|
|
||||||
ElemType: 0x70,
|
|
||||||
Limit: &LimitsType{Min: 10},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x02, 0x0, 0x0a},
|
|
||||||
exp: &ImportDesc{
|
|
||||||
Kind: 2,
|
|
||||||
MemTypePtr: &MemoryType{Min: 10},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x03, 0x7e, 0x01},
|
|
||||||
exp: &ImportDesc{
|
|
||||||
Kind: 3,
|
|
||||||
GlobalTypePtr: &GlobalType{ValType: ValueTypeI64, Mutable: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readImportDesc(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadImportSegment(t *testing.T) {
|
|
||||||
exp := &ImportSegment{
|
|
||||||
Module: "abc",
|
|
||||||
Name: "ABC",
|
|
||||||
Desc: &ImportDesc{Kind: 0, TypeIndexPtr: uint32Ptr(10)},
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := []byte{byte(len(exp.Module))}
|
|
||||||
buf = append(buf, exp.Module...)
|
|
||||||
buf = append(buf, byte(len(exp.Name)))
|
|
||||||
buf = append(buf, exp.Name...)
|
|
||||||
buf = append(buf, 0x00, 0x0a)
|
|
||||||
|
|
||||||
actual, err := readImportSegment(bytes.NewBuffer(buf))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, exp, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadGlobalSegment(t *testing.T) {
|
|
||||||
exp := &GlobalSegment{
|
|
||||||
Type: &GlobalType{ValType: ValueTypeI64, Mutable: false},
|
|
||||||
Init: &ConstantExpression{
|
|
||||||
OptCode: OptCodeI64Const,
|
|
||||||
Data: []byte{0x01},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := []byte{0x7e, 0x00, 0x42, 0x01, 0x0b}
|
|
||||||
actual, err := readGlobalSegment(bytes.NewBuffer(buf))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, exp, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadExportDesc(t *testing.T) {
|
|
||||||
t.Run("ng", func(t *testing.T) {
|
|
||||||
buf := []byte{0x04}
|
|
||||||
_, err := readExportDesc(bytes.NewBuffer(buf))
|
|
||||||
require.True(t, errors.Is(err, ErrInvalidByte))
|
|
||||||
t.Log(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *ExportDesc
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
bytes: []byte{0x00, 0x0a},
|
|
||||||
exp: &ExportDesc{Kind: 0, Index: 10},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x01, 0x05},
|
|
||||||
exp: &ExportDesc{Kind: 1, Index: 5},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x02, 0x01},
|
|
||||||
exp: &ExportDesc{Kind: 2, Index: 1},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x03, 0x0b},
|
|
||||||
exp: &ExportDesc{Kind: 3, Index: 11},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readExportDesc(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadExportSegment(t *testing.T) {
|
|
||||||
exp := &ExportSegment{
|
|
||||||
Name: "ABC",
|
|
||||||
Desc: &ExportDesc{Kind: 0, Index: 10},
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := []byte{byte(len(exp.Name))}
|
|
||||||
buf = append(buf, exp.Name...)
|
|
||||||
buf = append(buf, 0x00, 0x0a)
|
|
||||||
|
|
||||||
actual, err := readExportSegment(bytes.NewBuffer(buf))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, exp, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadElementSegment(t *testing.T) {
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *ElementSegment
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
bytes: []byte{0xa, 0x41, 0x1, 0x0b, 0x02, 0x05, 0x07},
|
|
||||||
exp: &ElementSegment{
|
|
||||||
TableIndex: 10,
|
|
||||||
OffsetExpr: &ConstantExpression{
|
|
||||||
OptCode: OptCodeI32Const,
|
|
||||||
Data: []byte{0x01},
|
|
||||||
},
|
|
||||||
Init: []uint32{5, 7},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x3, 0x41, 0x04, 0x0b, 0x01, 0x0a},
|
|
||||||
exp: &ElementSegment{
|
|
||||||
TableIndex: 3,
|
|
||||||
OffsetExpr: &ConstantExpression{
|
|
||||||
OptCode: OptCodeI32Const,
|
|
||||||
Data: []byte{0x04},
|
|
||||||
},
|
|
||||||
Init: []uint32{10},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readElementSegment(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadCodeSegment(t *testing.T) {
|
|
||||||
buf := []byte{0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x12, 0x3, 0x01, 0x0b}
|
|
||||||
exp := &CodeSegment{
|
|
||||||
NumLocals: 0x01,
|
|
||||||
Body: []byte{0x1, 0x1, 0x12, 0x3, 0x01},
|
|
||||||
}
|
|
||||||
actual, err := readCodeSegment(bytes.NewBuffer(buf))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, exp, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDataSegment(t *testing.T) {
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *DataSegment
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
bytes: []byte{0x0, 0x41, 0x1, 0x0b, 0x02, 0x05, 0x07},
|
|
||||||
exp: &DataSegment{
|
|
||||||
OffsetExpression: &ConstantExpression{
|
|
||||||
OptCode: OptCodeI32Const,
|
|
||||||
Data: []byte{0x01},
|
|
||||||
},
|
|
||||||
Init: []byte{5, 7},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x0, 0x41, 0x04, 0x0b, 0x01, 0x0a},
|
|
||||||
exp: &DataSegment{
|
|
||||||
OffsetExpression: &ConstantExpression{
|
|
||||||
OptCode: OptCodeI32Const,
|
|
||||||
Data: []byte{0x04},
|
|
||||||
},
|
|
||||||
Init: []byte{0x0a},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readDataSegment(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -202,8 +202,8 @@ func TestSpecification(t *testing.T) {
|
|||||||
|
|
||||||
var base testbase
|
var base testbase
|
||||||
require.NoError(t, json.Unmarshal(raw, &base))
|
require.NoError(t, json.Unmarshal(raw, &base))
|
||||||
|
|
||||||
wastName := filepath.Base(base.SourceFile)
|
wastName := filepath.Base(base.SourceFile)
|
||||||
|
|
||||||
t.Run(wastName, func(t *testing.T) {
|
t.Run(wastName, func(t *testing.T) {
|
||||||
vm, err := wasm.NewVM()
|
vm, err := wasm.NewVM()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -317,7 +317,17 @@ func TestSpecification(t *testing.T) {
|
|||||||
t.Fatalf("unsupported action type type: %v", c)
|
t.Fatalf("unsupported action type type: %v", c)
|
||||||
}
|
}
|
||||||
case "assert_invalid":
|
case "assert_invalid":
|
||||||
// TODO:
|
if c.ModuleType == "text" {
|
||||||
|
// We don't support direct loading of wast yet.
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
buf, err := os.ReadFile(filepath.Join(caseDir, c.Filename))
|
||||||
|
require.NoError(t, err, msg)
|
||||||
|
mod, err := wasm.DecodeModule(buf)
|
||||||
|
if err == nil {
|
||||||
|
err = vm.InstantiateModule(mod, "")
|
||||||
|
}
|
||||||
|
require.Error(t, err, msg)
|
||||||
case "assert_exhaustion":
|
case "assert_exhaustion":
|
||||||
// TODO:
|
// TODO:
|
||||||
case "assert_unlinkable":
|
case "assert_unlinkable":
|
||||||
|
|||||||
1022
wasm/store.go
1022
wasm/store.go
File diff suppressed because it is too large
Load Diff
18
wasm/type.go
18
wasm/type.go
@@ -39,6 +39,8 @@ func readFunctionType(r io.Reader) (*FunctionType, error) {
|
|||||||
s, _, err = leb128.DecodeUint32(r)
|
s, _, err = leb128.DecodeUint32(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get the size of output value types: %w", err)
|
return nil, fmt.Errorf("get the size of output value types: %w", err)
|
||||||
|
} else if s > 1 {
|
||||||
|
return nil, fmt.Errorf("multi value results not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
op, err := readValueTypes(r, s)
|
op, err := readValueTypes(r, s)
|
||||||
@@ -116,7 +118,21 @@ func readTableType(r io.Reader) (*TableType, error) {
|
|||||||
type MemoryType = LimitsType
|
type MemoryType = LimitsType
|
||||||
|
|
||||||
func readMemoryType(r io.Reader) (*MemoryType, error) {
|
func readMemoryType(r io.Reader) (*MemoryType, error) {
|
||||||
return readLimitsType(r)
|
ret, err := readLimitsType(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ret.Min > uint32(PageSize) {
|
||||||
|
return nil, fmt.Errorf("memory min must be at most 65536 pages (4GiB)")
|
||||||
|
}
|
||||||
|
if ret.Max != nil {
|
||||||
|
if *ret.Max < ret.Min {
|
||||||
|
return nil, fmt.Errorf("memory size minimum must not be greater than maximum")
|
||||||
|
} else if *ret.Max > uint32(PageSize) {
|
||||||
|
return nil, fmt.Errorf("memory max must be at most 65536 pages (4GiB)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type GlobalType struct {
|
type GlobalType struct {
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
package wasm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReadFunctionType(t *testing.T) {
|
|
||||||
t.Run("ng", func(t *testing.T) {
|
|
||||||
buf := []byte{0x00}
|
|
||||||
_, err := readFunctionType(bytes.NewBuffer(buf))
|
|
||||||
assert.True(t, errors.Is(err, ErrInvalidByte))
|
|
||||||
t.Log(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *FunctionType
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
bytes: []byte{0x60, 0x0, 0x0},
|
|
||||||
exp: &FunctionType{
|
|
||||||
InputTypes: []ValueType{},
|
|
||||||
ReturnTypes: []ValueType{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x60, 0x2, 0x7f, 0x7e, 0x0},
|
|
||||||
exp: &FunctionType{
|
|
||||||
InputTypes: []ValueType{ValueTypeI32, ValueTypeI64},
|
|
||||||
ReturnTypes: []ValueType{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x60, 0x1, 0x7e, 0x2, 0x7f, 0x7e},
|
|
||||||
exp: &FunctionType{
|
|
||||||
InputTypes: []ValueType{ValueTypeI64},
|
|
||||||
ReturnTypes: []ValueType{ValueTypeI32, ValueTypeI64},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x60, 0x0, 0x2, 0x7f, 0x7e},
|
|
||||||
exp: &FunctionType{
|
|
||||||
InputTypes: []ValueType{},
|
|
||||||
ReturnTypes: []ValueType{ValueTypeI32, ValueTypeI64},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readFunctionType(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadLimitsType(t *testing.T) {
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *LimitsType
|
|
||||||
}{
|
|
||||||
{bytes: []byte{0x00, 0xa}, exp: &LimitsType{Min: 10}},
|
|
||||||
{bytes: []byte{0x01, 0xa, 0xa}, exp: &LimitsType{Min: 10, Max: uint32Ptr(10)}},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readLimitsType(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func uint32Ptr(in uint32) *uint32 {
|
|
||||||
return &in
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadTableType(t *testing.T) {
|
|
||||||
t.Run("ng", func(t *testing.T) {
|
|
||||||
buf := []byte{0x00}
|
|
||||||
_, err := readTableType(bytes.NewBuffer(buf))
|
|
||||||
require.True(t, errors.Is(err, ErrInvalidByte))
|
|
||||||
t.Log(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *TableType
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
bytes: []byte{0x70, 0x00, 0xa},
|
|
||||||
exp: &TableType{
|
|
||||||
ElemType: 0x70,
|
|
||||||
Limit: &LimitsType{Min: 10},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bytes: []byte{0x70, 0x01, 0x01, 0xa},
|
|
||||||
exp: &TableType{
|
|
||||||
ElemType: 0x70,
|
|
||||||
Limit: &LimitsType{Min: 1, Max: uint32Ptr(10)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
c := c
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readTableType(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadMemoryType(t *testing.T) {
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *MemoryType
|
|
||||||
}{
|
|
||||||
{bytes: []byte{0x00, 0xa}, exp: &MemoryType{Min: 10}},
|
|
||||||
{bytes: []byte{0x01, 0xa, 0xa}, exp: &MemoryType{Min: 10, Max: uint32Ptr(10)}},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readMemoryType(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadGlobalType(t *testing.T) {
|
|
||||||
t.Run("ng", func(t *testing.T) {
|
|
||||||
buf := []byte{0x7e, 0x3}
|
|
||||||
_, err := readGlobalType(bytes.NewBuffer(buf))
|
|
||||||
require.True(t, errors.Is(err, ErrInvalidByte))
|
|
||||||
t.Log(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
for i, c := range []struct {
|
|
||||||
bytes []byte
|
|
||||||
exp *GlobalType
|
|
||||||
}{
|
|
||||||
{bytes: []byte{0x7e, 0x00}, exp: &GlobalType{ValType: ValueTypeI64, Mutable: false}},
|
|
||||||
{bytes: []byte{0x7e, 0x01}, exp: &GlobalType{ValType: ValueTypeI64, Mutable: true}},
|
|
||||||
} {
|
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
||||||
actual, err := readGlobalType(bytes.NewBuffer(c.bytes))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, c.exp, actual)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ package wasm
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/mathetake/gasm/wasm/leb128"
|
"github.com/mathetake/gasm/wasm/leb128"
|
||||||
)
|
)
|
||||||
@@ -46,6 +47,10 @@ func readNameValue(r io.Reader) (string, error) {
|
|||||||
return "", fmt.Errorf("read bytes of name: %v", err)
|
return "", fmt.Errorf("read bytes of name: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utf8.Valid(buf) {
|
||||||
|
return "", fmt.Errorf("name must be valid as utf8")
|
||||||
|
}
|
||||||
|
|
||||||
return string(buf), nil
|
return string(buf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (vm *VirtualMachine) ExecExportedFunction(moduleName, funcName string, args
|
|||||||
|
|
||||||
exp, ok := m.Exports[funcName]
|
exp, ok := m.Exports[funcName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, fmt.Errorf("exported func of name '%s' not found in '%s'", funcName, moduleName)
|
return nil, nil, fmt.Errorf("exported function '%s' not found in '%s'", funcName, moduleName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if exp.Kind != ExportKindFunction {
|
if exp.Kind != ExportKindFunction {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ type (
|
|||||||
}
|
}
|
||||||
NativeFunction struct {
|
NativeFunction struct {
|
||||||
Signature *FunctionType
|
Signature *FunctionType
|
||||||
NumLocal uint32
|
NumLocals uint32
|
||||||
|
LocalTypes []ValueType
|
||||||
Body []byte
|
Body []byte
|
||||||
Blocks map[uint64]*NativeFunctionBlock
|
Blocks map[uint64]*NativeFunctionBlock
|
||||||
ModuleInstance *ModuleInstance
|
ModuleInstance *ModuleInstance
|
||||||
@@ -24,6 +25,8 @@ type (
|
|||||||
StartAt, ElseAt, EndAt uint64
|
StartAt, ElseAt, EndAt uint64
|
||||||
BlockType *FunctionType
|
BlockType *FunctionType
|
||||||
BlockTypeBytes uint64
|
BlockTypeBytes uint64
|
||||||
|
IsLoop bool // TODO: might not be necessary
|
||||||
|
IsIf bool // TODO: might not be necessary
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,7 +86,7 @@ func (h *HostFunction) Call(vm *VirtualMachine) {
|
|||||||
|
|
||||||
func (n *NativeFunction) Call(vm *VirtualMachine) {
|
func (n *NativeFunction) Call(vm *VirtualMachine) {
|
||||||
al := len(n.Signature.InputTypes)
|
al := len(n.Signature.InputTypes)
|
||||||
locals := make([]uint64, n.NumLocal+uint32(al))
|
locals := make([]uint64, n.NumLocals+uint32(al))
|
||||||
for i := 0; i < al; i++ {
|
for i := 0; i < al; i++ {
|
||||||
locals[al-1-i] = vm.OperandStack.Pop()
|
locals[al-1-i] = vm.OperandStack.Pop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ func setGlobal(vm *VirtualMachine) {
|
|||||||
vm.ActiveContext.PC++
|
vm.ActiveContext.PC++
|
||||||
index := vm.FetchUint32()
|
index := vm.FetchUint32()
|
||||||
addr := vm.ActiveContext.Function.ModuleInstance.GlobalsAddrs[index]
|
addr := vm.ActiveContext.Function.ModuleInstance.GlobalsAddrs[index]
|
||||||
// TODO: Check mutatability.
|
|
||||||
vm.Store.Globals[addr].Val = vm.OperandStack.Pop()
|
vm.Store.Globals[addr].Val = vm.OperandStack.Pop()
|
||||||
vm.ActiveContext.PC++
|
vm.ActiveContext.PC++
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user