Files
wazero/internal/wasm/func_validation_test.go
Takeshi Yoneda 72f16d21eb Adds support for multi tables (#517)
This commit adds support for multiple tables per module.
Notably, if the WithFeatureReferenceTypes is enabled,
call_indirect, table.init and table.copy instructions
can reference non-zero indexed tables.

part of #484

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
2022-05-03 11:38:51 +09:00

2232 lines
79 KiB
Go

package wasm
import (
"fmt"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func TestModule_ValidateFunction_validateFunctionWithMaxStackValues(t *testing.T) {
const max = 100
const valuesNum = max + 1
// Build 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(Features20191205, 0, []Index{0}, nil, nil, nil, max+1)
require.NoError(t, err)
})
t.Run("exceed", func(t *testing.T) {
err := m.validateFunctionWithMaxStackValues(Features20191205, 0, []Index{0}, nil, nil, nil, max)
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(Features20191205, 0, []Index{0}, 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(FeatureSignExtensionOps, 0, []Index{0}, 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(Features20191205, 0, []Index{0}, 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(FeatureNonTrappingFloatToIntConversion, 0, []Index{0}, 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(Features20191205, 0, []Index{0}, nil, nil, nil)
require.EqualError(t, err, tc.expectedErrOnDisable)
})
t.Run("enabled", func(t *testing.T) {
err := tc.module.validateFunction(FeatureMultiValue, 0, []Index{0}, 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(FeatureBulkMemoryOperations, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}})
require.NoError(t, err)
})
}
})
t.Run("errors", func(t *testing.T) {
for _, tc := range []struct {
body []byte
dataSection []*DataSegment
elementSection []*ElementSegment
dataCountSectionNil bool
memory *Memory
tables []*Table
flag Features
expectedErr string
}{
// memory.init
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: FeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.init",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: Features20191205,
expectedErr: `memory.init invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: FeatureBulkMemoryOperations,
dataCountSectionNil: true,
expectedErr: `memory must exist for memory.init`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read data segment index for memory.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 100 /* data section out of range */},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "index 100 out of range of data section(len=1)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "failed to read memory index for memory.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 1},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "memory.init reserved byte must be zero encoded with 1 byte",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryInit, 0, 0},
flag: FeatureBulkMemoryOperations,
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: FeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "cannot pop the operand for memory.init: i32 missing",
},
// data.drop
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
flag: Features20191205,
expectedErr: `data.drop invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
dataCountSectionNil: true,
memory: &Memory{},
flag: FeatureBulkMemoryOperations,
expectedErr: `data.drop requires data count section`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read data segment index for data.drop: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscDataDrop, 100 /* data section out of range */},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
dataSection: []*DataSegment{{}},
expectedErr: "index 100 out of range of data section(len=1)",
},
// memory.copy
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: FeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.copy",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: Features20191205,
expectedErr: `memory.copy invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `failed to read memory index for memory.copy: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "failed to read memory index for memory.copy: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 1},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "memory.copy reserved byte must be zero encoded with 1 byte",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryCopy, 0, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.copy: i32 missing",
},
// memory.fill
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: FeatureBulkMemoryOperations,
memory: nil,
expectedErr: "memory must exist for memory.fill",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: Features20191205,
expectedErr: `memory.fill invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `failed to read memory index for memory.fill: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 1},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: `memory.fill reserved byte must be zero encoded with 1 byte`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscMemoryFill, 0},
flag: FeatureBulkMemoryOperations,
memory: &Memory{},
expectedErr: "cannot pop the operand for memory.fill: i32 missing",
},
// table.init
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
flag: Features20191205,
tables: []*Table{{}},
expectedErr: `table.init invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "failed to read element segment index for table.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 100 /* data section out of range */},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "index 100 out of range of element section(len=1)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "failed to read source table index for table.init: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 10},
flag: FeatureBulkMemoryOperations,
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: FeatureBulkMemoryOperations | FeatureReferenceTypes,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "table of index 10 not found",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 1},
flag: FeatureBulkMemoryOperations | FeatureReferenceTypes,
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: FeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableInit, 0, 0},
flag: FeatureBulkMemoryOperations,
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: FeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "cannot pop the operand for table.init: i32 missing",
},
// elem.drop
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
flag: Features20191205,
tables: []*Table{{}},
expectedErr: `elem.drop invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "failed to read element segment index for elem.drop: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscElemDrop, 100 /* element section out of range */},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
elementSection: []*ElementSegment{{}},
expectedErr: "index 100 out of range of element section(len=1)",
},
// table.copy
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
flag: Features20191205,
tables: []*Table{{}},
expectedErr: `table.copy invalid as feature "bulk-memory-operations" is disabled`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: `failed to read destination table index for table.copy: EOF`,
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 10},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "destination table index must be zero for table.copy as feature \"reference-types\" is disabled",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
flag: FeatureBulkMemoryOperations | FeatureReferenceTypes,
tables: []*Table{{}, {}},
expectedErr: "table of index 3 not found",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 3},
flag: FeatureBulkMemoryOperations | FeatureReferenceTypes,
tables: []*Table{{}, {}, {}, {}},
expectedErr: "failed to read source table index for table.copy: EOF",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 3},
flag: FeatureBulkMemoryOperations, // Multiple tables require FeatureReferenceTypes.
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: FeatureBulkMemoryOperations | FeatureReferenceTypes,
tables: []*Table{{}, {Type: RefTypeFuncref}, {}, {Type: RefTypeExternref}},
expectedErr: "table type mismatch for table.copy: funcref (src) != externref (dst)",
},
{
body: []byte{OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
{
body: []byte{OpcodeI32Const, 0, OpcodeI32Const, 0, OpcodeMiscPrefix, OpcodeMiscTableCopy, 0, 0},
flag: FeatureBulkMemoryOperations,
tables: []*Table{{}},
expectedErr: "cannot pop the operand for table.copy: i32 missing",
},
} {
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)
require.EqualError(t, err, tc.expectedErr)
})
}
})
}
var (
f32, f64, i32, i64 = ValueTypeF32, ValueTypeF64, ValueTypeI32, ValueTypeI64
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 Features
}{
// 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(FeatureMultiValue, 0, []Index{0}, 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(FeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{Type: RefTypeFuncref}})
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(Features20191205, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}})
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(FeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{}, {}})
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(FeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []*Table{{Type: RefTypeExternref}})
require.EqualError(t, err, "table is not funcref type but was externref for call_indirect")
})
}