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(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max+1, nil, bytes.NewReader(nil)) require.NoError(t, err) }) t.Run("exceed", func(t *testing.T) { err := m.validateFunctionWithMaxStackValues(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max, nil, bytes.NewReader(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(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureSignExtensionOps, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureNonTrappingFloatToIntConversion, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(nil)) require.EqualError(t, err, tc.expectedErrOnDisable) }) t.Run("enabled", func(t *testing.T) { err := tc.module.validateFunction(&stacks{}, api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureBulkMemoryOperations, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, bytes.NewReader(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(&stacks{}, tc.flag, 0, []Index{0}, nil, tc.memory, tc.tables, nil, bytes.NewReader(nil)) require.EqualError(t, err, tc.expectedErr) }) } }) } var ( f32, f64, i32, i64, v128, externref = ValueTypeF32, ValueTypeF64, ValueTypeI32, ValueTypeI64, ValueTypeV128, ValueTypeExternref f32i32_v = initFt([]ValueType{f32, i32}, nil) f64f32_i64 = initFt([]ValueType{f64, f32}, []ValueType{i64}) f64i32_v128i64 = initFt([]ValueType{f64, i32}, []ValueType{v128, i64}) i32_i32 = initFt([]ValueType{i32}, []ValueType{i32}) i32f64_v = initFt([]ValueType{i32, f64}, nil) i32i32_i32 = initFt([]ValueType{i32, i32}, []ValueType{i32}) i32_v = initFt([]ValueType{i32}, nil) v_v = FunctionType{} v_f32 = initFt(nil, []ValueType{f32}) v_f32f32 = initFt(nil, []ValueType{f32, f32}) v_f64i32 = initFt(nil, []ValueType{f64, i32}) v_f64f64 = initFt(nil, []ValueType{f64, f64}) v_i32 = initFt(nil, []ValueType{i32}) v_i32i32 = initFt(nil, []ValueType{i32, i32}) v_i32i64 = initFt(nil, []ValueType{i32, i64}) v_i64i64 = initFt(nil, []ValueType{i64, i64}) ) func initFt(params, results []ValueType) FunctionType { ft := FunctionType{Params: params, Results: results} ft.CacheNumInUint64() return ft } // TestModule_ValidateFunction_MultiValue_TypeMismatch 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(&stacks{}, api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeFuncref}}, nil, bytes.NewReader(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(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeExternref}}, nil, bytes.NewReader(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(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, nil, tc.declaredFunctionIndexes, bytes.NewReader(nil)) 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(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, tables, nil, bytes.NewReader(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(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, tables, nil, bytes.NewReader(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(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureSIMD, 0, []Index{0}, nil, &Memory{}, nil, nil, bytes.NewReader(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(&stacks{}, tc.flag, 0, []Index{0}, nil, &Memory{}, nil, nil, bytes.NewReader(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 := range types { expected := &types[index] 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(&stacks{}, api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil, bytes.NewReader(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(&stacks{}, api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil, bytes.NewReader(nil)) if tc.expErr != "" { require.EqualError(t, err, tc.expErr) } else { require.NoError(t, err) } }) } } // TestFunctionValidation_redundantEnd is found in th validation fuzzing #879. func TestFunctionValidation_redundantEnd(t *testing.T) { m := &Module{ TypeSection: []FunctionType{{}}, FunctionSection: []Index{0}, CodeSection: []Code{{Body: []byte{OpcodeEnd, OpcodeEnd}}}, } err := m.validateFunction(&stacks{}, api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil, bytes.NewReader(nil)) require.EqualError(t, err, "redundant End instruction at 0x1") } // TestFunctionValidation_redundantEnd is found in th validation fuzzing. func TestFunctionValidation_redundantElse(t *testing.T) { m := &Module{ TypeSection: []FunctionType{{}}, FunctionSection: []Index{0}, CodeSection: []Code{{Body: []byte{OpcodeEnd, OpcodeElse}}}, } err := m.validateFunction(&stacks{}, api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil, bytes.NewReader(nil)) require.EqualError(t, err, "redundant Else instruction at 0x1") } func Test_SplitCallStack(t *testing.T) { oneToEight := []uint64{1, 2, 3, 4, 5, 6, 7, 8} tests := []struct { name string ft *FunctionType stack, expectedParams, expectedResults []uint64 expectedErr string }{ { name: "v_v", ft: &v_v, stack: oneToEight, expectedParams: nil, expectedResults: nil, }, { name: "v_v - stack nil", ft: &v_v, expectedParams: nil, expectedResults: nil, }, { name: "v_i32", ft: &v_i32, stack: oneToEight, expectedParams: nil, expectedResults: []uint64{1}, }, { name: "f32i32_v", ft: &f32i32_v, stack: oneToEight, expectedParams: []uint64{1, 2}, expectedResults: nil, }, { name: "f64f32_i64", ft: &f64f32_i64, stack: oneToEight, expectedParams: []uint64{1, 2}, expectedResults: []uint64{1}, }, { name: "f64i32_v128i64", ft: &f64i32_v128i64, stack: oneToEight, expectedParams: []uint64{1, 2}, expectedResults: []uint64{1, 2, 3}, }, { name: "not enough room for params", ft: &f64i32_v128i64, stack: oneToEight[0:1], expectedErr: "need 2 params, but stack size is 1", }, { name: "not enough room for results", ft: &f64i32_v128i64, stack: oneToEight[0:2], expectedErr: "need 3 results, but stack size is 2", }, } for _, tt := range tests { tc := tt t.Run(tc.name, func(t *testing.T) { params, results, err := SplitCallStack(tc.ft, tc.stack) if tc.expectedErr != "" { require.EqualError(t, err, tc.expectedErr) } else { require.Equal(t, tc.expectedParams, params) require.Equal(t, tc.expectedResults, results) } }) } }