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
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@@ -15,52 +15,58 @@ type ConstantExpression struct {
|
||||
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)
|
||||
switch expr.OptCode {
|
||||
case OptCodeI32Const:
|
||||
v, _, err = leb128.DecodeInt32(r)
|
||||
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:
|
||||
v, _, err = leb128.DecodeInt32(r)
|
||||
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:
|
||||
v, err = readFloat32(r)
|
||||
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:
|
||||
v, err = readFloat64(r)
|
||||
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:
|
||||
id, _, err := leb128.DecodeUint32(r)
|
||||
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 {
|
||||
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]]
|
||||
switch g.Type.ValType {
|
||||
case ValueTypeI32:
|
||||
v = int32(g.Val)
|
||||
return v, ValueTypeI32, nil
|
||||
case ValueTypeI64:
|
||||
v = int64(g.Val)
|
||||
return v, ValueTypeI64, nil
|
||||
case ValueTypeF32:
|
||||
v = math.Float32frombits(uint32(g.Val))
|
||||
return v, ValueTypeF32, nil
|
||||
case ValueTypeF64:
|
||||
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) {
|
||||
|
||||
@@ -56,12 +56,13 @@ const (
|
||||
OptCodeMemorySize OptCode = 0x3f
|
||||
OptCodeMemoryGrow OptCode = 0x40
|
||||
|
||||
// numeric instruction
|
||||
// const instructions.
|
||||
OptCodeI32Const OptCode = 0x41
|
||||
OptCodeI64Const OptCode = 0x42
|
||||
OptCodeF32Const OptCode = 0x43
|
||||
OptCodeF64Const OptCode = 0x44
|
||||
|
||||
// numeric instructions.
|
||||
OptCodeI32eqz OptCode = 0x45
|
||||
OptCodeI32eq OptCode = 0x46
|
||||
OptCodeI32ne OptCode = 0x47
|
||||
|
||||
@@ -70,7 +70,7 @@ func (m *Module) readSections(r *Reader) error {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func (m *Module) readSectionFunctions(r *Reader) error {
|
||||
for i := range m.FunctionSection {
|
||||
m.FunctionSection[i], _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get typeidx: %v", err)
|
||||
return fmt.Errorf("get type index: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -215,7 +215,9 @@ func (m *Module) readSectionExports(r *Reader) error {
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
return nil
|
||||
@@ -257,7 +259,7 @@ func (m *Module) readSectionCodes(r *Reader) error {
|
||||
for i := range m.CodeSection {
|
||||
m.CodeSection[i], err = readCodeSegment(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read code segment: %v", err)
|
||||
return fmt.Errorf("read %d-th code segment: %v", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -3,12 +3,18 @@ package wasm
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
||||
"github.com/mathetake/gasm/wasm/leb128"
|
||||
)
|
||||
|
||||
const (
|
||||
ImportKindFunction = 0x00
|
||||
ImportKindTable = 0x01
|
||||
ImportKindMemory = 0x02
|
||||
ImportKindGlobal = 0x03
|
||||
)
|
||||
|
||||
type ImportDesc struct {
|
||||
Kind byte
|
||||
|
||||
@@ -25,41 +31,40 @@ func readImportDesc(r io.Reader) (*ImportDesc, error) {
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
case 0x00:
|
||||
case ImportKindFunction:
|
||||
tID, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read typeindex: %v", err)
|
||||
}
|
||||
return &ImportDesc{
|
||||
Kind: 0x00,
|
||||
Kind: ImportKindFunction,
|
||||
TypeIndexPtr: &tID,
|
||||
}, nil
|
||||
case 0x01:
|
||||
case ImportKindTable:
|
||||
tt, err := readTableType(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read table type: %v", err)
|
||||
}
|
||||
return &ImportDesc{
|
||||
Kind: 0x01,
|
||||
Kind: ImportKindTable,
|
||||
TableTypePtr: tt,
|
||||
}, nil
|
||||
case 0x02:
|
||||
case ImportKindMemory:
|
||||
mt, err := readMemoryType(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read table type: %v", err)
|
||||
}
|
||||
return &ImportDesc{
|
||||
Kind: 0x02,
|
||||
Kind: ImportKindMemory,
|
||||
MemTypePtr: mt,
|
||||
}, nil
|
||||
case 0x03:
|
||||
case ImportKindGlobal:
|
||||
gt, err := readGlobalType(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read global type: %v", err)
|
||||
}
|
||||
|
||||
return &ImportDesc{
|
||||
Kind: 0x03,
|
||||
Kind: ImportKindGlobal,
|
||||
GlobalTypePtr: gt,
|
||||
}, nil
|
||||
default:
|
||||
@@ -206,8 +211,9 @@ func readElementSegment(r io.Reader) (*ElementSegment, error) {
|
||||
}
|
||||
|
||||
type CodeSegment struct {
|
||||
NumLocals uint32
|
||||
Body []byte
|
||||
NumLocals uint32
|
||||
LocalTypes []ValueType
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func readCodeSegment(r io.Reader) (*CodeSegment, error) {
|
||||
@@ -221,29 +227,46 @@ func readCodeSegment(r io.Reader) (*CodeSegment, error) {
|
||||
// parse locals
|
||||
ls, _, err := leb128.DecodeUint32(r)
|
||||
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)
|
||||
for i := uint32(0); i < ls; i++ {
|
||||
n, _, err := leb128.DecodeUint32(r)
|
||||
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 {
|
||||
return nil, fmt.Errorf("read type of local")
|
||||
_, err = io.ReadFull(r, b)
|
||||
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 {
|
||||
return nil, fmt.Errorf("too many locals: %d", numLocals)
|
||||
if sum > math.MaxUint32 {
|
||||
return nil, fmt.Errorf("too many locals: %d", sum)
|
||||
}
|
||||
|
||||
// extract body
|
||||
body, err := ioutil.ReadAll(r)
|
||||
var localTypes []ValueType
|
||||
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 {
|
||||
return nil, fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
@@ -253,8 +276,9 @@ func readCodeSegment(r io.Reader) (*CodeSegment, error) {
|
||||
}
|
||||
|
||||
return &CodeSegment{
|
||||
Body: body[:len(body)-1],
|
||||
NumLocals: uint32(numLocals),
|
||||
Body: body,
|
||||
NumLocals: uint32(sum),
|
||||
LocalTypes: localTypes,
|
||||
}, 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
|
||||
require.NoError(t, json.Unmarshal(raw, &base))
|
||||
|
||||
wastName := filepath.Base(base.SourceFile)
|
||||
|
||||
t.Run(wastName, func(t *testing.T) {
|
||||
vm, err := wasm.NewVM()
|
||||
require.NoError(t, err)
|
||||
@@ -317,7 +317,17 @@ func TestSpecification(t *testing.T) {
|
||||
t.Fatalf("unsupported action type type: %v", c)
|
||||
}
|
||||
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":
|
||||
// TODO:
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
@@ -116,7 +118,21 @@ func readTableType(r io.Reader) (*TableType, error) {
|
||||
type MemoryType = LimitsType
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
|
||||
"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)
|
||||
}
|
||||
|
||||
if !utf8.Valid(buf) {
|
||||
return "", fmt.Errorf("name must be valid as utf8")
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ func (vm *VirtualMachine) ExecExportedFunction(moduleName, funcName string, args
|
||||
|
||||
exp, ok := m.Exports[funcName]
|
||||
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 {
|
||||
|
||||
@@ -15,7 +15,8 @@ type (
|
||||
}
|
||||
NativeFunction struct {
|
||||
Signature *FunctionType
|
||||
NumLocal uint32
|
||||
NumLocals uint32
|
||||
LocalTypes []ValueType
|
||||
Body []byte
|
||||
Blocks map[uint64]*NativeFunctionBlock
|
||||
ModuleInstance *ModuleInstance
|
||||
@@ -24,6 +25,8 @@ type (
|
||||
StartAt, ElseAt, EndAt uint64
|
||||
BlockType *FunctionType
|
||||
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) {
|
||||
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++ {
|
||||
locals[al-1-i] = vm.OperandStack.Pop()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ func setGlobal(vm *VirtualMachine) {
|
||||
vm.ActiveContext.PC++
|
||||
index := vm.FetchUint32()
|
||||
addr := vm.ActiveContext.Function.ModuleInstance.GlobalsAddrs[index]
|
||||
// TODO: Check mutatability.
|
||||
vm.Store.Globals[addr].Val = vm.OperandStack.Pop()
|
||||
vm.ActiveContext.PC++
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user