diff --git a/circle.yml b/circle.yml index 72b42a9f7..1d10e0c9a 100644 --- a/circle.yml +++ b/circle.yml @@ -144,6 +144,7 @@ jobs: ./wat2wasm4cpp.py test/unittests/execute_test.cpp ./wat2wasm4cpp.py test/unittests/instantiate_test.cpp ./wat2wasm4cpp.py test/unittests/parser_test.cpp + ./wat2wasm4cpp.py test/unittests/validation_stack_test.cpp ./wat2wasm4cpp.py test/unittests/validation_test.cpp ./wat2wasm4cpp.py test/unittests/wasm_engine_test.cpp ./wat2wasm4cpp.py test/spectests/spectests.cpp diff --git a/lib/fizzy/CMakeLists.txt b/lib/fizzy/CMakeLists.txt index 21b89593a..8acac2b5d 100644 --- a/lib/fizzy/CMakeLists.txt +++ b/lib/fizzy/CMakeLists.txt @@ -6,6 +6,8 @@ target_sources( bytes.hpp execute.cpp execute.hpp + instructions.cpp + instructions.hpp leb128.hpp limits.hpp parser.cpp diff --git a/lib/fizzy/instructions.cpp b/lib/fizzy/instructions.cpp new file mode 100644 index 000000000..eb596a3db --- /dev/null +++ b/lib/fizzy/instructions.cpp @@ -0,0 +1,237 @@ +#include "instructions.hpp" + +namespace fizzy +{ +namespace +{ +constexpr InstructionMetrics instruction_metrics_table[256] = { + // 5.4.1 Control instructions + /* unreachable = 0x00 */ {0, 0}, + /* nop = 0x01 */ {0, 0}, + /* block = 0x02 */ {0, 0}, + /* loop = 0x03 */ {0, 0}, + /* if_ = 0x04 */ {1, -1}, + /* else_ = 0x05 */ {0, 0}, + /* 0x06 */ {}, + /* 0x07 */ {}, + /* 0x08 */ {}, + /* 0x09 */ {}, + /* 0x0a */ {}, + /* end = 0x0b */ {0, 0}, + + // TODO: After br code is unreachable so stack height should be reset (according to the target + // label?) Similarly for return. + /* br = 0x0c */ {0, 0}, + /* br_if = 0x0d */ {1, -1}, + /* br_table = 0x0e */ {1, -1}, + /* return_ = 0x0f */ {0, 0}, + + // TODO: Call require number of stack items equal the number of inputs in the target function + // (call_indirect one more item). They can return 0 or 1 item. + // Here is simplified setup without inspecting function types where we assume zero + // function arguments and one returned value. + /* call = 0x10 */ {0, 1}, + /* call_indirect = 0x11 */ {1, 0}, + + /* 0x12 */ {}, + /* 0x13 */ {}, + /* 0x14 */ {}, + /* 0x15 */ {}, + /* 0x16 */ {}, + /* 0x17 */ {}, + /* 0x18 */ {}, + /* 0x19 */ {}, + + // 5.4.2 Parametric instructions + /* drop = 0x1a */ {1, -1}, + /* select = 0x1b */ {3, -2}, + + /* 0x1c */ {}, + /* 0x1d */ {}, + /* 0x1e */ {}, + /* 0x1f */ {}, + + // 5.4.3 Variable instructions + /* local_get = 0x20 */ {0, 1}, + /* local_set = 0x21 */ {1, -1}, + /* local_tee = 0x22 */ {1, 0}, + /* global_get = 0x23 */ {0, 1}, + /* global_set = 0x24 */ {1, -1}, + + /* 0x25 */ {}, + /* 0x26 */ {}, + /* 0x27 */ {}, + + // 5.4.4 Memory instructions + /* i32_load = 0x28 */ {1, 0}, + /* i64_load = 0x29 */ {1, 0}, + /* f32_load = 0x2a */ {1, 0}, + /* f64_load = 0x2b */ {1, 0}, + /* i32_load8_s = 0x2c */ {1, 0}, + /* i32_load8_u = 0x2d */ {1, 0}, + /* i32_load16_s = 0x2e */ {1, 0}, + /* i32_load16_u = 0x2f */ {1, 0}, + /* i64_load8_s = 0x30 */ {1, 0}, + /* i64_load8_u = 0x31 */ {1, 0}, + /* i64_load16_s = 0x32 */ {1, 0}, + /* i64_load16_u = 0x33 */ {1, 0}, + /* i64_load32_s = 0x34 */ {1, 0}, + /* i64_load32_u = 0x35 */ {1, 0}, + /* i32_store = 0x36 */ {2, -2}, + /* i64_store = 0x37 */ {2, -2}, + /* f32_store = 0x38 */ {2, -2}, + /* f64_store = 0x39 */ {2, -2}, + /* i32_store8 = 0x3a */ {2, -2}, + /* i32_store16 = 0x3b */ {2, -2}, + /* i64_store8 = 0x3c */ {2, -2}, + /* i64_store16 = 0x3d */ {2, -2}, + /* i64_store32 = 0x3e */ {2, -2}, + /* memory_size = 0x3f */ {0, 1}, + /* memory_grow = 0x40 */ {1, 0}, + + // 5.4.5 Numeric instructions + /* i32_const = 0x41 */ {0, 1}, + /* i64_const = 0x42 */ {0, 1}, + /* f32_const = 0x43 */ {0, 1}, + /* f64_const = 0x44 */ {0, 1}, + + /* i32_eqz = 0x45 */ {1, 0}, + /* i32_eq = 0x46 */ {2, -1}, + /* i32_ne = 0x47 */ {2, -1}, + /* i32_lt_s = 0x48 */ {2, -1}, + /* i32_lt_u = 0x49 */ {2, -1}, + /* i32_gt_s = 0x4a */ {2, -1}, + /* i32_gt_u = 0x4b */ {2, -1}, + /* i32_le_s = 0x4c */ {2, -1}, + /* i32_le_u = 0x4d */ {2, -1}, + /* i32_ge_s = 0x4e */ {2, -1}, + /* i32_ge_u = 0x4f */ {2, -1}, + + /* i64_eqz = 0x50 */ {1, 0}, + /* i64_eq = 0x51 */ {2, -1}, + /* i64_ne = 0x52 */ {2, -1}, + /* i64_lt_s = 0x53 */ {2, -1}, + /* i64_lt_u = 0x54 */ {2, -1}, + /* i64_gt_s = 0x55 */ {2, -1}, + /* i64_gt_u = 0x56 */ {2, -1}, + /* i64_le_s = 0x57 */ {2, -1}, + /* i64_le_u = 0x58 */ {2, -1}, + /* i64_ge_s = 0x59 */ {2, -1}, + /* i64_ge_u = 0x5a */ {2, -1}, + + /* f32_eq = 0x5b */ {2, -1}, + /* f32_ne = 0x5c */ {2, -1}, + /* f32_lt = 0x5d */ {2, -1}, + /* f32_gt = 0x5e */ {2, -1}, + /* f32_le = 0x5f */ {2, -1}, + /* f32_ge = 0x60 */ {2, -1}, + + /* f64_eq = 0x61 */ {2, -1}, + /* f64_ne = 0x62 */ {2, -1}, + /* f64_lt = 0x63 */ {2, -1}, + /* f64_gt = 0x64 */ {2, -1}, + /* f64_le = 0x65 */ {2, -1}, + /* f64_ge = 0x66 */ {2, -1}, + + /* i32_clz = 0x67 */ {1, 0}, + /* i32_ctz = 0x68 */ {1, 0}, + /* i32_popcnt = 0x69 */ {1, 0}, + /* i32_add = 0x6a */ {2, -1}, + /* i32_sub = 0x6b */ {2, -1}, + /* i32_mul = 0x6c */ {2, -1}, + /* i32_div_s = 0x6d */ {2, -1}, + /* i32_div_u = 0x6e */ {2, -1}, + /* i32_rem_s = 0x6f */ {2, -1}, + /* i32_rem_u = 0x70 */ {2, -1}, + /* i32_and = 0x71 */ {2, -1}, + /* i32_or = 0x72 */ {2, -1}, + /* i32_xor = 0x73 */ {2, -1}, + /* i32_shl = 0x74 */ {2, -1}, + /* i32_shr_s = 0x75 */ {2, -1}, + /* i32_shr_u = 0x76 */ {2, -1}, + /* i32_rotl = 0x77 */ {2, -1}, + /* i32_rotr = 0x78 */ {2, -1}, + + /* i64_clz = 0x79 */ {1, 0}, + /* i64_ctz = 0x7a */ {1, 0}, + /* i64_popcnt = 0x7b */ {1, 0}, + /* i64_add = 0x7c */ {2, -1}, + /* i64_sub = 0x7d */ {2, -1}, + /* i64_mul = 0x7e */ {2, -1}, + /* i64_div_s = 0x7f */ {2, -1}, + /* i64_div_u = 0x80 */ {2, -1}, + /* i64_rem_s = 0x81 */ {2, -1}, + /* i64_rem_u = 0x82 */ {2, -1}, + /* i64_and = 0x83 */ {2, -1}, + /* i64_or = 0x84 */ {2, -1}, + /* i64_xor = 0x85 */ {2, -1}, + /* i64_shl = 0x86 */ {2, -1}, + /* i64_shr_s = 0x87 */ {2, -1}, + /* i64_shr_u = 0x88 */ {2, -1}, + /* i64_rotl = 0x89 */ {2, -1}, + /* i64_rotr = 0x8a */ {2, -1}, + + /* f32_abs = 0x8b */ {1, 0}, + /* f32_neg = 0x8c */ {1, 0}, + /* f32_ceil = 0x8d */ {1, 0}, + /* f32_floor = 0x8e */ {1, 0}, + /* f32_trunc = 0x8f */ {1, 0}, + /* f32_nearest = 0x90 */ {1, 0}, + /* f32_sqrt = 0x91 */ {1, 0}, + /* f32_add = 0x92 */ {2, -1}, + /* f32_sub = 0x93 */ {2, -1}, + /* f32_mul = 0x94 */ {2, -1}, + /* f32_div = 0x95 */ {2, -1}, + /* f32_min = 0x96 */ {2, -1}, + /* f32_max = 0x97 */ {2, -1}, + /* f32_copysign = 0x98 */ {2, -1}, + + /* f64_abs = 0x99 */ {1, 0}, + /* f64_neg = 0x9a */ {1, 0}, + /* f64_ceil = 0x9b */ {1, 0}, + /* f64_floor = 0x9c */ {1, 0}, + /* f64_trunc = 0x9d */ {1, 0}, + /* f64_nearest = 0x9e */ {1, 0}, + /* f64_sqrt = 0x9f */ {1, 0}, + /* f64_add = 0xa0 */ {2, -1}, + /* f64_sub = 0xa1 */ {2, -1}, + /* f64_mul = 0xa2 */ {2, -1}, + /* f64_div = 0xa3 */ {2, -1}, + /* f64_min = 0xa4 */ {2, -1}, + /* f64_max = 0xa5 */ {2, -1}, + /* f64_copysign = 0xa6 */ {2, -1}, + + /* i32_wrap_i64 = 0xa7 */ {1, 0}, + /* i32_trunc_f32_s = 0xa8 */ {1, 0}, + /* i32_trunc_f32_u = 0xa9 */ {1, 0}, + /* i32_trunc_f64_s = 0xaa */ {1, 0}, + /* i32_trunc_f64_u = 0xab */ {1, 0}, + /* i64_extend_i32_s = 0xac */ {1, 0}, + /* i64_extend_i32_u = 0xad */ {1, 0}, + /* i64_trunc_f32_s = 0xae */ {1, 0}, + /* i64_trunc_f32_u = 0xaf */ {1, 0}, + /* i64_trunc_f64_s = 0xb0 */ {1, 0}, + /* i64_trunc_f64_u = 0xb1 */ {1, 0}, + /* f32_convert_i32_s = 0xb2 */ {1, 0}, + /* f32_convert_i32_u = 0xb3 */ {1, 0}, + /* f32_convert_i64_s = 0xb4 */ {1, 0}, + /* f32_convert_i64_u = 0xb5 */ {1, 0}, + /* f32_demote_f64 = 0xb6 */ {1, 0}, + /* f64_convert_i32_s = 0xb7 */ {1, 0}, + /* f64_convert_i32_u = 0xb8 */ {1, 0}, + /* f64_convert_i64_s = 0xb9 */ {1, 0}, + /* f64_convert_i64_u = 0xba */ {1, 0}, + /* f64_promote_f32 = 0xbb */ {1, 0}, + /* i32_reinterpret_f32 = 0xbc */ {1, 0}, + /* i64_reinterpret_f64 = 0xbd */ {1, 0}, + /* f32_reinterpret_i32 = 0xbe */ {1, 0}, + /* f64_reinterpret_i64 = 0xbf */ {1, 0}, +}; +} // namespace + +const InstructionMetrics* get_instruction_metrics_table() noexcept +{ + return instruction_metrics_table; +} + +} // namespace fizzy diff --git a/lib/fizzy/instructions.hpp b/lib/fizzy/instructions.hpp new file mode 100644 index 000000000..f50c7abe0 --- /dev/null +++ b/lib/fizzy/instructions.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace fizzy +{ +struct InstructionMetrics +{ + /// The minimum number of the stack items required for the instruction. + int8_t stack_height_required; + + /// The stack height change caused by the instruction execution, + /// i.e. stack height _after_ execution - stack height _before_ execution. + int8_t stack_height_change; +}; + +const InstructionMetrics* get_instruction_metrics_table() noexcept; + +} // namespace fizzy diff --git a/lib/fizzy/parser_expr.cpp b/lib/fizzy/parser_expr.cpp index 300a54da5..cac8c5336 100644 --- a/lib/fizzy/parser_expr.cpp +++ b/lib/fizzy/parser_expr.cpp @@ -1,3 +1,4 @@ +#include "instructions.hpp" #include "parser.hpp" #include #include @@ -25,10 +26,17 @@ inline void push(bytes& b, T value) struct ControlFrame { /// The instruction that created the label. - Instr instruction{Instr::unreachable}; + const Instr instruction{Instr::unreachable}; /// The immediates offset for block instructions. - size_t immediates_offset{0}; + const size_t immediates_offset{0}; + + /// The frame stack height. + int stack_height{0}; + + /// Whether the remainder of the block is unreachable (used to handle stack-polymorphic typing + /// after branches). + bool unreachable{false}; }; /// Parses blocktype. @@ -63,7 +71,9 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have // For a block/if/else instruction the value is the block/if/else's immediate offset. std::stack control_stack; - control_stack.push({Instr::block, 0}); // The function's implicit block. + control_stack.push({Instr::block}); // The function's implicit block. + + const auto metrics_table = get_instruction_metrics_table(); bool continue_parsing = true; while (continue_parsing) @@ -71,9 +81,17 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have if (pos == end) throw parser_error{"Unexpected EOF"}; - const auto& frame = control_stack.top(); + auto& frame = control_stack.top(); + + const auto opcode = *pos++; + const auto& metrics = metrics_table[opcode]; + + if (frame.stack_height < metrics.stack_height_required && !frame.unreachable) + throw validation_error{"stack underflow"}; + + frame.stack_height += metrics.stack_height_change; - const auto instr = static_cast(*pos++); + const auto instr = static_cast(opcode); switch (instr) { default: @@ -161,8 +179,11 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have case Instr::f64_reinterpret_i64: case Instr::unreachable: - case Instr::nop: case Instr::return_: + frame.unreachable = true; + break; + + case Instr::nop: case Instr::drop: case Instr::select: case Instr::i32_eq: @@ -243,7 +264,7 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have block_imm += sizeof(target_pc); store(block_imm, target_imm); } - control_stack.pop(); + control_stack.pop(); // Pop the current frame. } else continue_parsing = false; @@ -256,6 +277,9 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have std::tie(arity, pos) = parse_blocktype(pos, end); code.immediates.push_back(arity); + // Parent frame gets additional items on stack after this block exit. + frame.stack_height += arity; + // Push label with immediates offset after arity. control_stack.push({Instr::block, code.immediates.size()}); @@ -267,8 +291,13 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have case Instr::loop: { - std::tie(std::ignore, pos) = parse_blocktype(pos, end); - control_stack.push({Instr::loop, 0}); // Mark as not interested. + uint8_t arity; + std::tie(arity, pos) = parse_blocktype(pos, end); + + // Parent frame gets additional items on stack after this block exit. + frame.stack_height += arity; + + control_stack.push({Instr::loop}); break; } @@ -278,6 +307,9 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have std::tie(arity, pos) = parse_blocktype(pos, end); code.immediates.push_back(arity); + // Parent frame gets additional items on stack after this block exit. + frame.stack_height += arity; + control_stack.push({Instr::if_, code.immediates.size()}); // Placeholders for immediate values, filled at the matching end and else instructions. @@ -294,6 +326,11 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have { if (frame.instruction != Instr::if_) throw parser_error{"unexpected else instruction (if instruction missing)"}; + + // Reset frame after if. The if result type validation not implemented yet. + frame.stack_height = 0; + frame.unreachable = false; + const auto target_pc = static_cast(code.instructions.size() + 1); const auto target_imm = static_cast(code.immediates.size()); @@ -317,6 +354,10 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have throw validation_error{"invalid label index"}; push(code.immediates, label_idx); + + if (instr == Instr::br) + frame.unreachable = true; + break; } @@ -353,6 +394,9 @@ parser_result parse_expr(const uint8_t* pos, const uint8_t* end, bool have for (const auto idx : label_indices) push(code.immediates, idx); push(code.immediates, default_label_idx); + + frame.unreachable = true; + break; } diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 2f57ec4b8..2e3953232 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -23,6 +23,7 @@ target_sources( parser_test.cpp stack_test.cpp utf8_test.cpp + validation_stack_test.cpp validation_test.cpp wasm_engine_test.cpp ) diff --git a/test/unittests/validation_stack_test.cpp b/test/unittests/validation_stack_test.cpp new file mode 100644 index 000000000..75e8ecb63 --- /dev/null +++ b/test/unittests/validation_stack_test.cpp @@ -0,0 +1,343 @@ +#include "parser.hpp" +#include +#include +#include + +using namespace fizzy; + +TEST(validation_stack, func_stack_underflow) +{ + /* wat2wasm --no-check + (func (param i32 i32) (result i32) + get_local 0 + get_local 1 + i32.add + i32.add + ) + */ + const auto wasm = + from_hex("0061736d0100000001070160027f7f017f030201000a0a010800200020016a6a0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, block_stack_underflow) +{ + /* wat2wasm --no-check + (func + i32.const 2 + (block + drop + ) + ) + */ + const auto wasm = from_hex("0061736d01000000010401600000030201000a0a010800410202401a0b0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, block_with_result) +{ + /* wat2wasm + (func + (block (result i32) + i32.const -1 + ) + drop + ) + */ + const auto wasm = from_hex("0061736d01000000010401600000030201000a0a010800027f417f0b1a0b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, block_with_result_stack_underflow) +{ + /* wat2wasm --no-check + (func (result i32) + (block (result i32) + i32.const -1 + ) + i32.add + ) + */ + const auto wasm = from_hex("0061736d010000000105016000017f030201000a0a010800027f417f0b6a0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, loop_stack_underflow) +{ + /* wat2wasm --no-check + (func (param i32) + get_local 0 + (loop + i32.eqz + drop + ) + ) + */ + const auto wasm = from_hex("0061736d0100000001050160017f00030201000a0b01090020000340451a0b0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, loop_with_result) +{ + /* wat2wasm + (func + (loop (result i32) + i32.const -1 + ) + drop + ) + */ + const auto wasm = from_hex("0061736d01000000010401600000030201000a0a010800037f417f0b1a0b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, loop_with_result_stack_underflow) +{ + /* wat2wasm --no-check + (func (result i32) + (loop (result i32) + i32.const -1 + ) + i32.add + ) + */ + const auto wasm = from_hex("0061736d010000000105016000017f030201000a0a010800037f417f0b6a0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, DISABLED_call_stack_underflow) +{ + /* wat2wasm --no-check + (func $f (param i32) (result i32) + get_local 0 + ) + (func (result i32) + ;; Call argument missing. + call $f + ) + */ + const auto wasm = + from_hex("0061736d01000000010a0260017f017f6000017f03030200010a0b02040020000b040010000b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, unreachable) +{ + /* wat2wasm + (func (result i32) + unreachable + i32.eqz + ) + */ + const auto wasm = from_hex("0061736d010000000105016000017f030201000a0601040000450b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, unreachable_2) +{ + /* wat2wasm + (func + unreachable + i32.add + i32.add + i32.add + drop + ) + */ + const auto wasm = from_hex("0061736d01000000010401600000030201000a09010700006a6a6a1a0b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, br) +{ + /* wat2wasm + (func + (block + br 0 + i32.eqz ;; unreachable + drop + ) + ) + */ + const auto wasm = from_hex("0061736d01000000010401600000030201000a0b01090002400c00451a0b0b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, br_table) +{ + /* wat2wasm + (func (param i32) + (block + i32.const 1001 + get_local 0 + br_table 0 1 + i32.mul ;; unreachable + i32.mul + i32.mul + drop + ) + ) + */ + const auto wasm = from_hex( + "0061736d0100000001050160017f00030201000a14011200024041e90720000e0100016c6c6c1a0b0b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, return_) +{ + /* wat2wasm + (func + return + i32.eqz ;; unreachable + drop + ) + */ + const auto wasm = from_hex("0061736d01000000010401600000030201000a070105000f451a0b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, if_stack_underflow) +{ + /* wat2wasm --no-check + (func + (local i64) + i64.const 1 + i32.const 2 + (if + (then + set_local 0 ;; stack underflow + ) + ) + drop + ) + */ + const auto wasm = + from_hex("0061736d01000000010401600000030201000a10010e01017e42014102044021000b1a0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, else_stack_underflow) +{ + /* wat2wasm --no-check + (func + (local i64) + i64.const 1 + i32.const 2 + (if + (then) + (else + set_local 0 ;; stack underflow + ) + ) + drop + ) + */ + const auto wasm = + from_hex("0061736d01000000010401600000030201000a11010f01017e4201410204400521000b1a0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, if_with_result_stack_underflow) +{ + /* wat2wasm --no-check + (func + (local i64) + i64.const 1 + i32.const 2 + (if (result i64) + (then + set_local 0 ;; stack underflow + i64.const -1 + ) + (else + i64.const -2 + ) + ) + drop + drop + ) + */ + const auto wasm = from_hex( + "0061736d01000000010401600000030201000a16011401017e42014102047e2100427f05427e0b1a1a0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, else_with_result_stack_underflow) +{ + /* wat2wasm --no-check + (func + (local i64) + i64.const 1 + i32.const 2 + (if (result i64) + (then + i64.const -1 + ) + (else + set_local 0 ;; stack underflow + i64.const -2 + ) + ) + drop + drop + ) + */ + const auto wasm = from_hex( + "0061736d01000000010401600000030201000a16011401017e42014102047e427f052100427e0b1a1a0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +} + +TEST(validation_stack, if_invalid_end_stack_height) +{ + /* wat2wasm --no-check + (func + i64.const 1 + i32.const 2 + (if (result i64) + (then + i64.const 1 + i64.const 2 ;; Stack height 2, but should be 1. + ) + (else + i64.const 3 + i64.const 4 + drop + ) + ) + drop + drop + ) + */ + const auto wasm = from_hex( + "0061736d01000000010401600000030201000a1701150042014102047e4201420205420342041a0b1a1a0b"); + parse(wasm); + // TODO: Add max stack height check. +} + +TEST(validation_stack, if_with_unreachable) +{ + /* wat2wasm --no-check + (func (param i32) (result i64) + get_local 0 + (if (result i64) + (then + unreachable + i64.const 1 + ) + (else + drop ;; Stack underflow. + ) + ) + ) + */ + const auto wasm = + from_hex("0061736d0100000001060160017f017e030201000a0e010c002000047e004201051a0b0b"); + EXPECT_THROW_MESSAGE(parse(wasm), validation_error, "stack underflow"); +}