Files
wazero/internal/wasm/func_validation_test.go
Crypt Keeper b01effc8a9 Top-levels CoreFeatures and defaults to 2.0 (#800)
While compilers should be conservative when targeting WebAssembly Core
features, runtimes should be lenient as otherwise people need to
constantly turn on all features. Currently, most examples have to turn
on 2.0 features because compilers such as AssemblyScript and TinyGo use
them by default. This matches the policy with the reality, and should
make first time use easier.

This top-levels an internal type as `api.CoreFeatures` and defaults to
2.0 as opposed to 1.0, our previous default. This is less cluttered than
the excess of `WithXXX` methods we had prior to implementing all
planned WebAssembly Core Specification 1.0 features.

Finally, this backfills rationale as flat config types were a distinct
decision even if feature set selection muddied the topic.

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

3543 lines
126 KiB
Go

package wasm
import (
"bytes"
"fmt"
"testing"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestModule_ValidateFunction_validateFunctionWithMaxStackValues(t *testing.T) {
const max = 100
const valuesNum = max + 1
// Compile a function which has max+1 const instructions.
var body []byte
for i := 0; i < valuesNum; i++ {
body = append(body, OpcodeI32Const, 1)
}
// Drop all the consts so that if the max is higher, this function body would be sound.
for i := 0; i < valuesNum; i++ {
body = append(body, OpcodeDrop)
}
// Plus all functions must end with End opcode.
body = append(body, OpcodeEnd)
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: body}},
}
t.Run("not exceed", func(t *testing.T) {
err := m.validateFunctionWithMaxStackValues(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max+1, nil)
require.NoError(t, err)
})
t.Run("exceed", func(t *testing.T) {
err := m.validateFunctionWithMaxStackValues(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max, nil)
require.Error(t, err)
expMsg := fmt.Sprintf("function may have %d stack values, which exceeds limit %d", valuesNum, max)
require.Equal(t, expMsg, err.Error())
})
}
func TestModule_ValidateFunction_SignExtensionOps(t *testing.T) {
tests := []struct {
input Opcode
expectedErrOnDisable string
}{
{
input: OpcodeI32Extend8S,
expectedErrOnDisable: "i32.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI32Extend16S,
expectedErrOnDisable: "i32.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI64Extend8S,
expectedErrOnDisable: "i64.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI64Extend16S,
expectedErrOnDisable: "i64.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
},
{
input: OpcodeI64Extend32S,
expectedErrOnDisable: "i64.extend32_s invalid as feature \"sign-extension-ops\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(InstructionName(tc.input), func(t *testing.T) {
t.Run("disabled", func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{tc.input}}},
}
err := m.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
require.EqualError(t, err, tc.expectedErrOnDisable)
})
t.Run("enabled", func(t *testing.T) {
is32bit := tc.input == OpcodeI32Extend8S || tc.input == OpcodeI32Extend16S
var body []byte
if is32bit {
body = append(body, OpcodeI32Const)
} else {
body = append(body, OpcodeI64Const)
}
body = append(body, tc.input, 123, OpcodeDrop, OpcodeEnd)
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: body}},
}
err := m.validateFunction(api.CoreFeatureSignExtensionOps, 0, []Index{0}, nil, nil, nil, nil)
require.NoError(t, err)
})
})
}
}
func TestModule_ValidateFunction_NonTrappingFloatToIntConversion(t *testing.T) {
tests := []struct {
input Opcode
expectedErrOnDisable string
}{
{
input: OpcodeMiscI32TruncSatF32S,
expectedErrOnDisable: "i32.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI32TruncSatF32U,
expectedErrOnDisable: "i32.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI32TruncSatF64S,
expectedErrOnDisable: "i32.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI32TruncSatF64U,
expectedErrOnDisable: "i32.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF32S,
expectedErrOnDisable: "i64.trunc_sat_f32_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF32U,
expectedErrOnDisable: "i64.trunc_sat_f32_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF64S,
expectedErrOnDisable: "i64.trunc_sat_f64_s invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
{
input: OpcodeMiscI64TruncSatF64U,
expectedErrOnDisable: "i64.trunc_sat_f64_u invalid as feature \"nontrapping-float-to-int-conversion\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(InstructionName(tc.input), func(t *testing.T) {
t.Run("disabled", func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeMiscPrefix, tc.input}}},
}
err := m.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
require.EqualError(t, err, tc.expectedErrOnDisable)
})
t.Run("enabled", func(t *testing.T) {
var body []byte
switch tc.input {
case OpcodeMiscI32TruncSatF32S, OpcodeMiscI32TruncSatF32U, OpcodeMiscI64TruncSatF32S, OpcodeMiscI64TruncSatF32U:
body = []byte{OpcodeF32Const, 1, 2, 3, 4}
case OpcodeMiscI32TruncSatF64S, OpcodeMiscI32TruncSatF64U, OpcodeMiscI64TruncSatF64S, OpcodeMiscI64TruncSatF64U:
body = []byte{OpcodeF64Const, 1, 2, 3, 4, 5, 6, 7, 8}
}
body = append(body, OpcodeMiscPrefix, tc.input, OpcodeDrop, OpcodeEnd)
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: body}},
}
err := m.validateFunction(api.CoreFeatureNonTrappingFloatToIntConversion, 0, []Index{0}, nil, nil, nil, nil)
require.NoError(t, err)
})
})
}
}
// TestModule_ValidateFunction_MultiValue only tests what can't yet be detected during compilation. These examples are
// from test/core/if.wast from the commit that added "multi-value" support.
//
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
func TestModule_ValidateFunction_MultiValue(t *testing.T) {
tests := []struct {
name string
module *Module
expectedErrOnDisable string
}{
{
name: "block with function type",
module: &Module{
TypeSection: []*FunctionType{v_f64f64},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0, // (block (result f64 f64)
OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x10, 0x40, // (f64.const 4)
OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x14, 0x40, // (f64.const 5)
OpcodeBr, 0,
OpcodeF64Add,
OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x18, 0x40, // (f64.const 6)
OpcodeEnd,
OpcodeEnd,
}}},
},
expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
},
{
name: "if with function type", // a.k.a. "param"
module: &Module{
TypeSection: []*FunctionType{i32_i32}, // (func (param i32) (result i32)
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeLocalGet, 0, OpcodeIf, 0, // (if (param i32) (result i32) (local.get 0)
OpcodeI32Const, 2, OpcodeI32Add, // (then (i32.const 2) (i32.add))
OpcodeElse, OpcodeI32Const, 0x7e, OpcodeI32Add, // (else (i32.const -2) (i32.add))
OpcodeEnd, // )
OpcodeEnd, // )
}}},
},
expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
},
{
name: "if with function type - br", // a.k.a. "params-break"
module: &Module{
TypeSection: []*FunctionType{
i32_i32, // (func (param i32) (result i32)
i32i32_i32, // (if (param i32 i32) (result i32)
},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 2, // (i32.const 2)
OpcodeLocalGet, 0, OpcodeIf, 1, // (if (param i32) (result i32) (local.get 0)
OpcodeI32Add, OpcodeBr, 0, // (then (i32.add) (br 0))
OpcodeElse, OpcodeI32Sub, OpcodeBr, 0, // (else (i32.sub) (br 0))
OpcodeEnd, // )
OpcodeEnd, // )
}}},
},
expectedErrOnDisable: "read block: block with function type return invalid as feature \"multi-value\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
t.Run("disabled", func(t *testing.T) {
err := tc.module.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
require.EqualError(t, err, tc.expectedErrOnDisable)
})
t.Run("enabled", func(t *testing.T) {
err := tc.module.validateFunction(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
require.NoError(t, err)
})
})
}
}
func TestModule_ValidateFunction_BulkMemoryOperations(t *testing.T) {
t.Run("ok", func(t *testing.T) {
for _, op := range []OpcodeMisc{
OpcodeMiscMemoryInit, OpcodeMiscDataDrop, OpcodeMiscMemoryCopy,
OpcodeMiscMemoryFill, OpcodeMiscTableInit, OpcodeMiscElemDrop, OpcodeMiscTableCopy,
} {
t.Run(MiscInstructionName(op), func(t *testing.T) {
var body []byte
if op != OpcodeMiscDataDrop && op != OpcodeMiscElemDrop {
body = append(body, OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeI32Const, 3)
}
body = append(body, OpcodeMiscPrefix, op)
if op != OpcodeMiscDataDrop && op != OpcodeMiscMemoryFill && op != OpcodeMiscElemDrop {
body = append(body, 0, 0)
} else {
body = append(body, 0)
}
body = append(body, OpcodeEnd)
c := uint32(0)
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: body}},
DataSection: []*DataSegment{{}},
ElementSection: []*ElementSegment{{}},
DataCountSection: &c,
}
err := m.validateFunction(api.CoreFeatureBulkMemoryOperations, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}}, nil)
require.NoError(t, err)
})
}
})
t.Run("errors", func(t *testing.T) {
tests := []struct {
body []byte
dataSection []*DataSegment
elementSection []*ElementSegment
dataCountSectionNil bool
memory *Memory
tables []*Table
flag api.CoreFeatures
expectedErr string
}{
// memory.init
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.init",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeaturesV1,
expectedErr: `memory.init invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeatureBulkMemoryOperations,
dataCountSectionNil: true,
expectedErr: `memory must exist for memory.init`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read data segment index for memory.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 100 /* data section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "index 100 out of range of data section(len=1)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "failed to read memory index for memory.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 1},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "memory.init reserved byte must be zero encoded with 1 byte",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
// data.drop
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
flag: api.CoreFeaturesV1,
expectedErr: `data.drop invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
dataCountSectionNil: true,
memory: &Memory{},
flag: api.CoreFeatureBulkMemoryOperations,
expectedErr: `data.drop requires data count section`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read data segment index for data.drop: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop, 100 /* data section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "index 100 out of range of data section(len=1)",
},
// memory.copy
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: api.CoreFeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.copy",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: api.CoreFeaturesV1,
expectedErr: `memory.copy invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `failed to read memory index for memory.copy: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read memory index for memory.copy: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 1},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "memory.copy reserved byte must be zero encoded with 1 byte",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
// memory.fill
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: api.CoreFeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.fill",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: api.CoreFeaturesV1,
expectedErr: `memory.fill invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `failed to read memory index for memory.fill: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 1},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `memory.fill reserved byte must be zero encoded with 1 byte`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: api.CoreFeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
// table.init
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
flag: api.CoreFeaturesV1,
tables: []*Table{{}},
expectedErr: `table.init invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "failed to read element segment index for table.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 100 /* data section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "index 100 out of range of element section(len=1)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "failed to read source table index for table.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 10},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "source table index must be zero for table.init as feature \"reference-types\" is disabled",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 10},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "table of index 10 not found",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 1},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []*Table{{}, {Type: RefTypeExternref}},
elementSection: []*ElementSegment{{Type: RefTypeFuncref}},
expectedErr: "type mismatch for table.init: element type funcref does not match table type externref",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
// elem.drop
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
flag: api.CoreFeaturesV1,
tables: []*Table{{}},
expectedErr: `elem.drop invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "failed to read element segment index for elem.drop: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop, 100 /* element section out of range */},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "index 100 out of range of element section(len=1)",
},
// table.copy
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
flag: api.CoreFeaturesV1,
tables: []*Table{{}},
expectedErr: `table.copy invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: `failed to read destination table index for table.copy: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 10},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "destination table index must be zero for table.copy as feature \"reference-types\" is disabled",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []*Table{{}, {}},
expectedErr: "table of index 3 not found",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []*Table{{}, {}, {}, {}},
expectedErr: "failed to read source table index for table.copy: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 3},
flag: api.CoreFeatureBulkMemoryOperations, // Multiple tables require api.CoreFeatureReferenceTypes.
tables: []*Table{{}, {}, {}, {}},
expectedErr: "source table index must be zero for table.copy as feature \"reference-types\" is disabled",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3, 1},
flag: api.CoreFeatureBulkMemoryOperations | api.CoreFeatureReferenceTypes,
tables: []*Table{{}, {Type: RefTypeFuncref}, {}, {Type: RefTypeExternref}},
expectedErr: "table type mismatch for table.copy: funcref (src) != externref (dst)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: api.CoreFeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.expectedErr, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
ElementSection: tc.elementSection,
DataSection: tc.dataSection,
}
if !tc.dataCountSectionNil {
c := uint32(0)
m.DataCountSection = &c
}
err := m.validateFunction(tc.flag, 0, []Index{0}, nil, tc.memory, tc.tables, nil)
require.EqualError(t, err, tc.expectedErr)
})
}
})
}
var (
f32, f64, i32, i64, externref = ValueTypeF32, ValueTypeF64, ValueTypeI32, ValueTypeI64, ValueTypeExternref
f32i32_v = &FunctionType{Params: []ValueType{f32, i32}}
i32_i32 = &FunctionType{Params: []ValueType{i32}, Results: []ValueType{i32}}
i32f64_v = &FunctionType{Params: []ValueType{i32, f64}}
i32i32_i32 = &FunctionType{Params: []ValueType{i32, i32}, Results: []ValueType{i32}}
i32_v = &FunctionType{Params: []ValueType{i32}}
v_v = &FunctionType{}
v_f32 = &FunctionType{Results: []ValueType{f32}}
v_f32f32 = &FunctionType{Results: []ValueType{f32, f32}}
v_f64i32 = &FunctionType{Results: []ValueType{f64, i32}}
v_f64f64 = &FunctionType{Results: []ValueType{f64, f64}}
v_i32 = &FunctionType{Results: []ValueType{i32}}
v_i32i32 = &FunctionType{Results: []ValueType{i32, i32}}
v_i32i64 = &FunctionType{Results: []ValueType{i32, i64}}
v_i64i64 = &FunctionType{Results: []ValueType{i64, i64}}
)
// TestModule_ValidateFunction_TypeMismatchSpecTests are "type mismatch" tests when "multi-value" was merged.
//
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
func TestModule_ValidateFunction_MultiValue_TypeMismatch(t *testing.T) {
tests := []struct {
name string
module *Module
expectedErr string
enabledFeatures api.CoreFeatures
}{
// test/core/func.wast
{
name: `func.wast - type-empty-f64-i32`,
module: &Module{
TypeSection: []*FunctionType{v_f64i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
},
expectedErr: `not enough results
have ()
want (f64, i32)`,
},
{
name: `func.wast - type-value-void-vs-nums`,
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeNop, OpcodeEnd}}},
},
expectedErr: `not enough results
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-value-nums-vs-void`,
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeI32Const, 0, OpcodeI64Const, 0, OpcodeEnd}}},
},
expectedErr: `too many results
have (i32, i64)
want ()`,
},
{
name: `func.wast - type-value-num-vs-nums - v_f32f32 -> f32`,
module: &Module{
TypeSection: []*FunctionType{v_f32f32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (f32)
want (f32, f32)`,
},
{
name: `func.wast - type-value-num-vs-nums - v_f32 -> f32f32`,
module: &Module{
TypeSection: []*FunctionType{v_f32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0) (f32.const 0)
OpcodeEnd, // func
}}},
},
expectedErr: `too many results
have (f32, f32)
want (f32)`,
},
{
name: `func.wast - type-return-last-empty-vs-nums`,
module: &Module{
TypeSection: []*FunctionType{v_f32f32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeReturn, OpcodeEnd}}},
},
expectedErr: `not enough results
have ()
want (f32, f32)`,
},
{
name: `func.wast - type-return-last-void-vs-nums`,
module: &Module{
TypeSection: []*FunctionType{v_i32i64},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeNop, OpcodeReturn, OpcodeEnd}}}, // (return (nop))
},
expectedErr: `not enough results
have ()
want (i32, i64)`,
},
{
name: `func.wast - type-return-last-num-vs-nums`,
module: &Module{
TypeSection: []*FunctionType{v_i64i64},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI64Const, 0, OpcodeReturn, // (return (i64.const 0))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i64)
want (i64, i64)`,
},
{
name: `func.wast - type-return-empty-vs-nums`,
// This should err because (return) precedes the values expected in the signature (i32i32):
// (module (func $type-return-empty-vs-nums (result i32 i32)
// (return) (i32.const 1) (i32.const 2)
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeReturn, OpcodeI32Const, 1, OpcodeI32Const, 2,
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-return-partial-vs-nums`,
// This should err because (return) precedes one of the values expected in the signature (i32i32):
// (module (func $type-return-partial-vs-nums (result i32 i32)
// (i32.const 1) (return) (i32.const 2)
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeReturn, OpcodeI32Const, 2,
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i32)
want (i32, i32)`,
},
{
name: `func.wast - type-return-void-vs-nums`,
// This should err because (return) is empty due to nop, but the signature requires i32i32:
// (module (func $type-return-void-vs-nums (result i32 i32)
// (return (nop)) (i32.const 1)
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeNop, OpcodeReturn, // (return (nop))
OpcodeI32Const, 1, // (i32.const 1)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-return-num-vs-nums`,
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI64Const, 1, OpcodeReturn, // (return (i64.const 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i64)
want (i32, i32)`,
},
{
name: `func.wast - type-return-first-num-vs-nums`,
// This should err because the return block doesn't return enough values.
// (module (func $type-return-first-num-vs-nums (result i32 i32)
// (return (i32.const 1)) (return (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI64Const, 1, OpcodeReturn, // (return (i64.const 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeReturn, // (return (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results
have (i64)
want (i32, i32)`,
},
{
name: `func.wast - type-break-last-num-vs-nums`,
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 0, OpcodeBr, 0, // (br 0 (i32.const 0))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i32)
want (i32, i32)`,
},
{
name: `func.wast - type-break-void-vs-nums`,
// This should err because (br 0) returns no values, but its enclosing function requires two:
// (module (func $type-break-void-vs-nums (result i32 i32)
// (br 0) (i32.const 1) (i32.const 2)
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBr, 0, // (br 0)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-break-num-vs-nums`,
// This should err because (br 0) returns one value, but its enclosing function requires two:
// (module (func $type-break-num-vs-nums (result i32 i32)
// (br 0 (i32.const 1)) (i32.const 1) (i32.const 2)
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeBr, 0, // (br 0 (i32.const 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2)
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i32)
want (i32, i32)`,
},
{
name: `func.wast - type-break-nested-empty-vs-nums`,
// This should err because (br 1) doesn't return values, but its enclosing function does:
// (module (func $type-break-nested-empty-vs-nums (result i32 i32)
// (block (br 1)) (br 0 (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, OpcodeBr, 0x01, OpcodeEnd, // (block (br 1))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-break-nested-void-vs-nums`,
// This should err because nop returns the empty type, but the enclosing function returns i32i32:
// (module (func $type-break-nested-void-vs-nums (result i32 i32)
// (block (br 1 (nop))) (br 0 (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, OpcodeNop, OpcodeBr, 0x01, OpcodeEnd, // (block (br 1 (nop)))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `func.wast - type-break-nested-num-vs-nums`,
// This should err because the block signature is v_i32, but the enclosing function is v_i32i32:
// (module (func $type-break-nested-num-vs-nums (result i32 i32)
// (block (result i32) (br 1 (i32.const 1))) (br 0 (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x7f, OpcodeI32Const, 1, OpcodeBr, 1, OpcodeEnd, // (block (result i32) (br 1 (i32.const 1)))
OpcodeI32Const, 1, OpcodeI32Const, 2, OpcodeBr, 0, // (br 0 (i32.const 1) (i32.const 2))
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i32)
want (i32, i32)`,
},
// test/core/if.wast
{
name: `if.wast - wrong signature for if type use`,
// This should err because (br 0) returns no values, but its enclosing function requires two:
// (module
// (type $sig (func))
// (func (i32.const 1) (if (type $sig) (i32.const 0) (then)))
// )
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 0, OpcodeIf, 0, // (if (type $sig) (i32.const 0)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results
have (i32)
want ()`,
},
{
name: `if.wast - type-then-value-nums-vs-void`,
// This should err because (if) without a type use returns no values, but its (then) returns two:
// (module (func $type-then-value-nums-vs-void
// (if (i32.const 1) (then (i32.const 1) (i32.const 2)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-then-value-nums-vs-void-else`,
// This should err because (if) without a type use returns no values, but its (then) returns two:
// (module (func $type-then-value-nums-vs-void-else
// (if (i32.const 1) (then (i32.const 1) (i32.const 2)) (else))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
OpcodeElse, // (else)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-else-value-nums-vs-void`,
// This should err because (if) without a type use returns no values, but its (else) returns two:
// (module (func $type-else-value-nums-vs-void
// (if (i32.const 1) (then) (else (i32.const 1) (i32.const 2)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1) (then)
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 2, // (else (i32.const 1) (i32.const 2))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in else block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-both-value-nums-vs-void`,
// This should err because (if) without a type use returns no values, each branch returns two:
// (module (func $type-both-value-nums-vs-void
// (if (i32.const 1) (then (i32.const 1) (i32.const 2)) (else (i32.const 2) (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x40, // (if (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 2, // (then (i32.const 1) (i32.const 2))
OpcodeElse, OpcodeI32Const, 2, OpcodeI32Const, 1, // (else (i32.const 2) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want ()`,
},
{
name: `if.wast - type-then-value-empty-vs-nums`,
// This should err because the if branch is empty, but its type use requires two i32s:
// (module (func $type-then-value-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then) (else (i32.const 0) (i32.const 2)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1) (then)
OpcodeElse, OpcodeI32Const, 0, OpcodeI32Const, 2, // (else (i32.const 0) (i32.const 2)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-value-empty-vs-nums`,
// This should err because the else branch is empty, but its type use requires two i32s:
// (module (func $type-else-value-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 0) (i32.const 1)) (else))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 0, OpcodeI32Const, 2, // (then (i32.const 0) (i32.const 1))
OpcodeElse, // (else)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-empty-vs-nums`,
// This should err because the both branches are empty, but the if type use requires two i32s:
// (module (func $type-both-value-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then) (else))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1) (then)
OpcodeElse, // (else)
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-no-else-vs-nums`,
// This should err because the else branch is missing, but its type use requires two i32s:
// (module (func $type-no-else-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-void-vs-nums`,
// This should err because the then branch evaluates to empty, but its type use requires two i32s:
// (module (func $type-then-value-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (nop)) (else (i32.const 0) (i32.const 0)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (result i32 i32) (i32.const 1)
OpcodeNop, // (then (nop))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-void-vs-nums`,
// This should err because the else branch evaluates to empty, but its type use requires two i32s:
// (module (func $type-else-value-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 0) (i32.const 0)) (else (nop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 0, OpcodeI32Const, 0, // (then (i32.const 0) (i32.const 0))
OpcodeElse, OpcodeNop, // (else (nop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-void-vs-nums`,
// This should err because the if branch evaluates to empty, but its type use requires two i32s:
// (module (func $type-both-value-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (nop)) (else (nop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeNop, // (then (nop))
OpcodeElse, OpcodeNop, // (else (nop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-num-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-then-value-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-else-value-num-vs-nums`,
// This should err because the else branch returns one value, but its type use requires two:
// (module (func $type-else-value-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-num-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-both-value-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-partial-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-then-value-partial-vs-nums (result i32 i32)
// (i32.const 0)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-else-value-partial-vs-nums`,
// This should err because the else branch returns one value, but its type use requires two:
// (module (func $type-else-value-partial-vs-nums (result i32 i32)
// (i32.const 0)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in else block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-both-value-partial-vs-nums`,
// This should err because the if branch returns one value, but its type use requires two:
// (module (func $type-both-value-partial-vs-nums (result i32 i32)
// (i32.const 0)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 0, // (i32.const 0) - NOTE: this is outside the (if)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-then-value-nums-vs-num`,
// This should err because the if branch returns two values, but its type use requires one:
// (module (func $type-then-value-nums-vs-num (result i32)
// (if (result i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want (i32)`,
},
{
name: `if.wast - type-else-value-nums-vs-num`,
// This should err because the else branch returns two values, but its type use requires one:
// (module (func $type-else-value-nums-vs-num (result i32)
// (if (result i32) (i32.const 1) (then (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in else block
have (i32, i32)
want (i32)`,
},
{
name: `if.wast - type-both-value-nums-vs-num`,
// This should err because the if branch returns two values, but its type use requires one:
// (module (func $type-both-value-nums-vs-num (result i32)
// (if (result i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32)
want (i32)`,
},
{
name: `if.wast - type-both-different-value-nums-vs-nums`,
// This should err because the if branch returns three values, but its type use requires two:
// (module (func $type-both-different-value-nums-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1) (i32.const 1)) (else (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in if block
have (i32, i32, i32)
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-last-void-vs-nums`,
// This should err because the branch in the if returns no values, but its type use requires two:
// (module (func $type-then-break-last-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (br 0)) (else (i32.const 1) (i32.const 1)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeBr, 0, // (then (br 0))
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-last-void-vs-nums`,
// This should err because the branch in the else returns no values, but its type use requires two:
// (module (func $type-else-break-last-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1) (then (i32.const 1) (i32.const 1)) (else (br 0)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeBr, 0, // (else (br 0))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-empty-vs-nums`,
// This should err because the branch in the if returns no values, but its type use requires two:
// (module (func $type-then-break-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (br 0) (i32.const 1) (i32.const 1))
// (else (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-empty-vs-nums`,
// This should err because the branch in the else returns no values, but its type use requires two:
// (module (func $type-else-break-empty-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1) (i32.const 1))
// (else (br 0) (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-void-vs-nums`,
// This should err because the branch in the if evaluates to no values, but its type use requires two:
// (module (func $type-then-break-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (br 0 (nop)) (i32.const 1) (i32.const 1))
// (else (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeNop, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0 (nop)) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-void-vs-nums`,
// This should err because the branch in the else evaluates to no values, but its type use requires two:
// (module (func $type-else-break-void-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1) (i32.const 1))
// (else (br 0 (nop)) (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeNop, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0 (nop)) (i32.const 1) (i32.const 1))
// ^^ NOTE: consts are outside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have ()
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-num-vs-nums`,
// This should err because the branch in the if evaluates to one value, but its type use requires two:
// (module (func $type-then-break-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// (else (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeElse, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (i32.const 1) (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i64)
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-num-vs-nums`,
// This should err because the branch in the else evaluates to one value, but its type use requires two:
// (module (func $type-else-break-num-vs-nums (result i32 i32)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1) (i32.const 1))
// (else (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, OpcodeI32Const, 1, // (then (i32.const 1) (i32.const 1))
OpcodeElse, OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, OpcodeI32Const, 1, // (else (br 0 (i64.const 1)) (i32.const 1) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i64)
want (i32, i32)`,
},
{
name: `if.wast - type-then-break-partial-vs-nums`,
// This should err because the branch in the if evaluates to one value, but its type use requires two:
// (module (func $type-then-break-partial-vs-nums (result i32 i32)
// (i32.const 1)
// (if (result i32 i32) (i32.const 1)
// (then (br 0 (i64.const 1)) (i32.const 1))
// (else (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, // (then (br 0 (i64.const 1)) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeElse, OpcodeI32Const, 1, // (else (i32.const 1))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in br block
have (i64)
want (i32, i32)`,
},
{
name: `if.wast - type-else-break-partial-vs-nums`,
// This should err because the branch in the if evaluates to one value, but its type use requires two:
// (module (func $type-else-break-partial-vs-nums (result i32 i32)
// (i32.const 1)
// (if (result i32 i32) (i32.const 1)
// (then (i32.const 1))
// (else (br 0 (i64.const 1)) (i32.const 1))
// )
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1)
OpcodeI32Const, 1, OpcodeIf, 0x00, // (if (result i32 i32) (i32.const 1)
OpcodeI32Const, 1, // (then (i32.const 1))
OpcodeElse, OpcodeI64Const, 1, OpcodeBr, 0, OpcodeI32Const, 1, // (else (br 0 (i64.const 1)) (i32.const 1))
// ^^ NOTE: only one (incorrect) const is inside the br block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in if block
have (i32)
want (i32, i32)`,
},
{
name: `if.wast - type-param-void-vs-num`,
// This should err because the stack has no values, but the if type use requires two:
// (module (func $type-param-void-vs-num
// (if (param i32) (i32.const 1) (then (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop)))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32)`,
},
{
name: `if.wast - type-param-void-vs-nums`,
// This should err because the stack has no values, but the if type use requires two:
// (module (func $type-param-void-vs-nums
// (if (param i32 f64) (i32.const 1) (then (drop) (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32 f64) (i32.const 1)
OpcodeI32Const, 1, OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32, f64)`,
},
{
name: `if.wast - type-param-num-vs-num`,
// This should err because the stack has a different value that what the if type use requires:
// (module (func $type-param-num-vs-num
// (f32.const 0) (if (param i32) (i32.const 1) (then (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in if block as param[0] type i32",
},
{
name: `if.wast - type-param-num-vs-nums`,
// This should err because the stack has one value, but the if type use requires two:
// (module (func $type-param-num-vs-nums
// (f32.const 0) (if (param f32 i32) (i32.const 1) (then (drop) (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param f32 i32) (i32.const 1)
OpcodeDrop, OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have (f32)
want (f32, i32)`,
},
{
name: `if.wast - type-param-nested-void-vs-num`,
// This should err because the stack has no values, but the if type use requires one:
// (module (func $type-param-nested-void-vs-num
// (block (if (param i32) (i32.const 1) (then (drop))))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32)`,
},
{
name: `if.wast - type-param-void-vs-nums`,
// This should err because the stack has no values, but the if type use requires two:
// (module (func $type-param-void-vs-nums
// (block (if (param i32 f64) (i32.const 1) (then (drop) (drop))))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32 f64) (i32.const 1)
OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have ()
want (i32, f64)`,
},
{
name: `if.wast - type-param-num-vs-num`,
// This should err because the stack has a different values than required by the if type use:
// (module (func $type-param-num-vs-num
// (block (f32.const 0) (if (param i32) (i32.const 1) (then (drop))))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param i32) (i32.const 1)
OpcodeDrop, // (then (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in if block as param[0] type i32",
},
{
name: `if.wast - type-param-num-vs-nums`,
// This should err because the stack has one value, but the if type use requires two:
// (module (func $type-param-num-vs-nums
// (block (f32.const 0) (if (param f32 i32) (i32.const 1) (then (drop) (drop))))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeI32Const, 1, OpcodeIf, 0x01, // (if (param f32 i32) (i32.const 1)
OpcodeDrop, // (then (drop) (drop))
OpcodeEnd, // block
OpcodeEnd, // if
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for if block
have (f32)
want (f32, i32)`,
},
// test/core/loop.wast
{
name: `loop.wast - wrong signature for loop type use`,
// This should err because the loop type use returns no values, but its block returns one:
// (module
// (type $sig (func))
// (func (loop (type $sig) (i32.const 0)))
// )
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0, OpcodeI32Const, 0, // (loop (type $sig) (i32.const 0))
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in loop block
have (i32)
want ()`,
},
{
name: `loop.wast - type-value-nums-vs-void`,
// This should err because the empty block type requires no values, but the loop returns two:
// (module (func $type-value-nums-vs-void
// (loop (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0x40, OpcodeI32Const, 1, OpcodeI32Const, 2, // (loop (i32.const 1) (i32.const 2))
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in loop block
have (i32, i32)
want ()`,
},
{
name: `loop.wast - type-value-empty-vs-nums`,
// This should err because the loop type use returns two values, but the block returns none:
// (module (func $type-value-empty-vs-nums (result i32 i32)
// (loop (result i32 i32))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32 i32)) - matches existing func type
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have ()
want (i32, i32)`,
},
{
name: `loop.wast - type-value-void-vs-nums`,
// This should err because the loop type use returns two values, but the block returns none:
// (module (func $type-value-void-vs-nums (result i32 i32)
// (loop (result i32 i32) (nop))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
OpcodeNop, // (nop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have ()
want (i32, i32)`,
},
{
name: `loop.wast - type-value-num-vs-nums`,
// This should err because the loop type use returns two values, but the block returns one:
// (module (func $type-value-num-vs-nums (result i32 i32)
// (loop (result i32 i32) (i32.const 0))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
OpcodeI32Const, 0, // (i32.const 0)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have (i32)
want (i32, i32)`,
},
{
name: `loop.wast - type-value-partial-vs-nums`,
// This should err because the loop type use returns two values, but the block returns one:
// (module (func $type-value-partial-vs-nums (result i32 i32)
// (i32.const 1) (loop (result i32 i32) (i32.const 2))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1, // (i32.const 1) - NOTE: outside the loop!
OpcodeLoop, 0x0, // (loop (result i32 i32) - matches existing func type
OpcodeI32Const, 2, // (i32.const 2)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough results in loop block
have (i32)
want (i32, i32)`,
},
{
name: `loop.wast - type-value-nums-vs-num`,
// This should err because the loop type use returns one value, but the block returns two:
// (module (func $type-value-nums-vs-num (result i32)
// (loop (result i32) (i32.const 1) (i32.const 2))
// ))
module: &Module{
TypeSection: []*FunctionType{v_i32},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0x0, // (loop (result i32) - matches existing func type
OpcodeI32Const, 1, OpcodeI32Const, 2, // (i32.const 1) (i32.const 2))
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `too many results in loop block
have (i32, i32)
want (i32)`,
},
{
name: `loop.wast - type-param-void-vs-num`,
// This should err because the loop type use requires one param, but the stack has none:
// (module (func $type-param-void-vs-num
// (loop (param i32) (drop))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32)`,
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires two params, but the stack has none:
// (module (func $type-param-void-vs-nums
// (loop (param i32 f64) (drop) (drop))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeLoop, 0x1, // (loop (param i32 f64)
OpcodeDrop, // (drop)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32, f64)`,
},
{
name: `loop.wast - type-param-num-vs-num`,
// This should err because the loop type use requires a different param type than what's on the stack:
// (module (func $type-param-num-vs-num
// (f32.const 0) (loop (param i32) (drop))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in loop block as param[0] type i32",
},
{
name: `loop.wast - type-param-num-vs-num`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-num-vs-nums
// (f32.const 0) (loop (param f32 i32) (drop) (drop))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param f32 i32)
OpcodeDrop, OpcodeDrop, // (drop) (drop)
OpcodeEnd, // loop
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have (f32)
want (f32, i32)`,
},
{
name: `loop.wast - type-param-nested-void-vs-num`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-nested-void-vs-num
// (block (loop (param i32) (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32)`,
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-void-vs-nums
// (block (loop (param i32 f64) (drop) (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32f64_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeLoop, 0x1, // (loop (param i32 f64)
OpcodeDrop, OpcodeDrop, // (drop) (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have ()
want (i32, f64)`,
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires a different param type than what's on the stack:
// (module (func $type-param-num-vs-num
// (block (f32.const 0) (loop (param i32) (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param i32)
OpcodeDrop, // (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: "cannot use f32 in loop block as param[0] type i32",
},
{
name: `loop.wast - type-param-void-vs-nums`,
// This should err because the loop type use requires a more parameters than what's on the stack:
// (module (func $type-param-num-vs-nums
// (block (f32.const 0) (loop (param f32 i32) (drop) (drop)))
// ))
module: &Module{
TypeSection: []*FunctionType{v_v, f32i32_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeBlock, 0x40, // (block
OpcodeF32Const, 0, 0, 0, 0, // (f32.const 0)
OpcodeLoop, 0x1, // (loop (param f32 i32)
OpcodeDrop, OpcodeDrop, // (drop) (drop)
OpcodeEnd, // loop
OpcodeEnd, // block
OpcodeEnd, // func
}}},
},
expectedErr: `not enough params for loop block
have (f32)
want (f32, i32)`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
err := tc.module.validateFunction(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestModule_funcValidation_CallIndirect(t *testing.T) {
t.Run("ok", func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1,
OpcodeCallIndirect, 0, 0,
OpcodeEnd,
}}},
}
err := m.validateFunction(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{Type: RefTypeFuncref}}, nil)
require.NoError(t, err)
})
t.Run("non zero table index", func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1,
OpcodeCallIndirect, 0, 100,
OpcodeEnd,
}}},
}
t.Run("disabled", func(t *testing.T) {
err := m.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}}, nil)
require.EqualError(t, err, "table index must be zero but was 100: feature \"reference-types\" is disabled")
})
t.Run("enabled but out of range", func(t *testing.T) {
err := m.validateFunction(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}}, nil)
require.EqualError(t, err, "unknown table index: 100")
})
})
t.Run("non funcref table", func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{
OpcodeI32Const, 1,
OpcodeCallIndirect, 0, 0,
OpcodeEnd,
}}},
}
err := m.validateFunction(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{Type: RefTypeExternref}}, nil)
require.EqualError(t, err, "table is not funcref type but was externref for call_indirect")
})
}
func TestModule_funcValidation_RefTypes(t *testing.T) {
tests := []struct {
name string
body []byte
flag api.CoreFeatures
declaredFunctionIndexes map[Index]struct{}
expectedErr string
}{
{
name: "ref.null (funcref)",
flag: api.CoreFeatureReferenceTypes,
body: []byte{
OpcodeRefNull, ValueTypeFuncref,
OpcodeDrop, OpcodeEnd,
},
},
{
name: "ref.null (externref)",
flag: api.CoreFeatureReferenceTypes,
body: []byte{
OpcodeRefNull, ValueTypeExternref,
OpcodeDrop, OpcodeEnd,
},
},
{
name: "ref.null - disabled",
flag: api.CoreFeaturesV1,
body: []byte{
OpcodeRefNull, ValueTypeFuncref,
OpcodeDrop, OpcodeEnd,
},
expectedErr: "ref.null invalid as feature \"reference-types\" is disabled",
},
{
name: "ref.is_null",
flag: api.CoreFeatureReferenceTypes,
body: []byte{
OpcodeRefNull, ValueTypeFuncref,
OpcodeRefIsNull,
OpcodeDrop, OpcodeEnd,
},
},
{
name: "ref.is_null - disabled",
flag: api.CoreFeaturesV1,
body: []byte{
OpcodeRefIsNull,
OpcodeDrop, OpcodeEnd,
},
expectedErr: `ref.is_null invalid as feature "reference-types" is disabled`,
},
{
name: "ref.func",
flag: api.CoreFeatureReferenceTypes,
declaredFunctionIndexes: map[uint32]struct{}{0: {}},
body: []byte{
OpcodeRefFunc, 0,
OpcodeDrop, OpcodeEnd,
},
},
{
name: "ref.func - undeclared function index",
flag: api.CoreFeatureReferenceTypes,
declaredFunctionIndexes: map[uint32]struct{}{0: {}},
body: []byte{
OpcodeRefFunc, 100,
OpcodeDrop, OpcodeEnd,
},
expectedErr: `undeclared function index 100 for ref.func`,
},
{
name: "ref.func",
flag: api.CoreFeaturesV1,
declaredFunctionIndexes: map[uint32]struct{}{0: {}},
body: []byte{
OpcodeRefFunc, 0,
OpcodeDrop, OpcodeEnd,
},
expectedErr: "ref.func invalid as feature \"reference-types\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
}
err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, nil, tc.declaredFunctionIndexes)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_funcValidation_TableGrowSizeFill(t *testing.T) {
tables := []*Table{{Type: RefTypeFuncref}, {Type: RefTypeExternref}}
tests := []struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}{
{
name: "table.grow (funcref)",
body: []byte{
OpcodeRefNull, RefTypeFuncref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableGrow,
0, // Table Index.
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.grow (funcref) - type mismatch",
body: []byte{
OpcodeRefNull, RefTypeFuncref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableGrow,
1, // Table of externref type -> mismatch.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `cannot pop the operand for table.grow: type mismatch: expected externref, but was funcref`,
},
{
name: "table.grow (externref)",
body: []byte{
OpcodeRefNull, RefTypeExternref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableGrow,
1, // Table Index.
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.grow (externref) type mismatch",
body: []byte{
OpcodeRefNull, RefTypeExternref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableGrow,
0, // Table of funcref type -> mismatch.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `cannot pop the operand for table.grow: type mismatch: expected funcref, but was externref`,
},
{
name: "table.grow - table not found",
body: []byte{
OpcodeRefNull, RefTypeFuncref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableGrow,
10, // Table Index.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `table of index 10 not found`,
},
{
name: "table.size - table not found",
body: []byte{
OpcodeMiscPrefix, OpcodeMiscTableSize,
10, // Table Index.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `table of index 10 not found`,
},
{
name: "table.size",
body: []byte{
OpcodeMiscPrefix, OpcodeMiscTableSize,
1, // Table Index.
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.fill (funcref)",
body: []byte{
OpcodeI32Const, 1, // offset
OpcodeRefNull, RefTypeFuncref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableFill,
0, // Table Index.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.fill (funcref) - type mismatch",
body: []byte{
OpcodeI32Const, 1, // offset
OpcodeRefNull, RefTypeFuncref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableFill,
1, // Table of externref type -> mismatch.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `cannot pop the operand for table.fill: type mismatch: expected externref, but was funcref`,
},
{
name: "table.fill (externref)",
body: []byte{
OpcodeI32Const, 1, // offset
OpcodeRefNull, RefTypeExternref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableFill,
1, // Table Index.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.fill (externref) - type mismatch",
body: []byte{
OpcodeI32Const, 1, // offset
OpcodeRefNull, RefTypeExternref,
OpcodeI32Const, 1, // number of elements
OpcodeMiscPrefix, OpcodeMiscTableFill,
0, // Table of funcref type -> mismatch.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `cannot pop the operand for table.fill: type mismatch: expected funcref, but was externref`,
},
{
name: "table.fill - table not found",
body: []byte{
OpcodeMiscPrefix, OpcodeMiscTableFill,
10, // Table Index.
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `table of index 10 not found`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
}
err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, tables, nil)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_funcValidation_TableGetSet(t *testing.T) {
tables := []*Table{{Type: RefTypeFuncref}, {Type: RefTypeExternref}}
tests := []struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}{
{
name: "table.get (funcref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeTableGet, 0,
OpcodeRefIsNull,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.get (externref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeTableGet, 1,
OpcodeRefIsNull,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.get (disabled)",
body: []byte{
OpcodeI32Const, 0,
OpcodeTableGet, 0,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeaturesV1,
expectedErr: `table.get is invalid as feature "reference-types" is disabled`,
},
{
name: "table.set (funcref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeRefNull, ValueTypeFuncref,
OpcodeTableSet, 0,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.set type mismatch (src=funcref, dst=externref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeRefNull, ValueTypeFuncref,
OpcodeTableSet, 1,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `cannot pop the operand for table.set: type mismatch: expected externref, but was funcref`,
},
{
name: "table.set (externref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeRefNull, ValueTypeExternref,
OpcodeTableSet, 1,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
},
{
name: "table.set type mismatch (src=externref, dst=funcref)",
body: []byte{
OpcodeI32Const, 0,
OpcodeRefNull, ValueTypeExternref,
OpcodeTableSet, 0,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `cannot pop the operand for table.set: type mismatch: expected funcref, but was externref`,
},
{
name: "table.set (disabled)",
body: []byte{
OpcodeTableSet, 1,
OpcodeEnd,
},
flag: api.CoreFeaturesV1,
expectedErr: `table.set is invalid as feature "reference-types" is disabled`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
}
err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, tables, nil)
if tc.expectedErr != "" {
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
func TestModule_funcValidation_Select_error(t *testing.T) {
tests := []struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}{
{
name: "typed_select (disabled)",
body: []byte{
OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
OpcodeTypedSelect, 1, ValueTypeI32, // immediate vector's size must be one
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeaturesV1,
expectedErr: "typed_select is invalid as feature \"reference-types\" is disabled",
},
{
name: "typed_select (too many immediate types)",
body: []byte{
OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
OpcodeTypedSelect, 2, // immediate vector's size must be one
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `too many type immediates for typed_select`,
},
{
name: "typed_select (immediate type not found)",
body: []byte{
OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeI32Const, 0,
OpcodeTypedSelect, 1, 0,
OpcodeEnd,
},
flag: api.CoreFeatureReferenceTypes,
expectedErr: `invalid type unknown for typed_select`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
}
err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, nil, nil)
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestModule_funcValidation_SIMD(t *testing.T) {
addV128Const := func(in []byte) []byte {
return append(in, OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1)
}
vv2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
ret = addV128Const(ret)
return append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
vvv2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
ret = addV128Const(ret)
ret = addV128Const(ret)
return append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
v2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
return append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
vi2v := func(vec OpcodeVec) (ret []byte) {
ret = addV128Const(ret)
return append(ret,
OpcodeI32Const, 1,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
}
load := func(vec OpcodeVec, offset, align uint32) (ret []byte) {
ret = []byte{
OpcodeI32Const, 1,
OpcodeVecPrefix,
vec,
}
ret = append(ret, leb128.EncodeUint32(align)...)
ret = append(ret, leb128.EncodeUint32(offset)...)
ret = append(ret,
OpcodeDrop,
OpcodeEnd,
)
return
}
loadLane := func(vec OpcodeVec, offset, align uint32, lane byte) (ret []byte) {
ret = addV128Const([]byte{OpcodeI32Const, 1})
ret = append(ret,
OpcodeVecPrefix,
vec,
)
ret = append(ret, leb128.EncodeUint32(align)...)
ret = append(ret, leb128.EncodeUint32(offset)...)
ret = append(ret,
lane,
OpcodeDrop,
OpcodeEnd,
)
return
}
storeLane := func(vec OpcodeVec, offset, align uint32, lane byte) (ret []byte) {
ret = addV128Const([]byte{OpcodeI32Const, 1})
ret = append(ret,
OpcodeVecPrefix,
vec,
)
ret = append(ret, leb128.EncodeUint32(align)...)
ret = append(ret, leb128.EncodeUint32(offset)...)
ret = append(ret,
lane,
OpcodeEnd,
)
return
}
extractLane := func(vec OpcodeVec, lane byte) (ret []byte) {
ret = addV128Const(ret)
ret = append(ret,
OpcodeVecPrefix,
vec,
lane,
OpcodeDrop,
OpcodeEnd,
)
return
}
replaceLane := func(vec OpcodeVec, lane byte) (ret []byte) {
ret = addV128Const(ret)
switch vec {
case OpcodeVecI8x16ReplaceLane, OpcodeVecI16x8ReplaceLane, OpcodeVecI32x4ReplaceLane:
ret = append(ret, OpcodeI32Const, 0)
case OpcodeVecI64x2ReplaceLane:
ret = append(ret, OpcodeI64Const, 0)
case OpcodeVecF32x4ReplaceLane:
ret = append(ret, OpcodeF32Const, 0, 0, 0, 0)
case OpcodeVecF64x2ReplaceLane:
ret = append(ret, OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0, 0)
}
ret = append(ret,
OpcodeVecPrefix,
vec,
lane,
OpcodeDrop,
OpcodeEnd,
)
return
}
splat := func(vec OpcodeVec) (ret []byte) {
switch vec {
case OpcodeVecI8x16Splat, OpcodeVecI16x8Splat, OpcodeVecI32x4Splat:
ret = append(ret, OpcodeI32Const, 0, 0, 0, 0)
case OpcodeVecI64x2Splat:
ret = append(ret, OpcodeI64Const, 0, 0, 0, 0, 0, 0, 0, 0)
case OpcodeVecF32x4Splat:
ret = append(ret, OpcodeF32Const, 0, 0, 0, 0)
case OpcodeVecF64x2Splat:
ret = append(ret, OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0, 0)
}
ret = append(ret,
OpcodeVecPrefix,
vec,
OpcodeDrop,
OpcodeEnd,
)
return
}
tests := []struct {
name string
body []byte
expectedErr string
}{
{
name: "v128.const",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeDrop,
OpcodeEnd,
},
},
{name: OpcodeVecI8x16AddName, body: vv2v(OpcodeVecI8x16Add)},
{name: OpcodeVecI16x8AddName, body: vv2v(OpcodeVecI16x8Add)},
{name: OpcodeVecI32x4AddName, body: vv2v(OpcodeVecI32x4Add)},
{name: OpcodeVecI64x2AddName, body: vv2v(OpcodeVecI64x2Add)},
{name: OpcodeVecI8x16SubName, body: vv2v(OpcodeVecI8x16Sub)},
{name: OpcodeVecI16x8SubName, body: vv2v(OpcodeVecI16x8Sub)},
{name: OpcodeVecI32x4SubName, body: vv2v(OpcodeVecI32x4Sub)},
{name: OpcodeVecI64x2SubName, body: vv2v(OpcodeVecI64x2Sub)},
{name: OpcodeVecV128AnyTrueName, body: v2v(OpcodeVecV128AnyTrue)},
{name: OpcodeVecI8x16AllTrueName, body: v2v(OpcodeVecI8x16AllTrue)},
{name: OpcodeVecI16x8AllTrueName, body: v2v(OpcodeVecI16x8AllTrue)},
{name: OpcodeVecI32x4AllTrueName, body: v2v(OpcodeVecI32x4AllTrue)},
{name: OpcodeVecI64x2AllTrueName, body: v2v(OpcodeVecI64x2AllTrue)},
{name: OpcodeVecI8x16BitMaskName, body: v2v(OpcodeVecI8x16BitMask)},
{name: OpcodeVecI16x8BitMaskName, body: v2v(OpcodeVecI16x8BitMask)},
{name: OpcodeVecI32x4BitMaskName, body: v2v(OpcodeVecI32x4BitMask)},
{name: OpcodeVecI64x2BitMaskName, body: v2v(OpcodeVecI64x2BitMask)},
{name: OpcodeVecV128LoadName, body: load(OpcodeVecV128Load, 0, 0)},
{name: OpcodeVecV128LoadName + "/align=4", body: load(OpcodeVecV128Load, 0, 4)},
{name: OpcodeVecV128Load8x8SName, body: load(OpcodeVecV128Load8x8s, 1, 0)},
{name: OpcodeVecV128Load8x8SName + "/align=1", body: load(OpcodeVecV128Load8x8s, 0, 1)},
{name: OpcodeVecV128Load8x8UName, body: load(OpcodeVecV128Load8x8u, 0, 0)},
{name: OpcodeVecV128Load8x8UName + "/align=1", body: load(OpcodeVecV128Load8x8u, 0, 1)},
{name: OpcodeVecV128Load16x4SName, body: load(OpcodeVecV128Load16x4s, 1, 0)},
{name: OpcodeVecV128Load16x4SName + "/align=2", body: load(OpcodeVecV128Load16x4s, 0, 2)},
{name: OpcodeVecV128Load16x4UName, body: load(OpcodeVecV128Load16x4u, 0, 0)},
{name: OpcodeVecV128Load16x4UName + "/align=2", body: load(OpcodeVecV128Load16x4u, 0, 2)},
{name: OpcodeVecV128Load32x2SName, body: load(OpcodeVecV128Load32x2s, 1, 0)},
{name: OpcodeVecV128Load32x2SName + "/align=3", body: load(OpcodeVecV128Load32x2s, 0, 3)},
{name: OpcodeVecV128Load32x2UName, body: load(OpcodeVecV128Load32x2u, 0, 0)},
{name: OpcodeVecV128Load32x2UName + "/align=3", body: load(OpcodeVecV128Load32x2u, 0, 3)},
{name: OpcodeVecV128Load8SplatName, body: load(OpcodeVecV128Load8Splat, 2, 0)},
{name: OpcodeVecV128Load16SplatName, body: load(OpcodeVecV128Load16Splat, 0, 1)},
{name: OpcodeVecV128Load32SplatName, body: load(OpcodeVecV128Load32Splat, 3, 2)},
{name: OpcodeVecV128Load64SplatName, body: load(OpcodeVecV128Load64Splat, 0, 3)},
{name: OpcodeVecV128Load32zeroName, body: load(OpcodeVecV128Load32zero, 0, 2)},
{name: OpcodeVecV128Load64zeroName, body: load(OpcodeVecV128Load64zero, 5, 3)},
{name: OpcodeVecV128Load8LaneName, body: loadLane(OpcodeVecV128Load8Lane, 5, 0, 10)},
{name: OpcodeVecV128Load16LaneName, body: loadLane(OpcodeVecV128Load16Lane, 100, 1, 7)},
{name: OpcodeVecV128Load32LaneName, body: loadLane(OpcodeVecV128Load32Lane, 0, 2, 3)},
{name: OpcodeVecV128Load64LaneName, body: loadLane(OpcodeVecV128Load64Lane, 0, 3, 1)},
{
name: OpcodeVecV128StoreName, body: []byte{
OpcodeI32Const,
1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128Store,
4, // alignment
10, // offset
OpcodeEnd,
},
},
{name: OpcodeVecV128Store8LaneName, body: storeLane(OpcodeVecV128Store8Lane, 0, 0, 0)},
{name: OpcodeVecV128Store8LaneName + "/lane=15", body: storeLane(OpcodeVecV128Store8Lane, 100, 0, 15)},
{name: OpcodeVecV128Store16LaneName, body: storeLane(OpcodeVecV128Store16Lane, 0, 0, 0)},
{name: OpcodeVecV128Store16LaneName + "/lane=7/align=1", body: storeLane(OpcodeVecV128Store16Lane, 100, 1, 7)},
{name: OpcodeVecV128Store32LaneName, body: storeLane(OpcodeVecV128Store32Lane, 0, 0, 0)},
{name: OpcodeVecV128Store32LaneName + "/lane=3/align=2", body: storeLane(OpcodeVecV128Store32Lane, 100, 2, 3)},
{name: OpcodeVecV128Store64LaneName, body: storeLane(OpcodeVecV128Store64Lane, 0, 0, 0)},
{name: OpcodeVecV128Store64LaneName + "/lane=1/align=3", body: storeLane(OpcodeVecV128Store64Lane, 50, 3, 1)},
{name: OpcodeVecI8x16ExtractLaneSName, body: extractLane(OpcodeVecI8x16ExtractLaneS, 0)},
{name: OpcodeVecI8x16ExtractLaneSName + "/lane=15", body: extractLane(OpcodeVecI8x16ExtractLaneS, 15)},
{name: OpcodeVecI8x16ExtractLaneUName, body: extractLane(OpcodeVecI8x16ExtractLaneU, 0)},
{name: OpcodeVecI8x16ExtractLaneUName + "/lane=15", body: extractLane(OpcodeVecI8x16ExtractLaneU, 15)},
{name: OpcodeVecI16x8ExtractLaneSName, body: extractLane(OpcodeVecI16x8ExtractLaneS, 0)},
{name: OpcodeVecI16x8ExtractLaneSName + "/lane=7", body: extractLane(OpcodeVecI16x8ExtractLaneS, 7)},
{name: OpcodeVecI16x8ExtractLaneUName, body: extractLane(OpcodeVecI16x8ExtractLaneU, 0)},
{name: OpcodeVecI16x8ExtractLaneUName + "/lane=8", body: extractLane(OpcodeVecI16x8ExtractLaneU, 7)},
{name: OpcodeVecI32x4ExtractLaneName, body: extractLane(OpcodeVecI32x4ExtractLane, 0)},
{name: OpcodeVecI32x4ExtractLaneName + "/lane=3", body: extractLane(OpcodeVecI32x4ExtractLane, 3)},
{name: OpcodeVecI64x2ExtractLaneName, body: extractLane(OpcodeVecI64x2ExtractLane, 0)},
{name: OpcodeVecI64x2ExtractLaneName + "/lane=1", body: extractLane(OpcodeVecI64x2ExtractLane, 1)},
{name: OpcodeVecF32x4ExtractLaneName, body: extractLane(OpcodeVecF32x4ExtractLane, 0)},
{name: OpcodeVecF32x4ExtractLaneName + "/lane=3", body: extractLane(OpcodeVecF32x4ExtractLane, 3)},
{name: OpcodeVecF64x2ExtractLaneName, body: extractLane(OpcodeVecF64x2ExtractLane, 0)},
{name: OpcodeVecF64x2ExtractLaneName + "/lane=1", body: extractLane(OpcodeVecF64x2ExtractLane, 1)},
{name: OpcodeVecI8x16ReplaceLaneName, body: replaceLane(OpcodeVecI8x16ReplaceLane, 0)},
{name: OpcodeVecI8x16ReplaceLaneName + "/lane=15", body: replaceLane(OpcodeVecI8x16ReplaceLane, 15)},
{name: OpcodeVecI16x8ReplaceLaneName, body: replaceLane(OpcodeVecI16x8ReplaceLane, 0)},
{name: OpcodeVecI16x8ReplaceLaneName + "/lane=7", body: replaceLane(OpcodeVecI16x8ReplaceLane, 7)},
{name: OpcodeVecI32x4ReplaceLaneName, body: replaceLane(OpcodeVecI32x4ReplaceLane, 0)},
{name: OpcodeVecI32x4ReplaceLaneName + "/lane=3", body: replaceLane(OpcodeVecI32x4ReplaceLane, 3)},
{name: OpcodeVecI64x2ReplaceLaneName, body: replaceLane(OpcodeVecI64x2ReplaceLane, 0)},
{name: OpcodeVecI64x2ReplaceLaneName + "/lane=1", body: replaceLane(OpcodeVecI64x2ReplaceLane, 1)},
{name: OpcodeVecF32x4ReplaceLaneName, body: replaceLane(OpcodeVecF32x4ReplaceLane, 0)},
{name: OpcodeVecF32x4ReplaceLaneName + "/lane=3", body: replaceLane(OpcodeVecF32x4ReplaceLane, 3)},
{name: OpcodeVecF64x2ReplaceLaneName, body: replaceLane(OpcodeVecF64x2ReplaceLane, 0)},
{name: OpcodeVecF64x2ReplaceLaneName + "/lane=1", body: replaceLane(OpcodeVecF64x2ReplaceLane, 1)},
{name: OpcodeVecI8x16SplatName, body: splat(OpcodeVecI8x16Splat)},
{name: OpcodeVecI16x8SplatName, body: splat(OpcodeVecI16x8Splat)},
{name: OpcodeVecI32x4SplatName, body: splat(OpcodeVecI32x4Splat)},
{name: OpcodeVecI64x2SplatName, body: splat(OpcodeVecI64x2Splat)},
{name: OpcodeVecF32x4SplatName, body: splat(OpcodeVecF32x4Splat)},
{name: OpcodeVecF64x2SplatName, body: splat(OpcodeVecF64x2Splat)},
{name: OpcodeVecI8x16SwizzleName, body: vv2v(OpcodeVecI8x16Swizzle)},
{
name: OpcodeVecV128i8x16ShuffleName, body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecV128i8x16Shuffle,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
OpcodeDrop,
OpcodeEnd,
},
},
{name: OpcodeVecV128NotName, body: v2v(OpcodeVecV128Not)},
{name: OpcodeVecV128AndName, body: vv2v(OpcodeVecV128And)},
{name: OpcodeVecV128AndNotName, body: vv2v(OpcodeVecV128AndNot)},
{name: OpcodeVecV128OrName, body: vv2v(OpcodeVecV128Or)},
{name: OpcodeVecV128XorName, body: vv2v(OpcodeVecV128Xor)},
{name: OpcodeVecV128BitselectName, body: vvv2v(OpcodeVecV128Bitselect)},
{name: OpcodeVecI8x16ShlName, body: vi2v(OpcodeVecI8x16Shl)},
{name: OpcodeVecI8x16ShrSName, body: vi2v(OpcodeVecI8x16ShrS)},
{name: OpcodeVecI8x16ShrUName, body: vi2v(OpcodeVecI8x16ShrU)},
{name: OpcodeVecI16x8ShlName, body: vi2v(OpcodeVecI16x8Shl)},
{name: OpcodeVecI16x8ShrSName, body: vi2v(OpcodeVecI16x8ShrS)},
{name: OpcodeVecI16x8ShrUName, body: vi2v(OpcodeVecI16x8ShrU)},
{name: OpcodeVecI32x4ShlName, body: vi2v(OpcodeVecI32x4Shl)},
{name: OpcodeVecI32x4ShrSName, body: vi2v(OpcodeVecI32x4ShrS)},
{name: OpcodeVecI32x4ShrUName, body: vi2v(OpcodeVecI32x4ShrU)},
{name: OpcodeVecI64x2ShlName, body: vi2v(OpcodeVecI64x2Shl)},
{name: OpcodeVecI64x2ShrSName, body: vi2v(OpcodeVecI64x2ShrS)},
{name: OpcodeVecI64x2ShrUName, body: vi2v(OpcodeVecI64x2ShrU)},
{name: OpcodeVecI8x16EqName, body: vv2v(OpcodeVecI8x16Eq)},
{name: OpcodeVecI8x16NeName, body: vv2v(OpcodeVecI8x16Ne)},
{name: OpcodeVecI8x16LtSName, body: vv2v(OpcodeVecI8x16LtS)},
{name: OpcodeVecI8x16LtUName, body: vv2v(OpcodeVecI8x16LtU)},
{name: OpcodeVecI8x16GtSName, body: vv2v(OpcodeVecI8x16GtS)},
{name: OpcodeVecI8x16GtUName, body: vv2v(OpcodeVecI8x16GtU)},
{name: OpcodeVecI8x16LeSName, body: vv2v(OpcodeVecI8x16LeS)},
{name: OpcodeVecI8x16LeUName, body: vv2v(OpcodeVecI8x16LeU)},
{name: OpcodeVecI8x16GeSName, body: vv2v(OpcodeVecI8x16GeS)},
{name: OpcodeVecI8x16GeUName, body: vv2v(OpcodeVecI8x16GeU)},
{name: OpcodeVecI16x8EqName, body: vv2v(OpcodeVecI16x8Eq)},
{name: OpcodeVecI16x8NeName, body: vv2v(OpcodeVecI16x8Ne)},
{name: OpcodeVecI16x8LtSName, body: vv2v(OpcodeVecI16x8LtS)},
{name: OpcodeVecI16x8LtUName, body: vv2v(OpcodeVecI16x8LtU)},
{name: OpcodeVecI16x8GtSName, body: vv2v(OpcodeVecI16x8GtS)},
{name: OpcodeVecI16x8GtUName, body: vv2v(OpcodeVecI16x8GtU)},
{name: OpcodeVecI16x8LeSName, body: vv2v(OpcodeVecI16x8LeS)},
{name: OpcodeVecI16x8LeUName, body: vv2v(OpcodeVecI16x8LeU)},
{name: OpcodeVecI16x8GeSName, body: vv2v(OpcodeVecI16x8GeS)},
{name: OpcodeVecI16x8GeUName, body: vv2v(OpcodeVecI16x8GeU)},
{name: OpcodeVecI32x4EqName, body: vv2v(OpcodeVecI32x4Eq)},
{name: OpcodeVecI32x4NeName, body: vv2v(OpcodeVecI32x4Ne)},
{name: OpcodeVecI32x4LtSName, body: vv2v(OpcodeVecI32x4LtS)},
{name: OpcodeVecI32x4LtUName, body: vv2v(OpcodeVecI32x4LtU)},
{name: OpcodeVecI32x4GtSName, body: vv2v(OpcodeVecI32x4GtS)},
{name: OpcodeVecI32x4GtUName, body: vv2v(OpcodeVecI32x4GtU)},
{name: OpcodeVecI32x4LeSName, body: vv2v(OpcodeVecI32x4LeS)},
{name: OpcodeVecI32x4LeUName, body: vv2v(OpcodeVecI32x4LeU)},
{name: OpcodeVecI32x4GeSName, body: vv2v(OpcodeVecI32x4GeS)},
{name: OpcodeVecI32x4GeUName, body: vv2v(OpcodeVecI32x4GeU)},
{name: OpcodeVecI64x2EqName, body: vv2v(OpcodeVecI64x2Eq)},
{name: OpcodeVecI64x2NeName, body: vv2v(OpcodeVecI64x2Ne)},
{name: OpcodeVecI64x2LtSName, body: vv2v(OpcodeVecI64x2LtS)},
{name: OpcodeVecI64x2GtSName, body: vv2v(OpcodeVecI64x2GtS)},
{name: OpcodeVecI64x2LeSName, body: vv2v(OpcodeVecI64x2LeS)},
{name: OpcodeVecI64x2GeSName, body: vv2v(OpcodeVecI64x2GeS)},
{name: OpcodeVecF32x4EqName, body: vv2v(OpcodeVecF32x4Eq)},
{name: OpcodeVecF32x4NeName, body: vv2v(OpcodeVecF32x4Ne)},
{name: OpcodeVecF32x4LtName, body: vv2v(OpcodeVecF32x4Lt)},
{name: OpcodeVecF32x4GtName, body: vv2v(OpcodeVecF32x4Gt)},
{name: OpcodeVecF32x4LeName, body: vv2v(OpcodeVecF32x4Le)},
{name: OpcodeVecF32x4GeName, body: vv2v(OpcodeVecF32x4Ge)},
{name: OpcodeVecF64x2EqName, body: vv2v(OpcodeVecF64x2Eq)},
{name: OpcodeVecF64x2NeName, body: vv2v(OpcodeVecF64x2Ne)},
{name: OpcodeVecF64x2LtName, body: vv2v(OpcodeVecF64x2Lt)},
{name: OpcodeVecF64x2GtName, body: vv2v(OpcodeVecF64x2Gt)},
{name: OpcodeVecF64x2LeName, body: vv2v(OpcodeVecF64x2Le)},
{name: OpcodeVecF64x2GeName, body: vv2v(OpcodeVecF64x2Ge)},
{name: OpcodeVecI8x16AddName, body: vv2v(OpcodeVecI8x16Add)},
{name: OpcodeVecI8x16AddSatSName, body: vv2v(OpcodeVecI8x16AddSatS)},
{name: OpcodeVecI8x16AddSatUName, body: vv2v(OpcodeVecI8x16AddSatU)},
{name: OpcodeVecI8x16SubName, body: vv2v(OpcodeVecI8x16Sub)},
{name: OpcodeVecI8x16SubSatSName, body: vv2v(OpcodeVecI8x16SubSatS)},
{name: OpcodeVecI8x16SubSatUName, body: vv2v(OpcodeVecI8x16SubSatU)},
{name: OpcodeVecI16x8AddName, body: vv2v(OpcodeVecI16x8Add)},
{name: OpcodeVecI16x8AddSatSName, body: vv2v(OpcodeVecI16x8AddSatS)},
{name: OpcodeVecI16x8AddSatUName, body: vv2v(OpcodeVecI16x8AddSatU)},
{name: OpcodeVecI16x8SubName, body: vv2v(OpcodeVecI16x8Sub)},
{name: OpcodeVecI16x8SubSatSName, body: vv2v(OpcodeVecI16x8SubSatS)},
{name: OpcodeVecI16x8SubSatUName, body: vv2v(OpcodeVecI16x8SubSatU)},
{name: OpcodeVecI16x8MulName, body: vv2v(OpcodeVecI16x8Mul)},
{name: OpcodeVecI32x4AddName, body: vv2v(OpcodeVecI32x4Add)},
{name: OpcodeVecI32x4SubName, body: vv2v(OpcodeVecI32x4Sub)},
{name: OpcodeVecI32x4MulName, body: vv2v(OpcodeVecI32x4Mul)},
{name: OpcodeVecI64x2AddName, body: vv2v(OpcodeVecI64x2Add)},
{name: OpcodeVecI64x2SubName, body: vv2v(OpcodeVecI64x2Sub)},
{name: OpcodeVecI64x2MulName, body: vv2v(OpcodeVecI64x2Mul)},
{name: OpcodeVecF32x4AddName, body: vv2v(OpcodeVecF32x4Add)},
{name: OpcodeVecF32x4SubName, body: vv2v(OpcodeVecF32x4Sub)},
{name: OpcodeVecF32x4MulName, body: vv2v(OpcodeVecF32x4Mul)},
{name: OpcodeVecF32x4DivName, body: vv2v(OpcodeVecF32x4Div)},
{name: OpcodeVecF64x2AddName, body: vv2v(OpcodeVecF64x2Add)},
{name: OpcodeVecF64x2SubName, body: vv2v(OpcodeVecF64x2Sub)},
{name: OpcodeVecF64x2MulName, body: vv2v(OpcodeVecF64x2Mul)},
{name: OpcodeVecF64x2DivName, body: vv2v(OpcodeVecF64x2Div)},
{name: OpcodeVecI8x16NegName, body: v2v(OpcodeVecI8x16Neg)},
{name: OpcodeVecI16x8NegName, body: v2v(OpcodeVecI16x8Neg)},
{name: OpcodeVecI32x4NegName, body: v2v(OpcodeVecI32x4Neg)},
{name: OpcodeVecI64x2NegName, body: v2v(OpcodeVecI64x2Neg)},
{name: OpcodeVecF32x4NegName, body: v2v(OpcodeVecF32x4Neg)},
{name: OpcodeVecF64x2NegName, body: v2v(OpcodeVecF64x2Neg)},
{name: OpcodeVecF32x4SqrtName, body: v2v(OpcodeVecF32x4Sqrt)},
{name: OpcodeVecF64x2SqrtName, body: v2v(OpcodeVecF64x2Sqrt)},
{name: OpcodeVecI8x16MinSName, body: vv2v(OpcodeVecI8x16MinS)},
{name: OpcodeVecI8x16MinUName, body: vv2v(OpcodeVecI8x16MinU)},
{name: OpcodeVecI8x16MaxSName, body: vv2v(OpcodeVecI8x16MaxS)},
{name: OpcodeVecI8x16MaxUName, body: vv2v(OpcodeVecI8x16MaxU)},
{name: OpcodeVecI8x16AvgrUName, body: vv2v(OpcodeVecI8x16AvgrU)},
{name: OpcodeVecI8x16AbsName, body: v2v(OpcodeVecI8x16Abs)},
{name: OpcodeVecI8x16PopcntName, body: v2v(OpcodeVecI8x16Popcnt)},
{name: OpcodeVecI16x8MinSName, body: vv2v(OpcodeVecI16x8MinS)},
{name: OpcodeVecI16x8MinUName, body: vv2v(OpcodeVecI16x8MinU)},
{name: OpcodeVecI16x8MaxSName, body: vv2v(OpcodeVecI16x8MaxS)},
{name: OpcodeVecI16x8MaxUName, body: vv2v(OpcodeVecI16x8MaxU)},
{name: OpcodeVecI16x8AvgrUName, body: vv2v(OpcodeVecI16x8AvgrU)},
{name: OpcodeVecI16x8AbsName, body: v2v(OpcodeVecI16x8Abs)},
{name: OpcodeVecI32x4MinSName, body: vv2v(OpcodeVecI32x4MinS)},
{name: OpcodeVecI32x4MinUName, body: vv2v(OpcodeVecI32x4MinU)},
{name: OpcodeVecI32x4MaxSName, body: vv2v(OpcodeVecI32x4MaxS)},
{name: OpcodeVecI32x4MaxUName, body: vv2v(OpcodeVecI32x4MaxU)},
{name: OpcodeVecI32x4AbsName, body: v2v(OpcodeVecI32x4Abs)},
{name: OpcodeVecI64x2AbsName, body: v2v(OpcodeVecI64x2Abs)},
{name: OpcodeVecF32x4AbsName, body: v2v(OpcodeVecF32x4Abs)},
{name: OpcodeVecF64x2AbsName, body: v2v(OpcodeVecF64x2Abs)},
{name: OpcodeVecF32x4MinName, body: vv2v(OpcodeVecF32x4Min)},
{name: OpcodeVecF32x4MaxName, body: vv2v(OpcodeVecF32x4Max)},
{name: OpcodeVecF64x2MinName, body: vv2v(OpcodeVecF64x2Min)},
{name: OpcodeVecF64x2MaxName, body: vv2v(OpcodeVecF64x2Max)},
{name: OpcodeVecF32x4CeilName, body: v2v(OpcodeVecF32x4Ceil)},
{name: OpcodeVecF32x4FloorName, body: v2v(OpcodeVecF32x4Floor)},
{name: OpcodeVecF32x4TruncName, body: v2v(OpcodeVecF32x4Trunc)},
{name: OpcodeVecF32x4NearestName, body: v2v(OpcodeVecF32x4Nearest)},
{name: OpcodeVecF64x2CeilName, body: v2v(OpcodeVecF64x2Ceil)},
{name: OpcodeVecF64x2FloorName, body: v2v(OpcodeVecF64x2Floor)},
{name: OpcodeVecF64x2TruncName, body: v2v(OpcodeVecF64x2Trunc)},
{name: OpcodeVecF64x2NearestName, body: v2v(OpcodeVecF64x2Nearest)},
{name: OpcodeVecF32x4MinName, body: vv2v(OpcodeVecF32x4Pmin)},
{name: OpcodeVecF32x4MaxName, body: vv2v(OpcodeVecF32x4Pmax)},
{name: OpcodeVecF64x2MinName, body: vv2v(OpcodeVecF64x2Pmin)},
{name: OpcodeVecF64x2MaxName, body: vv2v(OpcodeVecF64x2Pmax)},
{name: OpcodeVecI16x8ExtendLowI8x16SName, body: v2v(OpcodeVecI16x8ExtendLowI8x16S)},
{name: OpcodeVecI16x8ExtendHighI8x16SName, body: v2v(OpcodeVecI16x8ExtendHighI8x16S)},
{name: OpcodeVecI16x8ExtendLowI8x16UName, body: v2v(OpcodeVecI16x8ExtendLowI8x16U)},
{name: OpcodeVecI16x8ExtendHighI8x16UName, body: v2v(OpcodeVecI16x8ExtendHighI8x16U)},
{name: OpcodeVecI32x4ExtendLowI16x8SName, body: v2v(OpcodeVecI32x4ExtendLowI16x8S)},
{name: OpcodeVecI32x4ExtendHighI16x8SName, body: v2v(OpcodeVecI32x4ExtendHighI16x8S)},
{name: OpcodeVecI32x4ExtendLowI16x8UName, body: v2v(OpcodeVecI32x4ExtendLowI16x8U)},
{name: OpcodeVecI32x4ExtendHighI16x8UName, body: v2v(OpcodeVecI32x4ExtendHighI16x8U)},
{name: OpcodeVecI64x2ExtendLowI32x4SName, body: v2v(OpcodeVecI64x2ExtendLowI32x4S)},
{name: OpcodeVecI64x2ExtendHighI32x4SName, body: v2v(OpcodeVecI64x2ExtendHighI32x4S)},
{name: OpcodeVecI64x2ExtendLowI32x4UName, body: v2v(OpcodeVecI64x2ExtendLowI32x4U)},
{name: OpcodeVecI64x2ExtendHighI32x4UName, body: v2v(OpcodeVecI64x2ExtendHighI32x4U)},
{name: OpcodeVecI16x8Q15mulrSatSName, body: vv2v(OpcodeVecI16x8Q15mulrSatS)},
{name: OpcodeVecI16x8ExtMulLowI8x16SName, body: vv2v(OpcodeVecI16x8ExtMulLowI8x16S)},
{name: OpcodeVecI16x8ExtMulHighI8x16SName, body: vv2v(OpcodeVecI16x8ExtMulHighI8x16S)},
{name: OpcodeVecI16x8ExtMulLowI8x16UName, body: vv2v(OpcodeVecI16x8ExtMulLowI8x16U)},
{name: OpcodeVecI16x8ExtMulHighI8x16UName, body: vv2v(OpcodeVecI16x8ExtMulHighI8x16U)},
{name: OpcodeVecI32x4ExtMulLowI16x8SName, body: vv2v(OpcodeVecI32x4ExtMulLowI16x8S)},
{name: OpcodeVecI32x4ExtMulHighI16x8SName, body: vv2v(OpcodeVecI32x4ExtMulHighI16x8S)},
{name: OpcodeVecI32x4ExtMulLowI16x8UName, body: vv2v(OpcodeVecI32x4ExtMulLowI16x8U)},
{name: OpcodeVecI32x4ExtMulHighI16x8UName, body: vv2v(OpcodeVecI32x4ExtMulHighI16x8U)},
{name: OpcodeVecI64x2ExtMulLowI32x4SName, body: vv2v(OpcodeVecI64x2ExtMulLowI32x4S)},
{name: OpcodeVecI64x2ExtMulHighI32x4SName, body: vv2v(OpcodeVecI64x2ExtMulHighI32x4S)},
{name: OpcodeVecI64x2ExtMulLowI32x4UName, body: vv2v(OpcodeVecI64x2ExtMulLowI32x4U)},
{name: OpcodeVecI64x2ExtMulHighI32x4UName, body: vv2v(OpcodeVecI64x2ExtMulHighI32x4U)},
{name: OpcodeVecI16x8ExtaddPairwiseI8x16SName, body: v2v(OpcodeVecI16x8ExtaddPairwiseI8x16S)},
{name: OpcodeVecI16x8ExtaddPairwiseI8x16UName, body: v2v(OpcodeVecI16x8ExtaddPairwiseI8x16U)},
{name: OpcodeVecI32x4ExtaddPairwiseI16x8SName, body: v2v(OpcodeVecI32x4ExtaddPairwiseI16x8S)},
{name: OpcodeVecI32x4ExtaddPairwiseI16x8UName, body: v2v(OpcodeVecI32x4ExtaddPairwiseI16x8U)},
{name: OpcodeVecF64x2PromoteLowF32x4ZeroName, body: v2v(OpcodeVecF64x2PromoteLowF32x4Zero)},
{name: OpcodeVecF32x4DemoteF64x2ZeroName, body: v2v(OpcodeVecF32x4DemoteF64x2Zero)},
{name: OpcodeVecF32x4ConvertI32x4SName, body: v2v(OpcodeVecF32x4ConvertI32x4S)},
{name: OpcodeVecF32x4ConvertI32x4UName, body: v2v(OpcodeVecF32x4ConvertI32x4U)},
{name: OpcodeVecF64x2ConvertLowI32x4SName, body: v2v(OpcodeVecF64x2ConvertLowI32x4S)},
{name: OpcodeVecF64x2ConvertLowI32x4UName, body: v2v(OpcodeVecF64x2ConvertLowI32x4U)},
{name: OpcodeVecI32x4DotI16x8SName, body: vv2v(OpcodeVecI32x4DotI16x8S)},
{name: OpcodeVecI8x16NarrowI16x8SName, body: vv2v(OpcodeVecI8x16NarrowI16x8S)},
{name: OpcodeVecI8x16NarrowI16x8UName, body: vv2v(OpcodeVecI8x16NarrowI16x8U)},
{name: OpcodeVecI16x8NarrowI32x4SName, body: vv2v(OpcodeVecI16x8NarrowI32x4S)},
{name: OpcodeVecI16x8NarrowI32x4UName, body: vv2v(OpcodeVecI16x8NarrowI32x4U)},
{name: OpcodeVecI32x4TruncSatF32x4SName, body: v2v(OpcodeVecI32x4TruncSatF32x4S)},
{name: OpcodeVecI32x4TruncSatF32x4UName, body: v2v(OpcodeVecI32x4TruncSatF32x4U)},
{name: OpcodeVecI32x4TruncSatF64x2SZeroName, body: v2v(OpcodeVecI32x4TruncSatF64x2SZero)},
{name: OpcodeVecI32x4TruncSatF64x2UZeroName, body: v2v(OpcodeVecI32x4TruncSatF64x2UZero)},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
}
err := m.validateFunction(api.CoreFeatureSIMD, 0, []Index{0}, nil, &Memory{}, nil, nil)
require.NoError(t, err)
})
}
}
func TestModule_funcValidation_SIMD_error(t *testing.T) {
type testCase struct {
name string
body []byte
flag api.CoreFeatures
expectedErr string
}
tests := []testCase{
{
name: "simd disabled",
body: []byte{
OpcodeVecPrefix,
OpcodeVecF32x4Abs,
},
flag: api.CoreFeaturesV1,
expectedErr: "f32x4.abs invalid as feature \"simd\" is disabled",
},
{
name: "v128.const immediate",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
},
flag: api.CoreFeatureSIMD,
expectedErr: "cannot read constant vector value for v128.const",
},
{
name: "i32x4.add operand",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecI32x4Add,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureSIMD,
expectedErr: "cannot pop the operand for i32x4.add: v128 missing",
},
{
name: "i64x2.add operand",
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128Const,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
OpcodeVecPrefix,
OpcodeVecI64x2Add,
OpcodeDrop,
OpcodeEnd,
},
flag: api.CoreFeatureSIMD,
expectedErr: "cannot pop the operand for i64x2.add: v128 missing",
},
{
name: "shuffle lane index not found",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128i8x16Shuffle,
},
expectedErr: "16 lane indexes for v128.shuffle not found",
},
{
name: "shuffle lane index not found",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix,
OpcodeVecV128i8x16Shuffle,
0xff, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
},
expectedErr: "invalid lane index[0] 255 >= 32 for v128.shuffle",
},
}
addExtractOrReplaceLaneOutOfIndexCase := func(op OpcodeVec, lane, laneCeil byte) {
n := VectorInstructionName(op)
tests = append(tests, testCase{
name: n + "/lane index out of range",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix, op, lane,
},
expectedErr: fmt.Sprintf("invalid lane index %d >= %d for %s", lane, laneCeil, n),
})
}
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ExtractLaneS, 16, 16)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ExtractLaneU, 20, 16)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ExtractLaneS, 8, 8)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ExtractLaneU, 8, 8)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI32x4ExtractLane, 4, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF32x4ExtractLane, 4, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI64x2ExtractLane, 2, 2)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF64x2ExtractLane, 2, 2)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI8x16ReplaceLane, 16, 16)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI16x8ReplaceLane, 8, 8)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI32x4ReplaceLane, 4, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecI64x2ReplaceLane, 2, 2)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF32x4ReplaceLane, 10, 4)
addExtractOrReplaceLaneOutOfIndexCase(OpcodeVecF64x2ReplaceLane, 3, 2)
addStoreOrLoadLaneOutOfIndexCase := func(op OpcodeVec, lane, laneCeil byte) {
n := VectorInstructionName(op)
tests = append(tests, testCase{
name: n + "/lane index out of range",
flag: api.CoreFeatureSIMD,
body: []byte{
OpcodeVecPrefix, op,
0, 0, // align and offset.
lane,
},
expectedErr: fmt.Sprintf("invalid lane index %d >= %d for %s", lane, laneCeil, n),
})
}
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load8Lane, 16, 16)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load16Lane, 8, 8)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load32Lane, 4, 4)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Load64Lane, 2, 2)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store8Lane, 16, 16)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store16Lane, 8, 8)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store32Lane, 4, 4)
addStoreOrLoadLaneOutOfIndexCase(OpcodeVecV128Store64Lane, 2, 2)
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{v_v},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
}
err := m.validateFunction(tc.flag, 0, []Index{0}, nil, &Memory{}, nil, nil)
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestDecodeBlockType(t *testing.T) {
t.Run("primitive", func(t *testing.T) {
for _, tc := range []struct {
name string
in byte
exp ValueType
expResultNumInUint64 int
}{
{name: "nil", in: 0x40},
{name: "i32", in: 0x7f, exp: ValueTypeI32, expResultNumInUint64: 1},
{name: "i64", in: 0x7e, exp: ValueTypeI64, expResultNumInUint64: 1},
{name: "f32", in: 0x7d, exp: ValueTypeF32, expResultNumInUint64: 1},
{name: "f64", in: 0x7c, exp: ValueTypeF64, expResultNumInUint64: 1},
{name: "v128", in: 0x7b, exp: ValueTypeV128, expResultNumInUint64: 2},
{name: "funcref", in: 0x70, exp: ValueTypeFuncref, expResultNumInUint64: 1},
{name: "externref", in: 0x6f, exp: ValueTypeExternref, expResultNumInUint64: 1},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
actual, read, err := DecodeBlockType(nil, bytes.NewReader([]byte{tc.in}), api.CoreFeaturesV2)
require.NoError(t, err)
require.Equal(t, uint64(1), read)
require.Equal(t, 0, len(actual.Params))
require.Equal(t, tc.expResultNumInUint64, actual.ResultNumInUint64)
require.Equal(t, 0, actual.ParamNumInUint64)
if tc.exp == 0 {
require.Equal(t, 0, len(actual.Results))
} else {
require.Equal(t, 1, len(actual.Results))
require.Equal(t, tc.exp, actual.Results[0])
}
})
}
})
t.Run("function type", func(t *testing.T) {
types := []*FunctionType{
{},
{Params: []ValueType{ValueTypeI32}},
{Results: []ValueType{ValueTypeI32}},
{Params: []ValueType{ValueTypeF32, ValueTypeV128}, Results: []ValueType{ValueTypeI32}},
{Params: []ValueType{ValueTypeF32, ValueTypeV128}, Results: []ValueType{ValueTypeI32, ValueTypeF32, ValueTypeV128}},
}
for index, expected := range types {
actual, read, err := DecodeBlockType(types, bytes.NewReader([]byte{byte(index)}), api.CoreFeatureMultiValue)
require.NoError(t, err)
require.Equal(t, uint64(1), read)
require.Equal(t, expected, actual)
}
})
}
// TestFuncValidation_UnreachableBrTable_NotModifyTypes ensures that we do not modify the
// original function type during the function validation with the presence of unreachable br_table
// targeting the function return label.
func TestFuncValidation_UnreachableBrTable_NotModifyTypes(t *testing.T) {
funcType := &FunctionType{Results: []ValueType{i32, i64}, Params: []ValueType{i32}}
copiedFuncType := &FunctionType{Params: make([]ValueType, len(funcType.Params)),
Results: make([]ValueType, len(funcType.Results))}
copy(copiedFuncType.Results, funcType.Results)
copy(copiedFuncType.Params, funcType.Params)
for _, tc := range []struct {
name string
m *Module
}{
{
name: "on function return",
m: &Module{
TypeSection: []*FunctionType{funcType},
FunctionSection: []Index{0},
CodeSection: []*Code{
{Body: []byte{
OpcodeUnreachable,
// Having br_table in unreachable state.
OpcodeI32Const, 1,
// Setting the destination as labels of index 0 which
// is the function return.
OpcodeBrTable, 2, 0, 0, 0,
OpcodeEnd,
}},
},
},
},
{
name: "on loop return",
m: &Module{
TypeSection: []*FunctionType{funcType},
FunctionSection: []Index{0},
CodeSection: []*Code{
{Body: []byte{
OpcodeUnreachable,
OpcodeLoop, 0, // indicates that loop has funcType as its block type
OpcodeUnreachable,
// Having br_table in unreachable state.
OpcodeI32Const, 1,
// Setting the destination as labels of index 0 which
// is the loop return.
OpcodeBrTable, 2, 0, 0, 0,
OpcodeEnd, // End of loop
OpcodeEnd,
}},
},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := tc.m.validateFunction(api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil)
require.NoError(t, err)
// Ensures that funcType has remained intact.
require.Equal(t, copiedFuncType, funcType)
})
}
}
func TestModule_funcValidation_loopWithParams(t *testing.T) {
tests := []struct {
name string
body []byte
expErr string
}{
{
name: "br",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeBr, 0,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br_if",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeI32Const, 1, // operand for br_if
OpcodeBrIf, 0,
OpcodeUnreachable,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br_table",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeI32Const, 4, // Operand for br_table.
OpcodeBrTable, 2, 0, 0, 0, 0, // Jump into the loop header anyway.
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br_table - nested",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeLoop, 1, // loop (param i32)
OpcodeI32Const, 4, // Operand for br_table.
OpcodeBrTable, 2, 0, 1, 0, // Jump into the loop header anyway.
OpcodeEnd,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
},
{
name: "br / mismatch",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
OpcodeDrop,
OpcodeBr, 0, // trying to jump the loop head after dropping the value which causes the type mismatch.
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
expErr: `not enough results in br block
have ()
want (i32)`,
},
{
name: "br_if / mismatch",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
// Use up the param for the br_if, therefore at the time of jumping, we don't have any param to the header.
OpcodeBrIf, 0, // trying to jump the loop head after dropping the value which causes the type mismatch.
OpcodeUnreachable,
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
expErr: `not enough results in br_if block
have ()
want (i32)`,
},
{
name: "br_table",
body: []byte{
OpcodeI32Const, 1,
OpcodeLoop, 1, // loop (param i32)
// Use up the param for the br_table, therefore at the time of jumping, we don't have any param to the header.
OpcodeBrTable, 2, 0, 0, 0, // Jump into the loop header anyway.
OpcodeEnd,
OpcodeUnreachable,
OpcodeEnd,
},
expErr: `not enough results in br_table block
have ()
want (i32)`,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
m := &Module{
TypeSection: []*FunctionType{
v_i32,
i32_v,
},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: tc.body}},
}
err := m.validateFunction(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
if tc.expErr != "" {
require.EqualError(t, err, tc.expErr)
} else {
require.NoError(t, err)
}
})
}
}