From ba20b0e63b91fccde78685e512cbf6f39382613a Mon Sep 17 00:00:00 2001 From: Gaurav Chaudhary Date: Fri, 7 Aug 2026 21:13:49 +0530 Subject: [PATCH 1/4] Fix typed select validation for empty result types Reject typed select instructions with an empty result type vector when reading binary modules and validating WAT, while preserving untyped select behavior. Add regression coverage for #2708. Signed-off-by: Gaurav Chaudhary --- include/wabt/ir.h | 1 + src/binary-reader-ir.cc | 1 + src/binary-reader.cc | 11 ++++----- src/validator.cc | 6 +++++ src/wast-parser.cc | 1 + test/binary/bad-select-empty-result.txt | 26 +++++++++++++++++++++ test/parse/expr/bad-select-empty-result.txt | 16 +++++++++++++ 7 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 test/binary/bad-select-empty-result.txt create mode 100644 test/parse/expr/bad-select-empty-result.txt diff --git a/include/wabt/ir.h b/include/wabt/ir.h index dc04577c3f..892008881c 100644 --- a/include/wabt/ir.h +++ b/include/wabt/ir.h @@ -702,6 +702,7 @@ class SelectExpr : public ExprMixin { SelectExpr(const Location& loc = Location()) : ExprMixin(loc) {} TypeVector result_type; + bool has_result_type = false; }; class TableInitExpr : public ExprMixin { diff --git a/src/binary-reader-ir.cc b/src/binary-reader-ir.cc index 232671e32b..384ac6d686 100644 --- a/src/binary-reader-ir.cc +++ b/src/binary-reader-ir.cc @@ -1212,6 +1212,7 @@ Result BinaryReaderIR::OnReturnExpr() { Result BinaryReaderIR::OnSelectExpr(Index result_count, Type* result_types) { auto expr_ptr = std::make_unique(); + expr_ptr->has_result_type = result_count > 0; expr_ptr->result_type.assign(result_types, result_types + result_count); return AppendExpr(std::move(expr_ptr)); } diff --git a/src/binary-reader.cc b/src/binary-reader.cc index 8c28d20b6b..f6d6f22b01 100644 --- a/src/binary-reader.cc +++ b/src/binary-reader.cc @@ -813,6 +813,8 @@ Result BinaryReader::ReadInstructions(Offset end_offset, const char* context) { case Opcode::SelectT: { Index num_results; CHECK_RESULT(ReadCount(&num_results, "num result types")); + ERROR_IF(num_results == 0, + "invalid arity in select instruction: 0."); result_types_.resize(num_results); for (Index i = 0; i < num_results; ++i) { @@ -825,13 +827,8 @@ Result BinaryReader::ReadInstructions(Offset end_offset, const char* context) { result_types_[i] = result_type; } - if (num_results) { - CALLBACK(OnSelectExpr, num_results, result_types_.data()); - CALLBACK(OnOpcodeType, result_types_[0]); - } else { - CALLBACK(OnSelectExpr, 0, NULL); - CALLBACK0(OnOpcodeBare); - } + CALLBACK(OnSelectExpr, num_results, result_types_.data()); + CALLBACK(OnOpcodeType, result_types_[0]); break; } diff --git a/src/validator.cc b/src/validator.cc index 504690ae77..c665d34e7b 100644 --- a/src/validator.cc +++ b/src/validator.cc @@ -589,6 +589,12 @@ Result Validator::OnReturnCallRefExpr(ReturnCallRefExpr* expr) { } Result Validator::OnSelectExpr(SelectExpr* expr) { + if (expr->has_result_type && expr->result_type.empty()) { + validator_.PrintError(expr->loc, + "invalid arity in select instruction: 0."); + result_ |= Result::Error; + return Result::Ok; + } result_ |= validator_.OnSelect(expr->loc, expr->result_type.size(), expr->result_type.data()); return Result::Ok; diff --git a/src/wast-parser.cc b/src/wast-parser.cc index 9272de391b..bc40700afe 100644 --- a/src/wast-parser.cc +++ b/src/wast-parser.cc @@ -2538,6 +2538,7 @@ Result WastParser::ParsePlainInstr(std::unique_ptr* out_expr) { ResolveTypeVector result_type(&expr->result_type); if (options_->features.reference_types_enabled() && PeekMatchLpar(TokenType::Result)) { + expr->has_result_type = true; CHECK_RESULT(ParseResultList(&expr->result_type, &result_type.vars)); } *out_expr = std::move(expr); diff --git a/test/binary/bad-select-empty-result.txt b/test/binary/bad-select-empty-result.txt new file mode 100644 index 0000000000..4eee676f58 --- /dev/null +++ b/test/binary/bad-select-empty-result.txt @@ -0,0 +1,26 @@ +;;; TOOL: run-gen-wasm-bad +;;; ERROR: 1 +;;; RUN: %(wasm-interp)s --enable-all %(temp_file)s.wasm --run-export=run +;;; ERROR3: 1 +magic +version +section(TYPE) { count[1] function params[0] results[1] i32 } +section(FUNCTION) { count[1] type[0] } +section(EXPORT) { count[1] str("run") func_kind 0 } +section(CODE) { + count[1] + func { + locals[0] + i32.const 42 + i32.const 100 + i32.const 1 + 0x1c + 0x00 + end + } +} +(;; STDERR ;;; +0000029: error: invalid arity in select instruction: 0. +0000029: error: invalid arity in select instruction: 0. +0000029: error: invalid arity in select instruction: 0. +;;; STDERR ;;) diff --git a/test/parse/expr/bad-select-empty-result.txt b/test/parse/expr/bad-select-empty-result.txt new file mode 100644 index 0000000000..336905b1b0 --- /dev/null +++ b/test/parse/expr/bad-select-empty-result.txt @@ -0,0 +1,16 @@ +;;; TOOL: wat2wasm +;;; ERROR: 1 +(module + (func + i32.const 42 + i32.const 100 + i32.const 1 + select (result))) +(;; STDERR ;;; +out/test/parse/expr/bad-select-empty-result.txt:8:5: error: invalid arity in select instruction: 0. + select (result))) + ^^^^^^ +out/test/parse/expr/bad-select-empty-result.txt:8:5: error: type mismatch at end of function, expected [] but got [i32, i32, i32] + select (result))) + ^^^^^^ +;;; STDERR ;;) From 435c6ad8bde1ce8b384f91de195bec4c744463d3 Mon Sep 17 00:00:00 2001 From: Gaurav Chaudhary Date: Fri, 7 Aug 2026 21:33:43 +0530 Subject: [PATCH 2/4] Applyed the clang-format formatting fixes. Signed-off-by: Gaurav Chaudhary --- src/binary-reader.cc | 3 +-- src/validator.cc | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/binary-reader.cc b/src/binary-reader.cc index f6d6f22b01..084c8bb377 100644 --- a/src/binary-reader.cc +++ b/src/binary-reader.cc @@ -813,8 +813,7 @@ Result BinaryReader::ReadInstructions(Offset end_offset, const char* context) { case Opcode::SelectT: { Index num_results; CHECK_RESULT(ReadCount(&num_results, "num result types")); - ERROR_IF(num_results == 0, - "invalid arity in select instruction: 0."); + ERROR_IF(num_results == 0, "invalid arity in select instruction: 0."); result_types_.resize(num_results); for (Index i = 0; i < num_results; ++i) { diff --git a/src/validator.cc b/src/validator.cc index c665d34e7b..e208b41370 100644 --- a/src/validator.cc +++ b/src/validator.cc @@ -590,8 +590,7 @@ Result Validator::OnReturnCallRefExpr(ReturnCallRefExpr* expr) { Result Validator::OnSelectExpr(SelectExpr* expr) { if (expr->has_result_type && expr->result_type.empty()) { - validator_.PrintError(expr->loc, - "invalid arity in select instruction: 0."); + validator_.PrintError(expr->loc, "invalid arity in select instruction: 0."); result_ |= Result::Error; return Result::Ok; } From 73d0f3b8cea287acb53814be3941a64aeaa78ee2 Mon Sep 17 00:00:00 2001 From: Gaurav Chaudhary Date: Sat, 8 Aug 2026 08:53:40 +0530 Subject: [PATCH 3/4] Address review feedback for typed select empty result validation. Use Type::Void as the untyped select sentinel instead of has_result_type, fix validator stack recovery, and update regression/spec goldens. Signed-off-by: Gaurav Chaudhary --- include/wabt/ir.h | 9 +++++++-- src/binary-reader-ir.cc | 7 +++++-- src/binary-writer.cc | 2 +- src/validator.cc | 16 ++++++++++++---- src/wast-parser.cc | 2 +- src/wat-writer.cc | 7 ++++++- test/binary/bad-select-empty-result.txt | 4 ++-- test/parse/expr/bad-select-empty-result.txt | 8 +++----- test/spec/function-references/select.txt | 3 +-- test/spec/select.txt | 3 +-- 10 files changed, 39 insertions(+), 22 deletions(-) diff --git a/include/wabt/ir.h b/include/wabt/ir.h index 892008881c..35f6593571 100644 --- a/include/wabt/ir.h +++ b/include/wabt/ir.h @@ -701,8 +701,13 @@ class SelectExpr : public ExprMixin { public: SelectExpr(const Location& loc = Location()) : ExprMixin(loc) {} - TypeVector result_type; - bool has_result_type = false; + // Untyped select is represented by {Type::Void}. An empty result_type + // represents an explicit typed select with zero result types and is invalid. + TypeVector result_type{Type::Void}; + + bool IsUntyped() const { + return result_type.size() == 1 && result_type[0] == Type::Void; + } }; class TableInitExpr : public ExprMixin { diff --git a/src/binary-reader-ir.cc b/src/binary-reader-ir.cc index 384ac6d686..b28eeab1fa 100644 --- a/src/binary-reader-ir.cc +++ b/src/binary-reader-ir.cc @@ -1212,8 +1212,11 @@ Result BinaryReaderIR::OnReturnExpr() { Result BinaryReaderIR::OnSelectExpr(Index result_count, Type* result_types) { auto expr_ptr = std::make_unique(); - expr_ptr->has_result_type = result_count > 0; - expr_ptr->result_type.assign(result_types, result_types + result_count); + if (result_count == 0) { + expr_ptr->result_type = {Type::Void}; + } else { + expr_ptr->result_type.assign(result_types, result_types + result_count); + } return AppendExpr(std::move(expr_ptr)); } diff --git a/src/binary-writer.cc b/src/binary-writer.cc index b2cfa5ad90..93b24a1ccc 100644 --- a/src/binary-writer.cc +++ b/src/binary-writer.cc @@ -1081,7 +1081,7 @@ void BinaryWriter::WriteExpr(const Func* func, const Expr* expr) { break; case ExprType::Select: { auto* select_expr = cast(expr); - if (select_expr->result_type.empty()) { + if (select_expr->IsUntyped()) { WriteOpcode(stream_, Opcode::Select); } else { WriteOpcode(stream_, Opcode::SelectT); diff --git a/src/validator.cc b/src/validator.cc index e208b41370..945fc46850 100644 --- a/src/validator.cc +++ b/src/validator.cc @@ -589,13 +589,21 @@ Result Validator::OnReturnCallRefExpr(ReturnCallRefExpr* expr) { } Result Validator::OnSelectExpr(SelectExpr* expr) { - if (expr->has_result_type && expr->result_type.empty()) { + Index result_count; + Type* result_types = nullptr; + + if (expr->result_type.empty()) { validator_.PrintError(expr->loc, "invalid arity in select instruction: 0."); result_ |= Result::Error; - return Result::Ok; + result_count = 0; + } else if (expr->IsUntyped()) { + result_count = 0; + } else { + result_count = expr->result_type.size(); + result_types = expr->result_type.data(); } - result_ |= validator_.OnSelect(expr->loc, expr->result_type.size(), - expr->result_type.data()); + + result_ |= validator_.OnSelect(expr->loc, result_count, result_types); return Result::Ok; } diff --git a/src/wast-parser.cc b/src/wast-parser.cc index bc40700afe..4fcfd4dbdc 100644 --- a/src/wast-parser.cc +++ b/src/wast-parser.cc @@ -2538,7 +2538,7 @@ Result WastParser::ParsePlainInstr(std::unique_ptr* out_expr) { ResolveTypeVector result_type(&expr->result_type); if (options_->features.reference_types_enabled() && PeekMatchLpar(TokenType::Result)) { - expr->has_result_type = true; + expr->result_type.clear(); CHECK_RESULT(ParseResultList(&expr->result_type, &result_type.vars)); } *out_expr = std::move(expr); diff --git a/src/wat-writer.cc b/src/wat-writer.cc index 14b9e024ca..b6c93db343 100644 --- a/src/wat-writer.cc +++ b/src/wat-writer.cc @@ -977,7 +977,12 @@ Result WatWriter::ExprVisitorDelegate::OnReturnCallRefExpr( Result WatWriter::ExprVisitorDelegate::OnSelectExpr(SelectExpr* expr) { writer_->WritePutsSpace(Opcode::Select_Opcode.GetName()); - if (!expr->result_type.empty()) { + if (expr->IsUntyped()) { + // no result annotation + } else if (expr->result_type.empty()) { + writer_->WriteOpenSpace("result"); + writer_->WriteCloseSpace(); + } else { writer_->WriteTypes(expr->result_type, "result"); } writer_->WriteNewline(NO_FORCE_NEWLINE); diff --git a/test/binary/bad-select-empty-result.txt b/test/binary/bad-select-empty-result.txt index 4eee676f58..06353dc447 100644 --- a/test/binary/bad-select-empty-result.txt +++ b/test/binary/bad-select-empty-result.txt @@ -14,8 +14,8 @@ section(CODE) { i32.const 42 i32.const 100 i32.const 1 - 0x1c - 0x00 + select_t[0x1c] + result_count[0x00] end } } diff --git a/test/parse/expr/bad-select-empty-result.txt b/test/parse/expr/bad-select-empty-result.txt index 336905b1b0..9c84a38f3b 100644 --- a/test/parse/expr/bad-select-empty-result.txt +++ b/test/parse/expr/bad-select-empty-result.txt @@ -5,12 +5,10 @@ i32.const 42 i32.const 100 i32.const 1 - select (result))) + select (result) + drop)) (;; STDERR ;;; out/test/parse/expr/bad-select-empty-result.txt:8:5: error: invalid arity in select instruction: 0. - select (result))) - ^^^^^^ -out/test/parse/expr/bad-select-empty-result.txt:8:5: error: type mismatch at end of function, expected [] but got [i32, i32, i32] - select (result))) + select (result) ^^^^^^ ;;; STDERR ;;) diff --git a/test/spec/function-references/select.txt b/test/spec/function-references/select.txt index 77e04f2482..554f55e796 100644 --- a/test/spec/function-references/select.txt +++ b/test/spec/function-references/select.txt @@ -12,8 +12,7 @@ out/test/spec/function-references/select.wast:364: assert_invalid passed: out/test/spec/function-references/select/select.1.wasm:000001c: error: type mismatch in select, expected [any, any, i32] but got [i32] 000001c: error: OnSelectExpr callback failed out/test/spec/function-references/select.wast:368: assert_invalid passed: - out/test/spec/function-references/select/select.2.wasm:000001c: error: type mismatch in select, expected [any, any, i32] but got [i32] - 000001c: error: OnSelectExpr callback failed + 000001d: error: invalid arity in select instruction: 0. out/test/spec/function-references/select.wast:372: assert_invalid passed: out/test/spec/function-references/select/select.3.wasm:0000027: error: invalid arity in select instruction: 2. 0000027: error: OnSelectExpr callback failed diff --git a/test/spec/select.txt b/test/spec/select.txt index 1418938b88..f242c98c92 100644 --- a/test/spec/select.txt +++ b/test/spec/select.txt @@ -7,8 +7,7 @@ out/test/spec/select.wast:320: assert_invalid passed: out/test/spec/select/select.1.wasm:000001c: error: type mismatch in select, expected [any, any, i32] but got [i32] 000001c: error: OnSelectExpr callback failed out/test/spec/select.wast:324: assert_invalid passed: - out/test/spec/select/select.2.wasm:000001c: error: type mismatch in select, expected [any, any, i32] but got [i32] - 000001c: error: OnSelectExpr callback failed + 000001d: error: invalid arity in select instruction: 0. out/test/spec/select.wast:328: assert_invalid passed: out/test/spec/select/select.3.wasm:0000027: error: invalid arity in select instruction: 2. 0000027: error: OnSelectExpr callback failed From e6d338748139f13f3507651ead45a0d4e32b8eca Mon Sep 17 00:00:00 2001 From: Gaurav Chaudhary Date: Sat, 8 Aug 2026 14:26:01 +0530 Subject: [PATCH 4/4] Address follow-up review feedback for typed select Simplify select IR handling and consume only the i32 condition during recovery for invalid select (result). Signed-off-by: Gaurav Chaudhary --- include/wabt/shared-validator.h | 1 + include/wabt/type-checker.h | 1 + src/binary-reader-ir.cc | 4 +--- src/shared-validator.cc | 6 ++++++ src/type-checker.cc | 4 ++++ src/validator.cc | 14 +++++++------- src/wat-writer.cc | 6 ++---- test/parse/expr/bad-select-empty-result.txt | 1 + 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/wabt/shared-validator.h b/include/wabt/shared-validator.h index c8e78ac96d..91d33d34c8 100644 --- a/include/wabt/shared-validator.h +++ b/include/wabt/shared-validator.h @@ -197,6 +197,7 @@ class SharedValidator { Result OnReturnCallRef(const Location&, Var function_type_var); Result OnReturn(const Location&); Result OnSelect(const Location&, Index result_count, Type* result_types); + Result OnSelectCondition(const Location&); Result OnSimdLaneOp(const Location&, Opcode, uint64_t lane_idx); Result OnSimdLoadLane(const Location&, Opcode, diff --git a/include/wabt/type-checker.h b/include/wabt/type-checker.h index 723e5d90d1..59a6353c6d 100644 --- a/include/wabt/type-checker.h +++ b/include/wabt/type-checker.h @@ -148,6 +148,7 @@ class TypeChecker { Result OnRethrow(Index depth); Result OnReturn(); Result OnSelect(const TypeVector& result_types); + Result OnSelectCondition(); Result OnSimdLaneOp(Opcode, uint64_t); Result OnSimdLoadLane(Opcode, const Limits& limits, uint64_t); Result OnSimdStoreLane(Opcode, const Limits& limits, uint64_t); diff --git a/src/binary-reader-ir.cc b/src/binary-reader-ir.cc index b28eeab1fa..633e182278 100644 --- a/src/binary-reader-ir.cc +++ b/src/binary-reader-ir.cc @@ -1212,9 +1212,7 @@ Result BinaryReaderIR::OnReturnExpr() { Result BinaryReaderIR::OnSelectExpr(Index result_count, Type* result_types) { auto expr_ptr = std::make_unique(); - if (result_count == 0) { - expr_ptr->result_type = {Type::Void}; - } else { + if (result_count != 0) { expr_ptr->result_type.assign(result_types, result_types + result_count); } return AppendExpr(std::move(expr_ptr)); diff --git a/src/shared-validator.cc b/src/shared-validator.cc index 10f977c3aa..fc7f3e3fac 100644 --- a/src/shared-validator.cc +++ b/src/shared-validator.cc @@ -1309,6 +1309,12 @@ Result SharedValidator::OnSelect(const Location& loc, return result; } +Result SharedValidator::OnSelectCondition(const Location& loc) { + Result result = CheckInstr(Opcode::Select, loc); + result |= typechecker_.OnSelectCondition(); + return result; +} + Result SharedValidator::OnSimdLaneOp(const Location& loc, Opcode opcode, uint64_t value) { diff --git a/src/type-checker.cc b/src/type-checker.cc index de097dd238..ada0ab0388 100644 --- a/src/type-checker.cc +++ b/src/type-checker.cc @@ -1061,6 +1061,10 @@ Result TypeChecker::OnSelect(const TypeVector& expected) { return result; } +Result TypeChecker::OnSelectCondition() { + return PopAndCheck1Type(Type::I32, "select"); +} + Result TypeChecker::OnStore(Opcode opcode, const Limits& limits) { return CheckOpcode2(opcode, &limits); } diff --git a/src/validator.cc b/src/validator.cc index 945fc46850..295f1c28d6 100644 --- a/src/validator.cc +++ b/src/validator.cc @@ -589,16 +589,16 @@ Result Validator::OnReturnCallRefExpr(ReturnCallRefExpr* expr) { } Result Validator::OnSelectExpr(SelectExpr* expr) { - Index result_count; - Type* result_types = nullptr; - if (expr->result_type.empty()) { validator_.PrintError(expr->loc, "invalid arity in select instruction: 0."); result_ |= Result::Error; - result_count = 0; - } else if (expr->IsUntyped()) { - result_count = 0; - } else { + result_ |= validator_.OnSelectCondition(expr->loc); + return Result::Ok; + } + + Index result_count = 0; + Type* result_types = nullptr; + if (!expr->IsUntyped()) { result_count = expr->result_type.size(); result_types = expr->result_type.data(); } diff --git a/src/wat-writer.cc b/src/wat-writer.cc index b6c93db343..77ac71aaef 100644 --- a/src/wat-writer.cc +++ b/src/wat-writer.cc @@ -977,12 +977,10 @@ Result WatWriter::ExprVisitorDelegate::OnReturnCallRefExpr( Result WatWriter::ExprVisitorDelegate::OnSelectExpr(SelectExpr* expr) { writer_->WritePutsSpace(Opcode::Select_Opcode.GetName()); - if (expr->IsUntyped()) { - // no result annotation - } else if (expr->result_type.empty()) { + if (expr->result_type.empty()) { writer_->WriteOpenSpace("result"); writer_->WriteCloseSpace(); - } else { + } else if (!expr->IsUntyped()) { writer_->WriteTypes(expr->result_type, "result"); } writer_->WriteNewline(NO_FORCE_NEWLINE); diff --git a/test/parse/expr/bad-select-empty-result.txt b/test/parse/expr/bad-select-empty-result.txt index 9c84a38f3b..da35bb53d8 100644 --- a/test/parse/expr/bad-select-empty-result.txt +++ b/test/parse/expr/bad-select-empty-result.txt @@ -6,6 +6,7 @@ i32.const 100 i32.const 1 select (result) + drop drop)) (;; STDERR ;;; out/test/parse/expr/bad-select-empty-result.txt:8:5: error: invalid arity in select instruction: 0.