Skip to content

Fix validation of typed select with empty result types (#2708) - #2810

Open
ANAMASGARD wants to merge 4 commits into
WebAssembly:mainfrom
ANAMASGARD:fix/2708-select-type-validation
Open

Fix validation of typed select with empty result types (#2708)#2810
ANAMASGARD wants to merge 4 commits into
WebAssembly:mainfrom
ANAMASGARD:fix/2708-select-type-validation

Conversation

@ANAMASGARD

Copy link
Copy Markdown

Fixes #2708

Summary

  • Reject SelectT with zero result types in the binary reader instead of treating it as untyped select
  • Reject WAT select (result) with an empty result list during validation
  • Add regression tests for the binary and WAT cases

Test plan

  • python3 test/run-tests.py binary/bad-select-empty-result parse/expr/bad-select-empty-result spec/select
  • ./out/wabt-unittests
  • python3 test/run-tests.py (full suite)
  • Manual repro: invalid SelectT + empty type vector rejected by wasm-interp and wasm-validate

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 WebAssembly#2708.

Signed-off-by: Gaurav Chaudhary <chaudharygaurav2004@gmail.com>
Signed-off-by: Gaurav Chaudhary <chaudharygaurav2004@gmail.com>
@sbc100
sbc100 requested a review from zherczeg August 7, 2026 16:46
@sbc100

sbc100 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Does this mean we are lacking a test in the spec repo for this?

Comment thread include/wabt/ir.h Outdated
SelectExpr(const Location& loc = Location())
: ExprMixin<ExprType::Select>(loc) {}
TypeVector result_type;
bool has_result_type = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use the zero-length result_type of imply that the expr has not result type?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbc100 empty result_type can mean either “untyped select” or “invalid typed select with no types” — has_result_type distinguishes those, so I don’t think we can drop it without losing that check. (Not part of this PR though.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do does has_result_type = false correspond to "untyped select".

Maybe a comment here explaining the distinction and why this member is needed?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of "untyped select"? This likely rare case could be represented by the void type.
Btw if "supporting untyped select" is not part of the PR, we should not introduce this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbc100 & @zherczeg Thanks for the feedback .
I removed has_result_type and now use {Type::Void} to represent the existing untyped select. An empty result_type now represents invalid typed select (result), and I added a comment documenting the distinction.

@ANAMASGARD

Copy link
Copy Markdown
Author

@sbc100 the spec repo already has tests for this in the GC proposal files (like struct/array fields with (ref N)). WABT just doesn’t run those GC spec tests yet — they’re marked unimplemented in our test script. So the tests exist upstream, but we weren’t exercising them in WABT, which is why I added regress-2750.txt for this parser bug.

Comment thread test/binary/bad-select-empty-result.txt Outdated
i32.const 100
i32.const 1
0x1c
0x00

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these two magic numbers?

I think you can document by using the name_of_thing[0x1x] syntax maybe?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbc100 thanks for the feedback I have replaced the raw bytes with select_t[0x1c] and result_count[0x00] to make the test clearer.

@sbc100

sbc100 commented Aug 7, 2026

Copy link
Copy Markdown
Member

@sbc100 the spec repo already has tests for this in the GC proposal files (like struct/array fields with (ref N)). WABT just doesn’t run those GC spec tests yet — they’re marked unimplemented in our test script. So the tests exist upstream, but we weren’t exercising them in WABT, which is why I added regress-2750.txt for this parser bug.

But what about the non-GC tests.. if we can write tests for this where in wabt that don't depend on GC types then presumably one could write the same non-GC upstream in the spec repo?

@sbc100

sbc100 commented Aug 7, 2026

Copy link
Copy Markdown
Member

@tlively does this looks like a test case that is missing upstream in the spec maybe?

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]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if the results are not consumed, the condition i32 should be.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zherczeg I have Fixed it . We now report the arity error but still run OnSelect, so the condition and operands are consumed correctly. I also updated the test with drop.

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 <chaudharygaurav2004@gmail.com>
@ANAMASGARD

Copy link
Copy Markdown
Author

@sbc100 I am sorry I mixed this up with another issue earlier . There is already an $arity-0 test in select.wast, but it uses nop operands, so WABT can fail for a stack mismatch instead of the missing arity check. A test with valid operands would catch this bug directly. Happy to follow up upstream if needed.

@ANAMASGARD
ANAMASGARD requested review from sbc100 and zherczeg August 8, 2026 03:39

@zherczeg zherczeg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this code much better.

Comment thread src/binary-reader-ir.cc Outdated
auto expr_ptr = std::make_unique<SelectExpr>();
expr_ptr->result_type.assign(result_types, result_types + result_count);
if (result_count == 0) {
expr_ptr->result_type = {Type::Void};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need {}? expr_ptr->result_type = Type::Void; should work

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zherczeg since SelectExpr already defaults to {Type::Void}, I simplified it to only assign when result_count != 0.

Comment thread src/validator.cc Outdated
result_ |= Result::Error;
result_count = 0;
} else if (expr->IsUntyped()) {
result_count = 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Index result_count = 0; Could simplify this code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @zherczeg I have simplified this by initializing result_count to 0.

Comment thread src/wat-writer.cc Outdated
writer_->WritePutsSpace(Opcode::Select_Opcode.GetName());
if (!expr->result_type.empty()) {
if (expr->IsUntyped()) {
// no result annotation

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually don't prefer empty statements.

if (expr->result_type.empty()) { ... } else if (!expr->IsUntyped()) { ... }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Updated it to avoid the empty branch.

i32.const 100
i32.const 1
select (result)
drop))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not this be drop drop? Only the i32.const 1 should be consumed if I remember correctly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zherczeg You're right. Only the i32 condition should be consumed here. I updated the recovery logic and changed the test to drop drop.

Simplify select IR handling and consume only the i32 condition during
recovery for invalid select (result).

Signed-off-by: Gaurav Chaudhary <chaudharygaurav2004@gmail.com>

@zherczeg zherczeg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing Type Validation for select Instruction

3 participants