Files
wazero/internal/wasm/counts_test.go
Crypt Keeper c68d16e61c Adds import count functions to unlock shared text decoding (#299)
Right now, it is a lot of copy/paste to decode inlined import
abbreviation with the same parser as module-defined ones. The primary
reason for this is that the import section is only guaranteed before
module-defined *after* expanding abbreviations. For example,

`(func (import "foo" "bar") ...` follows imports and may be followed by
any number of abbreviations. This means you can't know the end of
imports until you see the first `(func` which doesn't abbreviate one.

While certain external types, namely memory and table, can only be
imported once, this also applies to globals.

This change makes counting the current imports a `func() uint32` so that
it can be decoupled and used for any external type. Other PRs can
simplify memory and table which can only be imported once, but the
general signatures here will still work.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-02-28 15:15:14 +09:00

299 lines
7.0 KiB
Go

package internalwasm
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestModule_ImportFuncCount(t *testing.T) {
tests := []struct {
name string
input *Module
expected uint32
}{
{
name: "none",
input: &Module{},
},
{
name: "none with function section",
input: &Module{FunctionSection: []Index{0}},
},
{
name: "one",
input: &Module{ImportSection: []*Import{{Type: ExternTypeFunc}}},
expected: 1,
},
{
name: "one with function section",
input: &Module{ImportSection: []*Import{{Type: ExternTypeFunc}}, FunctionSection: []Index{0}},
expected: 1,
},
{
name: "one with other imports",
input: &Module{ImportSection: []*Import{{Type: ExternTypeFunc}, {Type: ExternTypeMemory}}},
expected: 1,
},
{
name: "two",
input: &Module{ImportSection: []*Import{{Type: ExternTypeFunc}, {Type: ExternTypeFunc}}},
expected: 2,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expected, tc.input.ImportFuncCount())
})
}
}
// TODO: once we fix up-front validation, this only needs to check zero or one
func TestModule_ImportTableCount(t *testing.T) {
tests := []struct {
name string
input *Module
expected uint32
}{
{
name: "none",
input: &Module{},
},
{
name: "none with table section",
input: &Module{TableSection: []*TableType{{0x70, &LimitsType{1, nil}}}},
},
{
name: "one",
input: &Module{ImportSection: []*Import{{Type: ExternTypeTable}}},
expected: 1,
},
{
name: "one with table section",
input: &Module{
ImportSection: []*Import{{Type: ExternTypeTable}},
TableSection: []*TableType{{0x70, &LimitsType{1, nil}}},
},
expected: 1,
},
{
name: "one with other imports",
input: &Module{ImportSection: []*Import{{Type: ExternTypeTable}, {Type: ExternTypeMemory}}},
expected: 1,
},
{
name: "two",
input: &Module{ImportSection: []*Import{{Type: ExternTypeTable}, {Type: ExternTypeTable}}},
expected: 2,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expected, tc.input.ImportTableCount())
})
}
}
// TODO: once we fix up-front validation, this only needs to check zero or one
func TestModule_ImportMemoryCount(t *testing.T) {
tests := []struct {
name string
input *Module
expected uint32
}{
{
name: "none",
input: &Module{},
},
{
name: "none with memory section",
input: &Module{MemorySection: []*MemoryType{{Min: 1}}},
},
{
name: "one",
input: &Module{ImportSection: []*Import{{Type: ExternTypeMemory}}},
expected: 1,
},
{
name: "one with memory section",
input: &Module{
ImportSection: []*Import{{Type: ExternTypeMemory}},
MemorySection: []*MemoryType{{Min: 1}},
},
expected: 1,
},
{
name: "one with other imports",
input: &Module{ImportSection: []*Import{{Type: ExternTypeMemory}, {Type: ExternTypeTable}}},
expected: 1,
},
{
name: "two",
input: &Module{ImportSection: []*Import{{Type: ExternTypeMemory}, {Type: ExternTypeMemory}}},
expected: 2,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expected, tc.input.ImportMemoryCount())
})
}
}
func TestModule_ImportGlobalCount(t *testing.T) {
tests := []struct {
name string
input *Module
expected uint32
}{
{
name: "none",
input: &Module{},
},
{
name: "none with global section",
input: &Module{GlobalSection: []*Global{{Type: &GlobalType{ValType: ValueTypeI64}}}},
},
{
name: "one",
input: &Module{ImportSection: []*Import{{Type: ExternTypeGlobal}}},
expected: 1,
},
{
name: "one with global section",
input: &Module{
ImportSection: []*Import{{Type: ExternTypeGlobal}},
GlobalSection: []*Global{{Type: &GlobalType{ValType: ValueTypeI64}}},
},
expected: 1,
},
{
name: "one with other imports",
input: &Module{ImportSection: []*Import{{Type: ExternTypeGlobal}, {Type: ExternTypeMemory}}},
expected: 1,
},
{
name: "two",
input: &Module{ImportSection: []*Import{{Type: ExternTypeGlobal}, {Type: ExternTypeGlobal}}},
expected: 2,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expected, tc.input.ImportGlobalCount())
})
}
}
func TestModule_SectionElementCount(t *testing.T) {
i32, f32 := ValueTypeI32, ValueTypeF32
zero := uint32(0)
empty := &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x00}}
tests := []struct {
name string
input *Module
expected map[string]uint32
}{
{
name: "empty",
input: &Module{},
expected: map[string]uint32{},
},
{
name: "only name section",
input: &Module{NameSection: &NameSection{ModuleName: "simple"}},
expected: map[string]uint32{"custom": 1},
},
{
name: "type section",
input: &Module{
TypeSection: []*FunctionType{
{},
{Params: []ValueType{i32, i32}, Results: []ValueType{i32}},
{Params: []ValueType{i32, i32, i32, i32}, Results: []ValueType{i32}},
},
},
expected: map[string]uint32{"type": 3},
},
{
name: "type and import section",
input: &Module{
TypeSection: []*FunctionType{
{Params: []ValueType{i32, i32}, Results: []ValueType{i32}},
{Params: []ValueType{f32, f32}, Results: []ValueType{f32}},
},
ImportSection: []*Import{
{
Module: "Math", Name: "Mul",
Type: ExternTypeFunc,
DescFunc: 1,
}, {
Module: "Math", Name: "Add",
Type: ExternTypeFunc,
DescFunc: 0,
},
},
},
expected: map[string]uint32{"import": 2, "type": 2},
},
{
name: "type function and start section",
input: &Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: []*Code{
{Body: []byte{OpcodeLocalGet, 0, OpcodeLocalGet, 1, OpcodeI32Add, OpcodeEnd}},
},
ExportSection: map[string]*Export{
"AddInt": {Name: "AddInt", Type: ExternTypeFunc, Index: Index(0)},
},
StartSection: &zero,
},
expected: map[string]uint32{"code": 1, "export": 1, "function": 1, "start": 1, "type": 1},
},
{
name: "memory and data",
input: &Module{
MemorySection: []*MemoryType{{Min: 1}},
DataSection: []*DataSegment{{MemoryIndex: 0, OffsetExpression: empty}},
},
expected: map[string]uint32{"data": 1, "memory": 1},
},
{
name: "table and element",
input: &Module{
TableSection: []*TableType{{ElemType: 0x70, Limit: &LimitsType{Min: 1}}},
ElementSection: []*ElementSegment{{TableIndex: 0, OffsetExpr: empty}},
},
expected: map[string]uint32{"element": 1, "table": 1},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
actual := map[string]uint32{}
for i := SectionID(0); i <= SectionIDData; i++ {
if size := tc.input.SectionElementCount(i); size > 0 {
actual[SectionIDName(i)] = size
}
}
require.Equal(t, tc.expected, actual)
})
}
}