Files
wazero/internal/wasm/module_test.go
Crypt Keeper 2664b1eb62 Simplifies API per feedback (#427)
During #425, @neilalexander gave constructive feedback that the API is
both moving fast, and not good enough yet. This attempts to reduce the
incidental complexity at the cost of a little conflation.

### odd presence of `wasm` and `wasi` packages -> `api` package

We had public API packages in wasm and wasi, which helped us avoid
leaking too many internals as public. That these had names that look
like there should be implementations in them cause unnecessary
confusion. This squashes both into one package "api" which has no
package collission with anything.

We've long struggled with the poorly specified and non-uniformly
implemented WASI specification. Trying to bring visibility to its
constraints knowing they are routinely invalid taints our API for no
good reason. This removes all `WASI` commands for a default to invoke
the function `_start` if it exists. In doing so, there's only one path
to start a module.

Moreover, this puts all wasi code in a top-level package "wasi" as it
isn't re-imported by any internal types.

### Reuse of Module for pre and post instantiation to `Binary` -> `Module`

Module is defined by WebAssembly in many phases, from decoded to
instantiated. However, using the same noun in multiple packages is very
confusing. We at one point tried a name "DecodedModule" or
"InstantiatedModule", but this is a fools errand. By deviating slightly
from the spec we can make it unambiguous what a module is.

This make a result of compilation a `Binary`, retaining `Module` for an
instantiated one. In doing so, there's no longer any name conflicts
whatsoever.

### Confusion about config -> `ModuleConfig`

Also caused by splitting wasm into wasm+wasi is configuration. This
conflates both into the same type `ModuleConfig` as it is simpler than
trying to explain a "will never be finished" api of wasi snapshot-01 in
routine use of WebAssembly. In other words, this further moves WASI out
of the foreground as it has been nothing but burden.

```diff
--- a/README.md
+++ b/README.md
@@ -49,8 +49,8 @@ For example, here's how you can allow WebAssembly modules to read
-wm, err := r.InstantiateModule(wazero.WASISnapshotPreview1())
-defer wm.Close()
+wm, err := wasi.InstantiateSnapshotPreview1(r)
+defer wm.Close()

-sysConfig := wazero.NewSysConfig().WithFS(os.DirFS("/work/home"))
-module, err := wazero.StartWASICommandWithConfig(r, compiled, sysConfig)
+config := wazero.ModuleConfig().WithFS(os.DirFS("/work/home"))
+module, err := r.InstantiateModule(binary, config)
 defer module.Close()
 ...
```
2022-04-02 06:42:36 +08:00

729 lines
22 KiB
Go

package wasm
import (
"encoding/binary"
"fmt"
"math"
"reflect"
"testing"
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api"
)
func TestFunctionType_String(t *testing.T) {
for _, tc := range []struct {
functype *FunctionType
exp string
}{
{functype: &FunctionType{}, exp: "v_v"},
{functype: &FunctionType{Params: []ValueType{ValueTypeI32}}, exp: "i32_v"},
{functype: &FunctionType{Params: []ValueType{ValueTypeI32, ValueTypeF64}}, exp: "i32f64_v"},
{functype: &FunctionType{Params: []ValueType{ValueTypeF32, ValueTypeI32, ValueTypeF64}}, exp: "f32i32f64_v"},
{functype: &FunctionType{Results: []ValueType{ValueTypeI64}}, exp: "v_i64"},
{functype: &FunctionType{Results: []ValueType{ValueTypeI64, ValueTypeF32}}, exp: "v_i64f32"},
{functype: &FunctionType{Results: []ValueType{ValueTypeF32, ValueTypeI32, ValueTypeF64}}, exp: "v_f32i32f64"},
{functype: &FunctionType{Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI64}}, exp: "i32_i64"},
{functype: &FunctionType{Params: []ValueType{ValueTypeI64, ValueTypeF32}, Results: []ValueType{ValueTypeI64, ValueTypeF32}}, exp: "i64f32_i64f32"},
{functype: &FunctionType{Params: []ValueType{ValueTypeI64, ValueTypeF32, ValueTypeF64}, Results: []ValueType{ValueTypeF32, ValueTypeI32, ValueTypeF64}}, exp: "i64f32f64_f32i32f64"},
} {
tc := tc
t.Run(tc.functype.String(), func(t *testing.T) {
require.Equal(t, tc.exp, tc.functype.String())
require.Equal(t, tc.exp, tc.functype.key())
require.Equal(t, tc.exp, tc.functype.string)
})
}
}
func TestSectionIDName(t *testing.T) {
tests := []struct {
name string
input SectionID
expected string
}{
{"custom", SectionIDCustom, "custom"},
{"type", SectionIDType, "type"},
{"import", SectionIDImport, "import"},
{"function", SectionIDFunction, "function"},
{"table", SectionIDTable, "table"},
{"memory", SectionIDMemory, "memory"},
{"global", SectionIDGlobal, "global"},
{"export", SectionIDExport, "export"},
{"start", SectionIDStart, "start"},
{"element", SectionIDElement, "element"},
{"code", SectionIDCode, "code"},
{"data", SectionIDData, "data"},
{"host_function", SectionIDHostFunction, "host_function"},
{"unknown", 100, "unknown"},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expected, SectionIDName(tc.input))
})
}
}
func TestExternTypeName(t *testing.T) {
tests := []struct {
name string
input ExternType
expected string
}{
{"func", ExternTypeFunc, "func"},
{"table", ExternTypeTable, "table"},
{"mem", ExternTypeMemory, "memory"},
{"global", ExternTypeGlobal, "global"},
{"unknown", 100, "0x64"},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expected, ExternTypeName(tc.input))
})
}
}
func TestModule_allDeclarations(t *testing.T) {
for i, tc := range []struct {
module *Module
expectedFunctions []Index
expectedGlobals []*GlobalType
expectedMemory *Memory
expectedTable *Table
}{
// Functions.
{
module: &Module{
ImportSection: []*Import{{Type: ExternTypeFunc, DescFunc: 10000}},
FunctionSection: []Index{10, 20, 30},
},
expectedFunctions: []Index{10000, 10, 20, 30},
},
{
module: &Module{
FunctionSection: []Index{10, 20, 30},
},
expectedFunctions: []Index{10, 20, 30},
},
{
module: &Module{
ImportSection: []*Import{{Type: ExternTypeFunc, DescFunc: 10000}},
},
expectedFunctions: []Index{10000},
},
// Globals.
{
module: &Module{
ImportSection: []*Import{{Type: ExternTypeGlobal, DescGlobal: &GlobalType{Mutable: false}}},
GlobalSection: []*Global{{Type: &GlobalType{Mutable: true}}},
},
expectedGlobals: []*GlobalType{{Mutable: false}, {Mutable: true}},
},
{
module: &Module{
GlobalSection: []*Global{{Type: &GlobalType{Mutable: true}}},
},
expectedGlobals: []*GlobalType{{Mutable: true}},
},
{
module: &Module{
ImportSection: []*Import{{Type: ExternTypeGlobal, DescGlobal: &GlobalType{Mutable: false}}},
},
expectedGlobals: []*GlobalType{{Mutable: false}},
},
// Memories.
{
module: &Module{
ImportSection: []*Import{{Type: ExternTypeMemory, DescMem: &Memory{Min: 1, Max: 10}}},
},
expectedMemory: &Memory{Min: 1, Max: 10},
},
{
module: &Module{
MemorySection: &Memory{Min: 100},
},
expectedMemory: &Memory{Min: 100},
},
// Tables.
{
module: &Module{
ImportSection: []*Import{{Type: ExternTypeTable, DescTable: &Table{Min: 1}}},
},
expectedTable: &Table{Min: 1},
},
{
module: &Module{
TableSection: &Table{Min: 10},
},
expectedTable: &Table{Min: 10},
},
} {
tc := tc
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
functions, globals, memory, table, err := tc.module.allDeclarations()
require.NoError(t, err)
require.Equal(t, tc.expectedFunctions, functions)
require.Equal(t, tc.expectedGlobals, globals)
require.Equal(t, tc.expectedTable, table)
require.Equal(t, tc.expectedMemory, memory)
})
}
}
func TestValidateConstExpression(t *testing.T) {
t.Run("invalid opcode", func(t *testing.T) {
expr := &ConstantExpression{Opcode: OpcodeNop}
err := validateConstExpression(nil, expr, valueTypeUnknown)
require.Error(t, err)
})
for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
t.Run(ValueTypeName(vt), func(t *testing.T) {
t.Run("valid", func(t *testing.T) {
// Allocate bytes with enough size for all types.
expr := &ConstantExpression{Data: make([]byte, 8)}
switch vt {
case ValueTypeI32:
expr.Data[0] = 1
expr.Opcode = OpcodeI32Const
case ValueTypeI64:
expr.Data[0] = 2
expr.Opcode = OpcodeI64Const
case ValueTypeF32:
binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32))
expr.Opcode = OpcodeF32Const
case ValueTypeF64:
binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64))
expr.Opcode = OpcodeF64Const
}
err := validateConstExpression(nil, expr, vt)
require.NoError(t, err)
})
t.Run("invalid", func(t *testing.T) {
// Empty data must be failure.
expr := &ConstantExpression{Data: make([]byte, 0)}
switch vt {
case ValueTypeI32:
expr.Opcode = OpcodeI32Const
case ValueTypeI64:
expr.Opcode = OpcodeI64Const
case ValueTypeF32:
expr.Opcode = OpcodeF32Const
case ValueTypeF64:
expr.Opcode = OpcodeF64Const
}
err := validateConstExpression(nil, expr, vt)
require.Error(t, err)
})
})
}
t.Run("global expr", func(t *testing.T) {
t.Run("failed to read global index", func(t *testing.T) {
// Empty data for global index is invalid.
expr := &ConstantExpression{Data: make([]byte, 0), Opcode: OpcodeGlobalGet}
err := validateConstExpression(nil, expr, valueTypeUnknown)
require.Error(t, err)
})
t.Run("global index out of range", func(t *testing.T) {
// Data holds the index in leb128 and this time the value exceeds len(globals) (=0).
expr := &ConstantExpression{Data: []byte{1}, Opcode: OpcodeGlobalGet}
var globals []*GlobalType
err := validateConstExpression(globals, expr, valueTypeUnknown)
require.Error(t, err)
})
t.Run("type mismatch", func(t *testing.T) {
for _, vt := range []ValueType{
ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64,
} {
t.Run(ValueTypeName(vt), func(t *testing.T) {
// The index specified in Data equals zero.
expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
globals := []*GlobalType{{ValType: valueTypeUnknown}}
err := validateConstExpression(globals, expr, vt)
require.Error(t, err)
})
}
})
t.Run("ok", func(t *testing.T) {
for _, vt := range []ValueType{
ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64,
} {
t.Run(ValueTypeName(vt), func(t *testing.T) {
// The index specified in Data equals zero.
expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
globals := []*GlobalType{{ValType: vt}}
err := validateConstExpression(globals, expr, vt)
require.NoError(t, err)
})
}
})
})
}
func TestModule_Validate_Errors(t *testing.T) {
zero := Index(0)
fn := reflect.ValueOf(func(api.Module) {})
tests := []struct {
name string
input *Module
expectedErr string
}{
{
name: "StartSection points to an invalid func",
input: &Module{
TypeSection: nil,
FunctionSection: []uint32{0},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
StartSection: &zero,
},
expectedErr: "invalid start function: func[0] has an invalid type",
},
{
name: "CodeSection and HostFunctionSection",
input: &Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []uint32{0},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
HostFunctionSection: []*reflect.Value{&fn},
},
expectedErr: "cannot mix functions and host functions in the same module",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
err := tc.input.Validate(Features20191205)
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestModule_validateStartSection(t *testing.T) {
t.Run("no start section", func(t *testing.T) {
m := Module{}
err := m.validateStartSection()
require.NoError(t, err)
})
t.Run("invalid type", func(t *testing.T) {
for _, ft := range []*FunctionType{
{Params: []ValueType{ValueTypeI32}},
{Results: []ValueType{ValueTypeI32}},
{Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI32}},
} {
t.Run(ft.String(), func(t *testing.T) {
index := uint32(0)
m := Module{StartSection: &index, FunctionSection: []uint32{0}, TypeSection: []*FunctionType{ft}}
err := m.validateStartSection()
require.Error(t, err)
})
}
})
}
func TestModule_validateGlobals(t *testing.T) {
t.Run("too many globals", func(t *testing.T) {
m := Module{}
err := m.validateGlobals(make([]*GlobalType, 10), 9)
require.Error(t, err)
require.EqualError(t, err, "too many globals in a module")
})
t.Run("global index out of range", func(t *testing.T) {
m := Module{GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI32},
// Trying to reference globals[1] which is not imported.
Init: &ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}},
},
}}
err := m.validateGlobals(nil, 9)
require.Error(t, err)
require.EqualError(t, err, "global index out of range")
})
t.Run("invalid const expression", func(t *testing.T) {
m := Module{GlobalSection: []*Global{
{
Type: &GlobalType{ValType: valueTypeUnknown},
Init: &ConstantExpression{Opcode: OpcodeUnreachable},
},
}}
err := m.validateGlobals(nil, 9)
require.Error(t, err)
require.EqualError(t, err, "invalid opcode for const expression: 0x0")
})
t.Run("ok", func(t *testing.T) {
m := Module{GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}},
},
}}
err := m.validateGlobals(nil, 9)
require.NoError(t, err)
})
t.Run("ok with imported global", func(t *testing.T) {
m := Module{
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI32},
// Trying to reference globals[1] which is imported.
Init: &ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0}},
},
},
ImportSection: []*Import{{Type: ExternTypeGlobal}},
}
globalDeclarations := []*GlobalType{
{ValType: ValueTypeI32}, // Imported one.
nil, // the local one trying to validate.
}
err := m.validateGlobals(globalDeclarations, 9)
require.NoError(t, err)
})
}
func TestModule_validateFunctions(t *testing.T) {
t.Run("ok", func(t *testing.T) {
m := Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []uint32{0},
CodeSection: []*Code{{Body: []byte{OpcodeI32Const, 0, OpcodeDrop, OpcodeEnd}}},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.NoError(t, err)
})
t.Run("too many functions", func(t *testing.T) {
m := Module{}
err := m.validateFunctions(Features20191205, []uint32{1, 2, 3, 4}, nil, nil, nil, 3)
require.Error(t, err)
require.EqualError(t, err, "too many functions in a store")
})
t.Run("function, but no code", func(t *testing.T) {
m := Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: nil,
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
require.EqualError(t, err, "code count (0) != function count (1)")
})
t.Run("function out of range of code", func(t *testing.T) {
m := Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{1},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
require.EqualError(t, err, "invalid function[0]: type section index 1 out of range")
})
t.Run("invalid", func(t *testing.T) {
m := Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid function[0]: cannot pop the 1st f32 operand")
})
t.Run("in- exported", func(t *testing.T) {
m := Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
ExportSection: map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 0}},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
require.Contains(t, err.Error(), `invalid function[0] export["f1"]: cannot pop the 1st f32`)
})
t.Run("in- exported after import", func(t *testing.T) {
m := Module{
TypeSection: []*FunctionType{{}},
ImportSection: []*Import{{Type: ExternTypeFunc}},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
ExportSection: map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 1}},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
require.Contains(t, err.Error(), `invalid function[0] export["f1"]: cannot pop the 1st f32`)
})
t.Run("in- exported twice", func(t *testing.T) {
m := Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
ExportSection: map[string]*Export{
"f1": {Name: "f1", Type: ExternTypeFunc, Index: 0},
"f2": {Name: "f2", Type: ExternTypeFunc, Index: 0},
},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
require.Contains(t, err.Error(), `invalid function[0] export["f1","f2"]: cannot pop the 1st f32`)
})
}
func TestModule_validateMemory(t *testing.T) {
t.Run("data section exits but memory not declared", func(t *testing.T) {
m := Module{DataSection: make([]*DataSegment, 1)}
err := m.validateMemory(nil, nil)
require.Error(t, err)
require.Contains(t, "unknown memory", err.Error())
})
t.Run("invalid const expr", func(t *testing.T) {
m := Module{DataSection: []*DataSegment{{
OffsetExpression: &ConstantExpression{
Opcode: OpcodeUnreachable, // Invalid!
},
}}}
err := m.validateMemory(&Memory{}, nil)
require.EqualError(t, err, "calculate offset: invalid opcode for const expression: 0x0")
})
t.Run("ok", func(t *testing.T) {
m := Module{DataSection: []*DataSegment{{
Init: []byte{0x1},
OffsetExpression: &ConstantExpression{
Opcode: OpcodeI32Const,
Data: []byte{0x1},
},
}}}
err := m.validateMemory(&Memory{}, nil)
require.NoError(t, err)
})
}
func TestModule_validateImports(t *testing.T) {
for _, tc := range []struct {
name string
enabledFeatures Features
i *Import
expectedErr string
}{
{name: "empty import section"},
{
name: "func",
enabledFeatures: Features20191205,
i: &Import{Module: "m", Name: "n", Type: ExternTypeFunc, DescFunc: 0},
},
{
name: "global var disabled",
enabledFeatures: Features20191205.Set(FeatureMutableGlobal, false),
i: &Import{
Module: "m",
Name: "n",
Type: ExternTypeGlobal,
DescGlobal: &GlobalType{ValType: ValueTypeI32, Mutable: true},
},
expectedErr: `invalid import["m"."n"] global: feature mutable-global is disabled`,
},
{
name: "table",
enabledFeatures: Features20191205,
i: &Import{
Module: "m",
Name: "n",
Type: ExternTypeTable,
DescTable: &Table{Min: 1},
},
},
{
name: "memory",
enabledFeatures: Features20191205,
i: &Import{
Module: "m",
Name: "n",
Type: ExternTypeMemory,
DescMem: &Memory{Min: 1},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
m := Module{}
if tc.i != nil {
m.ImportSection = []*Import{tc.i}
}
err := m.validateImports(tc.enabledFeatures)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_validateExports(t *testing.T) {
for _, tc := range []struct {
name string
enabledFeatures Features
exportSection map[string]*Export
functions []Index
globals []*GlobalType
memory *Memory
table *Table
expectedErr string
}{
{name: "empty export section", exportSection: map[string]*Export{}},
{
name: "func",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeFunc, Index: 0}},
functions: []Index{100 /* arbitrary type id*/},
},
{
name: "func out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeFunc, Index: 1}},
functions: []Index{100 /* arbitrary type id*/},
expectedErr: `unknown function for export["e1"]`,
},
{
name: "global const",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 0}},
globals: []*GlobalType{{ValType: ValueTypeI32}},
},
{
name: "global var",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 0}},
globals: []*GlobalType{{ValType: ValueTypeI32, Mutable: true}},
},
{
name: "global var disabled",
enabledFeatures: Features20191205.Set(FeatureMutableGlobal, false),
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 0}},
globals: []*GlobalType{{ValType: ValueTypeI32, Mutable: true}},
expectedErr: `invalid export["e1"] global[0]: feature mutable-global is disabled`,
},
{
name: "global out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 1}},
globals: []*GlobalType{{}},
expectedErr: `unknown global for export["e1"]`,
},
{
name: "table",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeTable, Index: 0}},
table: &Table{},
},
{
name: "table out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeTable, Index: 1}},
table: &Table{},
expectedErr: `table for export["e1"] out of range`,
},
{
name: "memory",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeMemory, Index: 0}},
memory: &Memory{},
},
{
name: "memory out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeMemory, Index: 0}},
table: &limitsType{},
expectedErr: `memory for export["e1"] out of range`,
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
m := Module{ExportSection: tc.exportSection}
err := m.validateExports(tc.enabledFeatures, tc.functions, tc.globals, tc.memory, tc.table)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_buildGlobalInstances(t *testing.T) {
data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
binary.LittleEndian.PutUint64(data, math.Float64bits(1.0))
m := Module{GlobalSection: []*Global{
{
Type: &GlobalType{Mutable: true, ValType: ValueTypeF64},
Init: &ConstantExpression{Opcode: OpcodeF64Const,
Data: []byte{0, 0, 0, 0, 0, 0, 0xf0, 0x3f}}, // == float64(1.0)
},
{
Type: &GlobalType{Mutable: false, ValType: ValueTypeI32},
Init: &ConstantExpression{Opcode: OpcodeI32Const,
Data: []byte{1}},
},
}}
globals := m.buildGlobals(nil)
expectedGlobals := []*GlobalInstance{
{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: math.Float64bits(1.0)},
{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(1)},
}
require.Len(t, globals, len(expectedGlobals))
for i := range globals {
actual, expected := globals[i], expectedGlobals[i]
require.Equal(t, expected, actual)
}
}
func TestModule_buildFunctionInstances(t *testing.T) {
nopCode := &Code{nil, []byte{OpcodeEnd}}
m := Module{
ImportSection: []*Import{{Type: ExternTypeFunc}},
NameSection: &NameSection{
FunctionNames: NameMap{
{Index: Index(2), Name: "two"},
{Index: Index(4), Name: "four"},
{Index: Index(5), Name: "five"},
},
},
CodeSection: []*Code{nopCode, nopCode, nopCode, nopCode, nopCode},
}
actual := m.buildFunctions()
expectedNames := []string{"unknown", "two", "unknown", "four", "five"}
for i, f := range actual {
require.Equal(t, expectedNames[i], f.Name)
require.Equal(t, nopCode.Body, f.Body)
}
}
func TestModule_buildMemoryInstance(t *testing.T) {
t.Run("nil", func(t *testing.T) {
m := Module{}
mem := m.buildMemory()
require.Nil(t, mem)
})
t.Run("non-nil", func(t *testing.T) {
min := uint32(1)
max := uint32(10)
m := Module{MemorySection: &Memory{Min: min, Max: max}}
mem := m.buildMemory()
require.Equal(t, min, mem.Min)
require.Equal(t, max, mem.Max)
})
}