Removes testing/modgen fuzzing (#779)

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Takeshi Yoneda
2022-08-30 11:04:01 +09:00
committed by GitHub
parent 69863c430d
commit 2706004b80
2 changed files with 0 additions and 1202 deletions

View File

@@ -1,449 +0,0 @@
package modgen
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"math"
"math/rand"
"strconv"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
// Gen generates a pseudo random compilable module based on `seed`.
// The size of each section is controlled by the corresponding params.
// For example, `numImports` controls the number of segment in the import section.
//
// Note: "pseudo" here means the determinism of the generated results,
// e.g. giving same seed returns exactly the same module for
// the same code base in Gen.
//
// Note: this is only used for testing wazero runtime.
func Gen(seed []byte, enabledFeature wasm.Features,
numTypes, numFunctions, numImports, numExports, numGlobals, numElements, numData uint32,
needStartSection bool,
) *wasm.Module {
if len(seed) == 0 {
return &wasm.Module{}
}
checksum := sha256.Sum256(seed)
g := &generator{
// Use 4 randoms created from the unique sha256 hash value of the seed.
size: len(seed), rands: make([]random, 4), enabledFeature: enabledFeature,
numTypes: numTypes,
numFunctions: numFunctions,
numImports: numImports,
numExports: numExports,
numGlobals: numGlobals,
numElements: numElements,
numData: numData,
needStartSection: needStartSection,
}
for i := 0; i < 4; i++ {
g.rands[i] = rand.New(rand.NewSource(
int64(binary.LittleEndian.Uint64(checksum[i*8 : (i+1)*8]))))
}
return g.gen()
}
type generator struct {
// rands holds random sources for generating a module.
rands []random
nextRandIndex int
// size holds the original size of the seed.
size int
// m is the resulting module.
m *wasm.Module
enabledFeature wasm.Features
numTypes, numFunctions, numImports, numExports,
numGlobals, numElements, numData uint32
needStartSection bool
}
// random is the interface over methods of rand.Rand which are used by our generator.
// This is only for testing the generator implmenetation.
type random interface {
// See rand.Intn.
Intn(n int) int
// See rand.Read
Read(p []byte) (n int, err error)
}
func (g *generator) nextRandom() (ret random) {
ret = g.rands[g.nextRandIndex]
g.nextRandIndex = (g.nextRandIndex + 1) % len(g.rands)
return
}
// gen generates a random Wasm module.
func (g *generator) gen() *wasm.Module {
g.m = &wasm.Module{}
g.genTypeSection()
g.genImportSection()
g.genFunctionSection()
g.genTableSection()
g.genMemorySection()
g.genGlobalSection()
g.genExportSection()
g.genStartSection()
g.genElementSection()
g.genCodeSection()
g.genDataSection()
return g.m
}
// genTypeSection creates random types each with a random number of parameters and results.
func (g *generator) genTypeSection() {
for i := uint32(0); i < g.numTypes; i++ {
var resultNumCeil = g.size
if !g.enabledFeature.Get(wasm.FeatureMultiValue) {
resultNumCeil = 2
}
ft := g.newFunctionType(g.nextRandom().Intn(g.size), g.nextRandom().Intn(resultNumCeil))
g.m.TypeSection = append(g.m.TypeSection, ft)
}
}
func (g *generator) newFunctionType(params, results int) *wasm.FunctionType {
ret := &wasm.FunctionType{}
for i := 0; i < params; i++ {
ret.Params = append(ret.Params, g.newValueType())
}
for i := 0; i < results; i++ {
ret.Results = append(ret.Results, g.newValueType())
}
return ret
}
func (g *generator) newValueType() (ret wasm.ValueType) {
switch g.nextRandom().Intn(4) {
case 0:
ret = wasm.ValueTypeI32
case 1:
ret = wasm.ValueTypeI64
case 2:
ret = wasm.ValueTypeF32
case 3:
ret = wasm.ValueTypeF64
default:
panic("BUG")
}
return
}
// genImportSection creates random import descriptions, including memory and table.
func (g *generator) genImportSection() {
var memoryImported, tableImported int
for i := uint32(0); i < g.numImports; i++ {
imp := &wasm.Import{
Name: fmt.Sprintf("%d", i),
Module: fmt.Sprintf("module-%d", i),
}
g.m.ImportSection = append(g.m.ImportSection, imp)
r := g.nextRandom().Intn(4 - memoryImported - tableImported)
if r == 0 && len(g.m.TypeSection) > 0 {
imp.Type = wasm.ExternTypeFunc
imp.DescFunc = uint32(g.nextRandom().Intn(len(g.m.TypeSection)))
continue
}
if r == 0 || r == 1 {
imp.Type = wasm.ExternTypeGlobal
imp.DescGlobal = &wasm.GlobalType{
ValType: g.newValueType(),
Mutable: g.nextRandom().Intn(2) == 0,
}
continue
}
if memoryImported == 0 {
min := g.nextRandom().Intn(4) // Min in reality is relatively small like 4.
max := g.nextRandom().Intn(int(wasm.MemoryLimitPages)-min) + min
imp.Type = wasm.ExternTypeMemory
imp.DescMem = &wasm.Memory{
Min: uint32(min),
Max: uint32(max),
IsMaxEncoded: true,
}
memoryImported = 1
continue
}
if tableImported == 0 {
min := g.nextRandom().Intn(4) // Min in reality is relatively small like 4.
max := uint32(g.nextRandom().Intn(int(wasm.MemoryLimitPages)-min) + min)
imp.Type = wasm.ExternTypeTable
tableImported = 1
imp.DescTable = &wasm.Table{
Min: uint32(min),
Max: &max,
}
continue
}
panic("BUG")
}
}
// genFunctionSection generates random function declarations whose type is randomly chosen
// from already generated type section.
func (g *generator) genFunctionSection() {
numTypes := len(g.m.TypeSection)
if numTypes == 0 {
return
}
for i := uint32(0); i < g.numFunctions; i++ {
typeIndex := g.nextRandom().Intn(numTypes)
g.m.FunctionSection = append(g.m.FunctionSection, uint32(typeIndex))
}
}
// genTableSection generates random table definition if there's no import fot table.
func (g *generator) genTableSection() {
if g.m.ImportTableCount() != 0 {
return
}
min := g.nextRandom().Intn(4) // Min in reality is relatively small like 4.
max := uint32(g.nextRandom().Intn(int(wasm.MemoryLimitPages)-min) + min)
g.m.TableSection = []*wasm.Table{{Min: uint32(min), Max: &max}}
}
// genTableSection generates random memory definition if there's no import fot table.
func (g *generator) genMemorySection() {
if g.m.ImportMemoryCount() != 0 {
return
}
min := g.nextRandom().Intn(4) // Min in reality is relatively small like 4.
max := g.nextRandom().Intn(int(wasm.MemoryLimitPages)-min) + min
g.m.MemorySection = &wasm.Memory{Min: uint32(min), Max: uint32(max), IsMaxEncoded: true}
}
// genTableSection generates random globals.
func (g *generator) genGlobalSection() {
for i := uint32(0); i < g.numGlobals; i++ {
expr, t := g.newConstExpr()
mutable := g.nextRandom().Intn(2) == 0
global := &wasm.Global{
Type: &wasm.GlobalType{ValType: t, Mutable: mutable},
Init: expr,
}
g.m.GlobalSection = append(g.m.GlobalSection, global)
}
}
func (g *generator) newConstExpr() (*wasm.ConstantExpression, wasm.ValueType) {
importedGlobalCount := g.m.ImportGlobalCount()
importedGlobalsNotExist := 1
if importedGlobalCount > 0 {
importedGlobalsNotExist = 0
}
var opcode wasm.Opcode
var data []byte
var valueType wasm.ValueType
switch g.nextRandom().Intn(5 - importedGlobalsNotExist) {
case 0:
opcode = wasm.OpcodeI32Const
v := g.nextRandom().Intn(math.MaxInt32)
if g.nextRandom().Intn(2) == 0 {
v = -v
}
data = leb128.EncodeInt32(int32(v))
valueType = wasm.ValueTypeI32
case 1:
opcode = wasm.OpcodeI64Const
v := g.nextRandom().Intn(math.MaxInt64)
if g.nextRandom().Intn(2) == 0 {
v = -v
}
data = leb128.EncodeInt64(int64(v))
valueType = wasm.ValueTypeI64
case 2:
opcode = wasm.OpcodeF32Const
data = make([]byte, 4)
_, err := g.nextRandom().Read(data)
if err != nil {
panic(err)
}
valueType = wasm.ValueTypeF32
case 3:
opcode = wasm.OpcodeF64Const
data = make([]byte, 8)
_, err := g.nextRandom().Read(data)
if err != nil {
panic(err)
}
valueType = wasm.ValueTypeF64
case 4:
opcode = wasm.OpcodeGlobalGet
// Constexpr can only reference imported globals.
globalIndex := g.nextRandom().Intn(int(importedGlobalCount))
data = leb128.EncodeUint32(uint32(globalIndex))
// Find the value type of the imported global.
var cnt int
for _, imp := range g.m.ImportSection {
if imp.Type == wasm.ExternTypeGlobal {
if cnt == globalIndex {
valueType = imp.DescGlobal.ValType
break
} else {
cnt++
}
}
}
default:
panic("BUG")
}
return &wasm.ConstantExpression{Opcode: opcode, Data: data}, valueType
}
// genTableSection generates random export descriptions from previously generated functions, globals, table and memory declarations.
func (g *generator) genExportSection() {
funcs, globals, table, memory, err := g.m.AllDeclarations()
if err != nil {
panic("BUG:" + err.Error())
}
var possibleExports []wasm.Export
for i := range funcs {
possibleExports = append(possibleExports, wasm.Export{Type: wasm.ExternTypeFunc, Index: uint32(i)})
}
for i := range globals {
possibleExports = append(possibleExports, wasm.Export{Type: wasm.ExternTypeGlobal, Index: uint32(i)})
}
if table != nil {
possibleExports = append(possibleExports, wasm.Export{Type: wasm.ExternTypeTable, Index: 0})
}
if memory != nil {
possibleExports = append(possibleExports, wasm.Export{Type: wasm.ExternTypeMemory, Index: 0})
}
for i := uint32(0); i < g.numExports; i++ {
target := possibleExports[g.nextRandom().Intn(len(possibleExports))]
g.m.ExportSection = append(g.m.ExportSection, &wasm.Export{
Type: target.Type,
Index: target.Index,
Name: strconv.Itoa(int(i)),
})
}
}
// genStartSection generates start section whose function is randomly chosen from previously declared function.
func (g *generator) genStartSection() {
if !g.needStartSection {
return
}
funcs, _, _, _, err := g.m.AllDeclarations()
if err != nil {
panic("BUG:" + err.Error())
}
var candidates []wasm.Index
for funcIndex, typeIndex := range funcs {
sig := g.m.TypeSection[typeIndex]
// Start function must have the empty signature.
if sig.EqualsSignature(nil, nil) {
candidates = append(candidates, wasm.Index(funcIndex))
}
}
if len(candidates) > 0 {
g.m.StartSection = &candidates[g.nextRandom().Intn(len(candidates))]
return
}
}
// genStartSection generates random element section if table and functions exist.
func (g *generator) genElementSection() {
funcs, _, _, tables, err := g.m.AllDeclarations()
if err != nil {
panic("BUG:" + err.Error())
}
numFuncs := len(funcs)
if tables == nil || numFuncs == 0 {
return
}
min := tables[0].Min
for i := uint32(0); i < g.numElements; i++ {
// Elements can't exceed min of table.
indexes := make([]*wasm.Index, g.nextRandom().Intn(int(min)+1))
for i := range indexes {
v := uint32(g.nextRandom().Intn(numFuncs))
indexes[i] = &v
}
offset := g.nextRandom().Intn(int(min) - len(indexes) + 1)
elem := &wasm.ElementSegment{
OffsetExpr: &wasm.ConstantExpression{
// TODO: support global.get expression.
Opcode: wasm.OpcodeI32Const,
Data: leb128.EncodeInt32(int32(offset)),
},
Init: indexes,
Type: wasm.RefTypeFuncref,
}
g.m.ElementSection = append(g.m.ElementSection, elem)
}
}
// genCodeSection generates random code section for functions defined in this module.
func (g *generator) genCodeSection() {
codeSectionSize := len(g.m.FunctionSection)
for i := 0; i < codeSectionSize; i++ {
g.m.CodeSection = append(g.m.CodeSection, g.newCode())
}
}
func (g *generator) newCode() *wasm.Code {
// TODO: generate random body.
return &wasm.Code{Body: []byte{wasm.OpcodeUnreachable, // With unreachable allows us to make this body valid for any signature.
wasm.OpcodeEnd}}
}
// genDataSection generates random data section if memory is declared and its min is not zero.
func (g *generator) genDataSection() {
_, _, mem, _, err := g.m.AllDeclarations()
if err != nil {
panic("BUG:" + err.Error())
}
if mem == nil || mem.Min == 0 || g.numData == 0 {
return
}
min := int(mem.Min * wasm.MemoryPageSize)
for i := uint32(0); i < g.numData; i++ {
offset := g.nextRandom().Intn(min)
expr := &wasm.ConstantExpression{
Opcode: wasm.OpcodeI32Const,
Data: leb128.EncodeInt32(int32(offset)),
}
init := make([]byte, g.nextRandom().Intn(min-offset+1))
if len(init) == 0 {
continue
}
_, err := g.nextRandom().Read(init)
if err != nil {
panic(err)
}
g.m.DataSection = append(g.m.DataSection, &wasm.DataSegment{
OffsetExpression: expr,
Init: init,
})
}
}

View File

@@ -1,753 +0,0 @@
package modgen
import (
"context"
"encoding/hex"
"math/rand"
"strconv"
"testing"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasm/binary"
)
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
const (
i32 = wasm.ValueTypeI32
i64 = wasm.ValueTypeI64
f32 = wasm.ValueTypeF32
f64 = wasm.ValueTypeF64
)
// TestModGen is like a end-to-end test and verifies that our module generator only generates valid compilable modules.
func TestModGen(t *testing.T) {
tested := map[string]struct{}{}
rand := rand.New(rand.NewSource(0)) // use deterministic seed source for easy debugging.
runtime := wazero.NewRuntimeWithConfig(testCtx, wazero.NewRuntimeConfig().WithWasmCore2())
for _, size := range []int{1, 2, 5, 10, 50, 100} {
for i := 0; i < 100; i++ {
seed := make([]byte, size)
_, err := rand.Read(seed)
require.NoError(t, err)
encoded := hex.EncodeToString(seed)
if _, ok := tested[encoded]; ok {
continue
}
t.Run(encoded, func(t *testing.T) {
numTypes, numFunctions, numImports, numExports, numGlobals, numElements, numData :=
rand.Intn(size), rand.Intn(size), rand.Intn(size), rand.Intn(size), rand.Intn(size), rand.Intn(size), rand.Intn(size)
needStartSection := rand.Intn(2) == 0
// Generate a random WebAssembly module.
m := Gen(seed, wasm.FeatureMultiValue,
uint32(numTypes), uint32(numFunctions), uint32(numImports), uint32(numExports), uint32(numGlobals), uint32(numElements), uint32(numData), needStartSection)
// Encode the generated module (*wasm.Module) as binary.
bin := binary.EncodeModule(m)
// Pass the generated binary into our compilers.
code, err := runtime.CompileModule(testCtx, bin, wazero.NewCompileConfig())
require.NoError(t, err)
err = code.Close(testCtx)
require.NoError(t, err)
})
}
}
}
// testRand implements randm for testing.
type testRand struct {
ints []int
intPos int
bufs [][]byte
bufPos int
}
var _ random = &testRand{}
func (tr *testRand) Intn(n int) int {
ret := tr.ints[tr.intPos] % n
tr.intPos = (tr.intPos + 1) % len(tr.ints)
return ret
}
func (tr *testRand) Read(p []byte) (n int, err error) {
buf := tr.bufs[tr.bufPos]
copy(p, buf)
tr.bufPos = (tr.bufPos + 1) % len(tr.bufs)
return
}
func newGenerator(size int, ints []int, bufs [][]byte) *generator {
return &generator{size: size,
rands: []random{&testRand{ints: ints, bufs: bufs}},
m: &wasm.Module{},
}
}
func TestGenerator_newFunctionType(t *testing.T) {
tests := []struct {
params, results int
exp *wasm.FunctionType
}{
{params: 1, results: 1, exp: &wasm.FunctionType{
Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i64}}},
{params: 1, results: 2, exp: &wasm.FunctionType{
Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i64, f32}}},
{params: 2, results: 2, exp: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}}},
{params: 0, results: 2, exp: &wasm.FunctionType{
Results: []wasm.ValueType{i32, i64}}},
{params: 2, results: 0, exp: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i64}}},
{params: 6, results: 6, exp: &wasm.FunctionType{
Params: []wasm.ValueType{i32, i64, f32, f64, i32, i32},
Results: []wasm.ValueType{i64, f32, f64, i32, i32, i64},
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.exp.String(), func(t *testing.T) {
g := newGenerator(0, []int{0, 1, 2, 3, 4}, nil)
actual := g.newFunctionType(tc.params, tc.results)
require.Equal(t, tc.exp.String(), actual.String())
})
}
}
func TestGenerator_newValueType(t *testing.T) {
g := newGenerator(0, []int{0, 1, 2, 3, 4, 5}, nil)
require.Equal(t,
[]wasm.ValueType{i32, i64, f32, f64, i32},
[]wasm.ValueType{g.newValueType(), g.newValueType(), g.newValueType(), g.newValueType(), g.newValueType()},
)
}
func TestGenerator_typeSection(t *testing.T) {
g := newGenerator(100, []int{
// Params = 1, Reults = 1, i32, i32
1, 1, 0, 0,
// Params = 2, Results = 2, i32f32, i64f64
2, 2, 0, 2, 1, 3,
// Params = 0, Results = 4, f64f32i64i32
0, 4, 3, 2, 1, 0,
}, nil)
g.enabledFeature = wasm.FeatureMultiValue
g.numTypes = 3
g.genTypeSection()
expected := []*wasm.FunctionType{
{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, f32}, Results: []wasm.ValueType{i64, f64}},
{Params: []wasm.ValueType{}, Results: []wasm.ValueType{f64, f32, i64, i32}},
}
require.Equal(t, len(expected), len(g.m.TypeSection))
for i := range expected {
require.Equal(t, expected[i].String(), g.m.TypeSection[i].String())
}
}
func TestGenerator_importSection(t *testing.T) {
five := uint32(5)
tests := []struct {
ints []int
numImports uint32
types []*wasm.FunctionType
exp []*wasm.Import
}{
{
types: []*wasm.FunctionType{{}},
numImports: 2,
ints: []int{
// function type with type of index = 0 x 2
0, 0,
0, 0,
},
exp: []*wasm.Import{
{Type: wasm.ExternTypeFunc, DescFunc: 0, Module: "module-0", Name: "0"},
{Type: wasm.ExternTypeFunc, DescFunc: 0, Module: "module-1", Name: "1"},
},
},
{
types: []*wasm.FunctionType{{}, {}},
numImports: 2,
ints: []int{
// function type with type of index = 1
0, 1,
// function type with type of index = 0
0, 0,
},
exp: []*wasm.Import{
{Type: wasm.ExternTypeFunc, DescFunc: 1, Module: "module-0", Name: "0"},
{Type: wasm.ExternTypeFunc, DescFunc: 0, Module: "module-1", Name: "1"},
},
},
{
types: []*wasm.FunctionType{{}, {}},
numImports: 2,
ints: []int{
// function type with type of index = 1
0, 1,
// global type with type f32, and mutable
1, 2, 0,
},
exp: []*wasm.Import{
{Type: wasm.ExternTypeFunc, DescFunc: 1, Module: "module-0", Name: "0"},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{
Mutable: true, ValType: f32,
}, Module: "module-1", Name: "1"},
},
},
{
types: []*wasm.FunctionType{{}, {}},
numImports: 2,
ints: []int{
// function type with type of index = 1
0, 1,
// global type with type f64, and immutable
1, 3, 1,
},
exp: []*wasm.Import{
{Type: wasm.ExternTypeFunc, DescFunc: 1, Module: "module-0", Name: "0"},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{
Mutable: false, ValType: f64,
}, Module: "module-1", Name: "1"},
},
},
{
numImports: 2,
ints: []int{
// global type with type i32, and mutable
0 /* func types are not given so 0 should be global */, 0, 0,
// global type with type f64, and immutable
1, 3, 1,
},
exp: []*wasm.Import{
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{
Mutable: true, ValType: i32,
}, Module: "module-0", Name: "0"},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{
Mutable: false, ValType: f64,
}, Module: "module-1", Name: "1"},
},
},
{
types: []*wasm.FunctionType{{}, {}},
numImports: 3,
ints: []int{
// Memory import with min=1,max=3,
2, 1, 2, /* max is rand + min = 2 + 1 = 3 */
// Table import with min=1,max=5
2, 1, 4, /* max is rand + min = 4 + 1 = 5 */
// function type with type of index = 1
2, 1,
},
exp: []*wasm.Import{
{Type: wasm.ExternTypeMemory, DescMem: &wasm.Memory{
Min: 1, Max: 3, IsMaxEncoded: true,
}, Module: "module-0", Name: "0"},
{Type: wasm.ExternTypeTable, DescTable: &wasm.Table{
Min: 1, Max: &five,
}, Module: "module-1", Name: "1"},
{Type: wasm.ExternTypeFunc, DescFunc: 1, Module: "module-2", Name: "2"},
},
},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
g := newGenerator(100, tc.ints, nil)
g.numImports = uint32(tc.numImports)
g.m.TypeSection = tc.types
g.genImportSection()
require.Equal(t, len(tc.exp), len(g.m.ImportSection))
for i := range tc.exp {
require.Equal(t, tc.exp[i], g.m.ImportSection[i])
}
})
}
}
func TestGenerator_functionSection(t *testing.T) {
t.Run("with types", func(t *testing.T) {
types := make([]*wasm.FunctionType, 10)
g := newGenerator(100, []int{
100001, 105, 102, 100000003, 104, 6, 9, 8, 7, 10000,
}, nil)
g.numFunctions = 10
g.m.TypeSection = types
g.genFunctionSection()
require.Equal(t, []uint32{1, 5, 2, 3, 4, 6, 9, 8, 7, 0}, g.m.FunctionSection)
})
t.Run("without types", func(t *testing.T) {
g := newGenerator(100, []int{10}, nil)
g.genFunctionSection()
require.Equal(t, []uint32(nil), g.m.FunctionSection)
})
}
func TestGenerator_tableSection(t *testing.T) {
t.Run("with table import", func(t *testing.T) {
g := newGenerator(100, []int{10}, nil)
g.m.ImportSection = append(g.m.ImportSection, &wasm.Import{Type: wasm.ExternTypeTable})
g.genTableSection()
require.Nil(t, g.m.TableSection)
})
t.Run("without table import", func(t *testing.T) {
g := newGenerator(100, []int{1, 100}, nil)
g.genTableSection()
expMax := uint32(101)
require.Equal(t, []*wasm.Table{{Min: 1, Max: &expMax}}, g.m.TableSection)
})
}
func TestGenerator_memorySection(t *testing.T) {
t.Run("with memory import", func(t *testing.T) {
g := newGenerator(100, []int{10}, nil)
g.m.ImportSection = append(g.m.ImportSection, &wasm.Import{Type: wasm.ExternTypeMemory})
g.genMemorySection()
require.Nil(t, g.m.MemorySection)
})
t.Run("without memory import", func(t *testing.T) {
g := newGenerator(100, []int{1, 100}, nil)
g.genMemorySection()
require.Equal(t, &wasm.Memory{Min: 1, Max: 101, IsMaxEncoded: true}, g.m.MemorySection)
})
}
func TestGenerator_newConstExpr(t *testing.T) {
tests := []struct {
ints []int
bufs [][]byte
imports []*wasm.Import
exp *wasm.ConstantExpression
expType wasm.ValueType
}{
{
ints: []int{
0, // i32.const
100, // 100 literal
1, // non-negative
},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(100)},
expType: i32,
},
{
ints: []int{
0, // i2.const
100, // 100 literal
0, // negative
},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(-100)},
expType: i32,
},
{
ints: []int{
1, // i64.const
100, // 100 literal
1, // non-negative
},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(100)},
expType: i64,
},
{
ints: []int{
1, // i64.const
100, // 100 literal
0, // negative
},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(-100)},
expType: i64,
},
{
ints: []int{2}, // f32.const
bufs: [][]byte{{1, 2, 3, 4}},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: []byte{1, 2, 3, 4}},
expType: f32,
},
{
ints: []int{3}, // f64.const
bufs: [][]byte{{1, 2, 3, 4, 5, 6, 7, 8}},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8}},
expType: f64,
},
{
ints: []int{
4, // globa.get
1, // 2nd global.
},
imports: []*wasm.Import{
{},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{ValType: i32}},
{},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{ValType: f32}},
},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeGlobalGet, Data: []byte{1}},
expType: f32,
},
{
ints: []int{
4, // globa.get
0, // 1st global.
},
imports: []*wasm.Import{
{},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{ValType: i32}},
{},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{ValType: f32}},
},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeGlobalGet, Data: []byte{0}},
expType: i32,
},
{
ints: []int{
4, // there's no imports, so this should result in i32.const.
100, // 100 literal
1, // non-negative
},
exp: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(100)},
expType: i32,
},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
g := newGenerator(100, tc.ints, tc.bufs)
g.m.ImportSection = tc.imports
actualExpr, actualType := g.newConstExpr()
require.Equal(t, tc.expType, actualType)
require.Equal(t, tc.exp, actualExpr)
})
}
}
func TestGenerator_globalSection(t *testing.T) {
g := newGenerator(100,
[]int{
// i32.const 123, mutable
0 /* i32.const */, 123, 1 /* non negative */, 0, /* mutable */
3 /* f64.const */, 1, /* immutable */
4 /* global.get */, 0 /* global index */, 1, /* immutable */
},
[][]byte{{1, 2, 3, 4, 5, 6, 7, 8}},
)
g.m.ImportSection = []*wasm.Import{{}, {},
{Type: wasm.ExternTypeGlobal, DescGlobal: &wasm.GlobalType{ValType: f32}},
}
g.numGlobals = 3
g.genGlobalSection()
expected := []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: i32, Mutable: true},
Init: &wasm.ConstantExpression{
Opcode: wasm.OpcodeI32Const,
Data: leb128.EncodeInt32(123),
},
},
{
Type: &wasm.GlobalType{ValType: f64, Mutable: false},
Init: &wasm.ConstantExpression{
Opcode: wasm.OpcodeF64Const,
Data: []byte{1, 2, 3, 4, 5, 6, 7, 8},
},
},
{
Type: &wasm.GlobalType{ValType: f32, Mutable: false},
Init: &wasm.ConstantExpression{
Opcode: wasm.OpcodeGlobalGet,
Data: []byte{0},
},
},
}
actual := g.m.GlobalSection
require.Equal(t, len(expected), len(actual))
for i := range actual {
require.Equal(t, expected[i], actual[i])
}
}
func TestGenerator_exportSection(t *testing.T) {
m := &wasm.Module{
FunctionSection: make([]uint32, 10),
GlobalSection: []*wasm.Global{
{Type: &wasm.GlobalType{}},
{Type: &wasm.GlobalType{}},
{Type: &wasm.GlobalType{}},
{Type: &wasm.GlobalType{}},
{Type: &wasm.GlobalType{}},
},
TableSection: []*wasm.Table{{}},
MemorySection: &wasm.Memory{},
}
g := newGenerator(100, []int{
2, // funcs[2]
13, // globals[3]
15, // table
16, // memory
7, // funcs[7]
}, nil)
g.m = m
g.numExports = 5
g.genExportSection()
expected := []*wasm.Export{
{Type: wasm.ExternTypeFunc, Index: 2, Name: "0"},
{Type: wasm.ExternTypeGlobal, Index: 3, Name: "1"},
{Type: wasm.ExternTypeTable, Index: 0, Name: "2"},
{Type: wasm.ExternTypeMemory, Index: 0, Name: "3"},
{Type: wasm.ExternTypeFunc, Index: 7, Name: "4"},
}
actual := g.m.ExportSection
require.Equal(t, len(expected), len(actual))
for i := range actual {
require.Equal(t, expected[i], actual[i])
}
}
func TestGenerator_startSection(t *testing.T) {
t.Run("with cand", func(t *testing.T) {
m := &wasm.Module{
ImportSection: []*wasm.Import{
{Type: wasm.ExternTypeFunc, DescFunc: 0},
{Type: wasm.ExternTypeFunc, DescFunc: 1}, // candidate
{Type: wasm.ExternTypeFunc, DescFunc: 2},
{Type: wasm.ExternTypeFunc, DescFunc: 1}, // candidate
},
FunctionSection: []uint32{
0, 0, 1 /* candidate */, 0,
},
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32}},
{},
{Params: []wasm.ValueType{f32}},
{Params: []wasm.ValueType{f64}},
},
}
takePtr := func(v uint32) *uint32 { return &v }
tests := []struct {
ints []int
exp *wasm.Index
}{
{ints: []int{0}, exp: takePtr(1)},
{ints: []int{1}, exp: takePtr(3)},
{ints: []int{2}, exp: takePtr(6)},
{ints: []int{3}, exp: takePtr(1)},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
g := newGenerator(100, tc.ints, nil)
g.needStartSection = true
g.m = m
g.genStartSection()
require.Equal(t, tc.exp, g.m.StartSection)
})
}
})
t.Run("no cand", func(t *testing.T) {
m := &wasm.Module{
ImportSection: []*wasm.Import{
{Type: wasm.ExternTypeFunc, DescFunc: 0},
{Type: wasm.ExternTypeFunc, DescFunc: 1},
{Type: wasm.ExternTypeFunc, DescFunc: 2},
{Type: wasm.ExternTypeFunc, DescFunc: 1},
},
FunctionSection: []uint32{
0, 1, 2, 0, 1, 2,
},
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{f32}},
{Params: []wasm.ValueType{f64}},
},
}
g := newGenerator(100, []int{0}, nil)
g.m = m
g.genStartSection()
require.Nil(t, g.m.StartSection)
})
}
func TestGenerator_elementSection(t *testing.T) {
uint32Ptr := func(in uint32) *uint32 {
return &in
}
t.Run("without table", func(t *testing.T) {
g := newGenerator(100, nil, nil)
g.genElementSection()
require.Nil(t, g.m.ElementSection)
})
t.Run("without function", func(t *testing.T) {
g := newGenerator(100, nil, nil)
g.m.TableSection = []*wasm.Table{{}}
g.genElementSection()
require.Nil(t, g.m.ElementSection)
})
t.Run("ok", func(t *testing.T) {
tests := []struct {
ints []int
numElements uint32
exps []*wasm.ElementSegment
}{
{
numElements: 1,
ints: []int{
2 /* number of inedxes */, 0, 50, 98, /* offset */
},
exps: []*wasm.ElementSegment{
{
OffsetExpr: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(98)},
Init: []*wasm.Index{uint32Ptr(0), uint32Ptr(50)},
Type: wasm.RefTypeFuncref,
},
},
},
{
numElements: 3,
ints: []int{
2 /* number of inedxes */, 25, 75, 0, /* offset */
1 /* number of inedxes */, 3, 99, /* offset */
10 /* number of inedxes */, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /* offset */, 90,
},
exps: []*wasm.ElementSegment{
{
OffsetExpr: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(0)},
Type: wasm.RefTypeFuncref,
Init: []*wasm.Index{uint32Ptr(25), uint32Ptr(75)},
},
{
OffsetExpr: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(99)},
Type: wasm.RefTypeFuncref,
Init: []*wasm.Index{uint32Ptr(3)},
},
{
OffsetExpr: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(90)},
Type: wasm.RefTypeFuncref,
Init: []*wasm.Index{uint32Ptr(1), uint32Ptr(2), uint32Ptr(3), uint32Ptr(4),
uint32Ptr(5), uint32Ptr(6), uint32Ptr(7), uint32Ptr(8), uint32Ptr(9), uint32Ptr(10)},
},
},
},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
g := newGenerator(100, tc.ints, nil)
g.m = &wasm.Module{
TableSection: []*wasm.Table{{Min: 100}},
FunctionSection: make([]uint32, 100),
}
g.numElements = tc.numElements
g.genElementSection()
actual := g.m.ElementSection
require.Equal(t, len(tc.exps), len(actual))
for i := range actual {
require.Equal(t, tc.exps[i], actual[i])
}
})
}
})
}
func TestGenerator_dataSection(t *testing.T) {
t.Run("without memory", func(t *testing.T) {
g := newGenerator(100, nil, nil)
g.genDataSection()
require.Nil(t, g.m.DataSection)
})
t.Run("min=0", func(t *testing.T) {
g := newGenerator(100, nil, nil)
g.m.MemorySection = &wasm.Memory{Min: 0}
g.genDataSection()
require.Nil(t, g.m.DataSection)
})
t.Run("ok", func(t *testing.T) {
tests := []struct {
ints []int
bufs [][]byte
numData uint32
exps []*wasm.DataSegment
}{
{
numData: 1,
ints: []int{
5, // offset
2, // size of inits
},
bufs: [][]byte{{0x1, 0x2}},
exps: []*wasm.DataSegment{
{
OffsetExpression: &wasm.ConstantExpression{
Opcode: wasm.OpcodeI32Const,
Data: leb128.EncodeUint32(5),
},
Init: []byte{0x1, 0x2},
},
},
},
{
numData: 1,
ints: []int{
int(wasm.MemoryLimitPages) - 1, // offset
1, // size of inits
},
bufs: [][]byte{{0x1}},
exps: []*wasm.DataSegment{
{
OffsetExpression: &wasm.ConstantExpression{
Opcode: wasm.OpcodeI32Const,
Data: leb128.EncodeUint32(uint32(wasm.MemoryLimitPages) - 1),
},
Init: []byte{0x1},
},
},
},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
g := newGenerator(100, tc.ints, tc.bufs)
g.m.MemorySection = &wasm.Memory{Min: 1}
g.numData = tc.numData
g.genDataSection()
actual := g.m.DataSection
require.Equal(t, len(tc.exps), len(actual))
for i := range actual {
require.Equal(t, tc.exps[i], actual[i])
}
})
}
})
}