This switches to gofumpt and applies changes, as I've noticed working in dapr (who uses this) that it finds some things that are annoying, such as inconsistent block formatting in test tables. Signed-off-by: Adrian Cole <adrian@tetrate.io>
971 lines
29 KiB
Go
971 lines
29 KiB
Go
package wasm
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/internal/leb128"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/u64"
|
|
)
|
|
|
|
func TestFunctionType_String(t *testing.T) {
|
|
tests := []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"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
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"},
|
|
{"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 TestMemory_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mem *Memory
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "ok",
|
|
mem: &Memory{Min: 2, Cap: 2, Max: 2},
|
|
},
|
|
{
|
|
name: "cap < min",
|
|
mem: &Memory{Min: 2, Cap: 1, Max: 2},
|
|
expectedErr: "capacity 1 pages (64 Ki) less than minimum 2 pages (128 Ki)",
|
|
},
|
|
{
|
|
name: "cap > maxLimit",
|
|
mem: &Memory{Min: 2, Cap: math.MaxUint32, Max: 2},
|
|
expectedErr: "capacity 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)",
|
|
},
|
|
{
|
|
name: "max < min",
|
|
mem: &Memory{Min: 2, Cap: 2, Max: 0, IsMaxEncoded: true},
|
|
expectedErr: "min 2 pages (128 Ki) > max 0 pages (0 Ki)",
|
|
},
|
|
{
|
|
name: "min > limit",
|
|
mem: &Memory{Min: math.MaxUint32},
|
|
expectedErr: "min 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)",
|
|
},
|
|
{
|
|
name: "max > limit",
|
|
mem: &Memory{Max: math.MaxUint32, IsMaxEncoded: true},
|
|
expectedErr: "max 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := tc.mem.Validate(MemoryLimitPages)
|
|
if tc.expectedErr == "" {
|
|
require.NoError(t, err)
|
|
} else {
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModule_allDeclarations(t *testing.T) {
|
|
tests := []struct {
|
|
module *Module
|
|
expectedFunctions []Index
|
|
expectedGlobals []*GlobalType
|
|
expectedMemory *Memory
|
|
expectedTables []*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}}},
|
|
},
|
|
expectedTables: []*Table{{Min: 1}},
|
|
},
|
|
{
|
|
module: &Module{
|
|
TableSection: []*Table{{Min: 10}},
|
|
},
|
|
expectedTables: []*Table{{Min: 10}},
|
|
},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
tc := tt
|
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
functions, globals, memory, tables, err := tc.module.AllDeclarations()
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedFunctions, functions)
|
|
require.Equal(t, tc.expectedGlobals, globals)
|
|
require.Equal(t, tc.expectedTables, tables)
|
|
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, 0, 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) {
|
|
expr := &ConstantExpression{}
|
|
switch vt {
|
|
case ValueTypeI32:
|
|
expr.Data = []byte{1}
|
|
expr.Opcode = OpcodeI32Const
|
|
case ValueTypeI64:
|
|
expr.Data = []byte{2}
|
|
expr.Opcode = OpcodeI64Const
|
|
case ValueTypeF32:
|
|
expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
|
|
expr.Opcode = OpcodeF32Const
|
|
case ValueTypeF64:
|
|
expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
|
|
expr.Opcode = OpcodeF64Const
|
|
}
|
|
|
|
err := validateConstExpression(nil, 0, 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, 0, expr, vt)
|
|
require.Error(t, err)
|
|
})
|
|
})
|
|
}
|
|
t.Run("ref types", func(t *testing.T) {
|
|
t.Run("ref.func", func(t *testing.T) {
|
|
expr := &ConstantExpression{Data: []byte{5}, Opcode: OpcodeRefFunc}
|
|
err := validateConstExpression(nil, 10, expr, ValueTypeFuncref)
|
|
require.NoError(t, err)
|
|
err = validateConstExpression(nil, 2, expr, ValueTypeFuncref)
|
|
require.EqualError(t, err, "ref.func index out of range [5] with length 1")
|
|
})
|
|
t.Run("ref.null", func(t *testing.T) {
|
|
err := validateConstExpression(nil, 0,
|
|
&ConstantExpression{Data: []byte{ValueTypeFuncref}, Opcode: OpcodeRefNull},
|
|
ValueTypeFuncref)
|
|
require.NoError(t, err)
|
|
err = validateConstExpression(nil, 0,
|
|
&ConstantExpression{Data: []byte{ValueTypeExternref}, Opcode: OpcodeRefNull},
|
|
ValueTypeExternref)
|
|
require.NoError(t, err)
|
|
err = validateConstExpression(nil, 0,
|
|
&ConstantExpression{Data: []byte{0xff}, Opcode: OpcodeRefNull},
|
|
ValueTypeExternref)
|
|
require.EqualError(t, err, "invalid type for ref.null: 0xff")
|
|
})
|
|
})
|
|
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, 0, 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, 0, 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, 0, 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, 0, expr, vt)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestModule_Validate_Errors(t *testing.T) {
|
|
zero := Index(0)
|
|
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",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := tc.input.Validate(api.CoreFeaturesV1)
|
|
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)
|
|
})
|
|
}
|
|
})
|
|
t.Run("imported valid func", func(t *testing.T) {
|
|
index := Index(1)
|
|
m := Module{
|
|
StartSection: &index,
|
|
TypeSection: []*FunctionType{{}, {Results: []ValueType{ValueTypeI32}}},
|
|
ImportSection: []*Import{
|
|
{Type: ExternTypeFunc, DescFunc: 1},
|
|
// import with index 1 is global but this should be skipped when searching imported functions.
|
|
{Type: ExternTypeGlobal},
|
|
{Type: ExternTypeFunc, DescFunc: 0}, // This one must be selected.
|
|
},
|
|
}
|
|
err := m.validateStartSection()
|
|
require.NoError(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), 0, 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, 0, 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, 0, 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: const0},
|
|
},
|
|
}}
|
|
err := m.validateGlobals(nil, 0, 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, 0, 9)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestModule_validateFunctions(t *testing.T) {
|
|
t.Run("ok", func(t *testing.T) {
|
|
m := Module{
|
|
TypeSection: []*FunctionType{v_v},
|
|
FunctionSection: []uint32{0},
|
|
CodeSection: []*Code{{Body: []byte{OpcodeI32Const, 0, OpcodeDrop, OpcodeEnd}}},
|
|
}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex)
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("too many functions", func(t *testing.T) {
|
|
m := Module{}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, []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{v_v},
|
|
FunctionSection: []Index{0},
|
|
CodeSection: nil,
|
|
}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, 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{v_v},
|
|
FunctionSection: []Index{1},
|
|
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
|
|
}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, 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{v_v},
|
|
FunctionSection: []Index{0},
|
|
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
|
|
}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, 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{v_v},
|
|
FunctionSection: []Index{0},
|
|
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
|
|
ExportSection: []*Export{{Name: "f1", Type: ExternTypeFunc, Index: 0}},
|
|
}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, 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{v_v},
|
|
ImportSection: []*Import{{Type: ExternTypeFunc}},
|
|
FunctionSection: []Index{0},
|
|
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
|
|
ExportSection: []*Export{{Name: "f1", Type: ExternTypeFunc, Index: 1}},
|
|
}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, 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{v_v},
|
|
FunctionSection: []Index{0},
|
|
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
|
|
ExportSection: []*Export{
|
|
{Name: "f1", Type: ExternTypeFunc, Index: 0},
|
|
{Name: "f2", Type: ExternTypeFunc, Index: 0},
|
|
},
|
|
}
|
|
err := m.validateFunctions(api.CoreFeaturesV1, 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("active data segment exits but memory not declared", func(t *testing.T) {
|
|
m := Module{DataSection: []*DataSegment{{OffsetExpression: &ConstantExpression{}}}}
|
|
err := m.validateMemory(nil, nil, api.CoreFeaturesV1)
|
|
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, api.CoreFeaturesV1)
|
|
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: leb128.EncodeInt32(1),
|
|
},
|
|
}}}
|
|
err := m.validateMemory(&Memory{}, nil, api.CoreFeaturesV1)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestModule_validateImports(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
enabledFeatures api.CoreFeatures
|
|
i *Import
|
|
expectedErr string
|
|
}{
|
|
{name: "empty import section"},
|
|
{
|
|
name: "func",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
i: &Import{Module: "m", Name: "n", Type: ExternTypeFunc, DescFunc: 0},
|
|
},
|
|
{
|
|
name: "global var disabled",
|
|
enabledFeatures: api.CoreFeaturesV1.SetEnabled(api.CoreFeatureMutableGlobal, 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: api.CoreFeaturesV1,
|
|
i: &Import{
|
|
Module: "m",
|
|
Name: "n",
|
|
Type: ExternTypeTable,
|
|
DescTable: &Table{Min: 1},
|
|
},
|
|
},
|
|
{
|
|
name: "memory",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
i: &Import{
|
|
Module: "m",
|
|
Name: "n",
|
|
Type: ExternTypeMemory,
|
|
DescMem: &Memory{Min: 1},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
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) {
|
|
tests := []struct {
|
|
name string
|
|
enabledFeatures api.CoreFeatures
|
|
exportSection []*Export
|
|
functions []Index
|
|
globals []*GlobalType
|
|
memory *Memory
|
|
tables []*Table
|
|
expectedErr string
|
|
}{
|
|
{name: "empty export section", exportSection: []*Export{}},
|
|
{
|
|
name: "func",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeFunc, Index: 0}},
|
|
functions: []Index{100 /* arbitrary type id*/},
|
|
},
|
|
{
|
|
name: "func out of range",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeFunc, Index: 1, Name: "e"}},
|
|
functions: []Index{100 /* arbitrary type id*/},
|
|
expectedErr: `unknown function for export["e"]`,
|
|
},
|
|
{
|
|
name: "global const",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 0}},
|
|
globals: []*GlobalType{{ValType: ValueTypeI32}},
|
|
},
|
|
{
|
|
name: "global var",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 0}},
|
|
globals: []*GlobalType{{ValType: ValueTypeI32, Mutable: true}},
|
|
},
|
|
{
|
|
name: "global var disabled",
|
|
enabledFeatures: api.CoreFeaturesV1.SetEnabled(api.CoreFeatureMutableGlobal, false),
|
|
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 0, Name: "e"}},
|
|
globals: []*GlobalType{{ValType: ValueTypeI32, Mutable: true}},
|
|
expectedErr: `invalid export["e"] global[0]: feature "mutable-global" is disabled`,
|
|
},
|
|
{
|
|
name: "global out of range",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 1, Name: "e"}},
|
|
globals: []*GlobalType{{}},
|
|
expectedErr: `unknown global for export["e"]`,
|
|
},
|
|
{
|
|
name: "table",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeTable, Index: 0}},
|
|
tables: []*Table{{}},
|
|
},
|
|
{
|
|
name: "multiple tables",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeTable, Index: 0}, {Type: ExternTypeTable, Index: 1}, {Type: ExternTypeTable, Index: 2}},
|
|
tables: []*Table{{}, {}, {}},
|
|
},
|
|
{
|
|
name: "table out of range",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeTable, Index: 1, Name: "e"}},
|
|
tables: []*Table{},
|
|
expectedErr: `table for export["e"] out of range`,
|
|
},
|
|
{
|
|
name: "memory",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeMemory, Index: 0}},
|
|
memory: &Memory{},
|
|
},
|
|
{
|
|
name: "memory out of range",
|
|
enabledFeatures: api.CoreFeaturesV1,
|
|
exportSection: []*Export{{Type: ExternTypeMemory, Index: 0, Name: "e"}},
|
|
tables: []*Table{},
|
|
expectedErr: `memory for export["e"] out of range`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
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.tables)
|
|
if tc.expectedErr != "" {
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModule_buildGlobals(t *testing.T) {
|
|
minusOne := int32(-1)
|
|
m := Module{GlobalSection: []*Global{
|
|
{
|
|
Type: &GlobalType{Mutable: true, ValType: ValueTypeF64},
|
|
Init: &ConstantExpression{
|
|
Opcode: OpcodeF64Const,
|
|
Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64)),
|
|
},
|
|
},
|
|
{
|
|
Type: &GlobalType{Mutable: false, ValType: ValueTypeI32},
|
|
Init: &ConstantExpression{
|
|
Opcode: OpcodeI32Const,
|
|
Data: leb128.EncodeInt32(math.MaxInt32),
|
|
},
|
|
},
|
|
{
|
|
Type: &GlobalType{Mutable: false, ValType: ValueTypeI32},
|
|
Init: &ConstantExpression{
|
|
Opcode: OpcodeI32Const,
|
|
Data: leb128.EncodeInt32(minusOne),
|
|
},
|
|
},
|
|
{
|
|
Type: &GlobalType{Mutable: false, ValType: ValueTypeV128},
|
|
Init: &ConstantExpression{
|
|
Opcode: OpcodeVecV128Const,
|
|
Data: []byte{
|
|
1, 0, 0, 0, 0, 0, 0, 0,
|
|
2, 0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
|
|
globals := m.buildGlobals(nil)
|
|
expectedGlobals := []*GlobalInstance{
|
|
{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: api.EncodeF64(math.MaxFloat64)},
|
|
{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(int32(math.MaxInt32))},
|
|
// Higher bits are must be zeroed for i32 globals, not signed-extended. See #656.
|
|
{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(uint32(minusOne))},
|
|
{Type: &GlobalType{ValType: ValueTypeV128, Mutable: false}, Val: 0x1, ValHi: 0x2},
|
|
}
|
|
require.Equal(t, expectedGlobals, globals)
|
|
}
|
|
|
|
func TestModule_buildFunctions(t *testing.T) {
|
|
nopCode := &Code{Body: []byte{OpcodeEnd}}
|
|
m := &Module{
|
|
TypeSection: []*FunctionType{v_v},
|
|
ImportSection: []*Import{{Type: ExternTypeFunc}},
|
|
FunctionSection: []Index{0, 0, 0, 0, 0},
|
|
CodeSection: []*Code{nopCode, nopCode, nopCode, nopCode, nopCode},
|
|
FunctionDefinitionSection: []*FunctionDefinition{
|
|
{index: 0, funcType: v_v},
|
|
{index: 1, funcType: v_v},
|
|
{index: 2, funcType: v_v, name: "two"},
|
|
{index: 3, funcType: v_v},
|
|
{index: 4, funcType: v_v, name: "four"},
|
|
{index: 5, funcType: v_v, name: "five"},
|
|
},
|
|
}
|
|
|
|
// Note: This only returns module-defined functions, not imported ones. That's why the index starts with 1, not 0.
|
|
instance := &ModuleInstance{Name: "counter", TypeIDs: []FunctionTypeID{0}}
|
|
instance.BuildFunctions(m, nil)
|
|
for i, f := range instance.Functions {
|
|
require.Equal(t, i, f.Definition.Index())
|
|
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, Cap: min, Max: max}}
|
|
mem := m.buildMemory()
|
|
require.Equal(t, min, mem.Min)
|
|
require.Equal(t, max, mem.Max)
|
|
})
|
|
}
|
|
|
|
func TestModule_validateDataCountSection(t *testing.T) {
|
|
t.Run("ok", func(t *testing.T) {
|
|
for _, m := range []*Module{
|
|
{
|
|
DataSection: []*DataSegment{},
|
|
DataCountSection: nil,
|
|
},
|
|
{
|
|
DataSection: []*DataSegment{{}, {}},
|
|
DataCountSection: nil,
|
|
},
|
|
} {
|
|
err := m.validateDataCountSection()
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
t.Run("error", func(t *testing.T) {
|
|
count := uint32(1)
|
|
for _, m := range []*Module{
|
|
{
|
|
DataSection: []*DataSegment{},
|
|
DataCountSection: &count,
|
|
},
|
|
{
|
|
DataSection: []*DataSegment{{}, {}},
|
|
DataCountSection: &count,
|
|
},
|
|
} {
|
|
err := m.validateDataCountSection()
|
|
require.Error(t, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestModule_declaredFunctionIndexes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mod *Module
|
|
exp map[Index]struct{}
|
|
expErr string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
mod: &Module{},
|
|
exp: map[uint32]struct{}{},
|
|
},
|
|
{
|
|
name: "global",
|
|
mod: &Module{
|
|
ExportSection: []*Export{
|
|
{Index: 10, Type: ExternTypeFunc},
|
|
{Index: 1000, Type: ExternTypeGlobal},
|
|
},
|
|
},
|
|
exp: map[uint32]struct{}{10: {}},
|
|
},
|
|
{
|
|
name: "export",
|
|
mod: &Module{
|
|
ExportSection: []*Export{
|
|
{Index: 1000, Type: ExternTypeGlobal},
|
|
{Index: 10, Type: ExternTypeFunc},
|
|
},
|
|
},
|
|
exp: map[uint32]struct{}{10: {}},
|
|
},
|
|
{
|
|
name: "element",
|
|
mod: &Module{
|
|
ElementSection: []*ElementSegment{
|
|
{
|
|
Mode: ElementModeActive,
|
|
Init: []*Index{uint32Ptr(0), nil, uint32Ptr(5)},
|
|
},
|
|
{
|
|
Mode: ElementModeDeclarative,
|
|
Init: []*Index{uint32Ptr(1), nil, uint32Ptr(5)},
|
|
},
|
|
{
|
|
Mode: ElementModePassive,
|
|
Init: []*Index{uint32Ptr(5), uint32Ptr(2), nil, nil},
|
|
},
|
|
},
|
|
},
|
|
exp: map[uint32]struct{}{0: {}, 1: {}, 2: {}, 5: {}},
|
|
},
|
|
{
|
|
name: "all",
|
|
mod: &Module{
|
|
ExportSection: []*Export{
|
|
{Index: 10, Type: ExternTypeGlobal},
|
|
{Index: 1000, Type: ExternTypeFunc},
|
|
},
|
|
GlobalSection: []*Global{
|
|
{
|
|
Init: &ConstantExpression{
|
|
Opcode: OpcodeI32Const, // not funcref.
|
|
Data: leb128.EncodeInt32(-1),
|
|
},
|
|
},
|
|
{
|
|
Init: &ConstantExpression{
|
|
Opcode: OpcodeRefFunc,
|
|
Data: leb128.EncodeInt32(123),
|
|
},
|
|
},
|
|
},
|
|
ElementSection: []*ElementSegment{
|
|
{
|
|
Mode: ElementModeActive,
|
|
Init: []*Index{uint32Ptr(0), nil, uint32Ptr(5)},
|
|
},
|
|
{
|
|
Mode: ElementModeDeclarative,
|
|
Init: []*Index{uint32Ptr(1), nil, uint32Ptr(5)},
|
|
},
|
|
{
|
|
Mode: ElementModePassive,
|
|
Init: []*Index{uint32Ptr(5), uint32Ptr(2), nil, nil},
|
|
},
|
|
},
|
|
},
|
|
exp: map[uint32]struct{}{0: {}, 1: {}, 2: {}, 5: {}, 123: {}, 1000: {}},
|
|
},
|
|
{
|
|
mod: &Module{
|
|
GlobalSection: []*Global{
|
|
{
|
|
Init: &ConstantExpression{
|
|
Opcode: OpcodeRefFunc,
|
|
Data: nil,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
name: "invalid global",
|
|
expErr: `global[0] failed to initialize: EOF`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actual, err := tc.mod.declaredFunctionIndexes()
|
|
if tc.expErr != "" {
|
|
require.EqualError(t, err, tc.expErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.exp, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|