Skip to content

[IRBuilder] Fix switch type with null continuation - #9328

Merged
tlively merged 1 commit into
mainfrom
ir-builder-stack-switch-bottom-cont
Oct 30, 2026
Merged

tlively merged 1 commit into
mainfrom
ir-builder-stack-switch-bottom-cont

Conversation

@tlively

@tlively tlively commented Oct 29, 2026

Copy link
Copy Markdown
Member

When IRBuilder::makeStackSwitch constructed a StackSwitch expression via Builder::makeStackSwitch, it did not pass the continuation type or initialize ret->type prior to calling ret->finalize(). When the continuation operand had a bottom reference type, StackSwitch::finalize() called getMaximallyUninhabitable(type) while type was still default-constructed Type::none, leaving the StackSwitch with Type::none instead of the uninhabitable refinement of the target continuation's return continuation parameters. This incorrect typing would cause parse failures on later instructions.

Although the problem primarily appears when parsing WAT we would never print (specifically switch with a null parameter), the fuzzer can also observe it when round-tripping a binary after Stack IR optimizations elide a local that was holding the null value.

Fix the problem by passing the continuation type to Builder::makeStackSwitch and initializing ret->type from the last parameter's continuation signature before calling ret->finalize(). Add validation in IRBuilder::makeStackSwitch that the last parameter of the switch continuation is a continuation type to avoid assertion failures or UB.

When `IRBuilder::makeStackSwitch` constructed a StackSwitch expression via `Builder::makeStackSwitch`, it did not pass the continuation type or initialize `ret->type` prior to calling `ret->finalize()`. When the continuation operand had a bottom reference type, `StackSwitch::finalize()` called `getMaximallyUninhabitable(type)` while `type` was still default-constructed `Type::none`, leaving the `StackSwitch` with `Type::none` instead of the uninhabitable refinement of the target continuation's return continuation parameters. This incorrect typing would cause parse failures on later instructions.

Although the problem primarily appears when parsing WAT we would never print (specifically switch with a null parameter), the fuzzer can also observe it when round-tripping a binary after Stack IR optimizations elide a local that was holding the null value.

Fix the problem by passing the continuation type to `Builder::makeStackSwitch` and initializing `ret->type` from the last parameter's continuation signature before calling `ret->finalize()`. Add validation in `IRBuilder::makeStackSwitch` that the last parameter of the switch continuation is a continuation type to avoid assertion failures or UB.
@tlively
tlively requested a review from a team as a code owner October 29, 2026 22:41
@tlively
tlively requested review from kripken and removed request for a team October 29, 2026 22:41

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates the stack switching implementation to compute the result type of a StackSwitch from its continuation type immediate, preventing issues when the continuation is null. It also adds validation to ensure the last parameter of a switch continuation is a continuation type, along with corresponding tests. The review feedback highlights potential assertion failures or undefined behavior if basic continuation types (such as nocont) are passed to getContinuation(). It is recommended to check if the heap types are basic and handle them safely by either setting the type to Type::unreachable or returning an error.

Comment thread src/wasm-builder.h
Comment on lines +1448 to +1453
auto params = contType.getContinuation().type.getSignature().params;
assert(params.size() > 0);
auto lastParam = params[params.size() - 1];
assert(lastParam.isContinuation());
ret->type =
lastParam.getHeapType().getContinuation().type.getSignature().params;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If contType or lastParam is a basic continuation type (such as nocont), calling getContinuation() on it will result in an assertion failure or undefined behavior because basic heap types do not have a continuation signature. We should check if the heap type is basic and handle it safely by setting the type to Type::unreachable.

    if (contType.isBasic()) {
      ret->type = Type::unreachable;
    } else {
      auto params = contType.getContinuation().type.getSignature().params;
      assert(params.size() > 0);
      auto lastParam = params[params.size() - 1];
      assert(lastParam.isContinuation());
      if (lastParam.getHeapType().isBasic()) {
        ret->type = Type::unreachable;
      } else {
        ret->type =
          lastParam.getHeapType().getContinuation().type.getSignature().params;
      }
    }

Comment on lines +2961 to +2962
auto params = ct.getContinuation().type.getSignature().params;
auto nparams = params.size();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If ct is a basic continuation type (such as nocont), calling getContinuation() on it will cause an assertion failure or undefined behavior. We should validate that ct is not a basic type before retrieving its continuation signature.

  if (ct.isBasic()) {
    return Err{"expected non-basic continuation type annotation on switch"};
  }
  auto params = ct.getContinuation().type.getSignature().params;
  auto nparams = params.size();

Comment thread src/wasm-builder.h
ret->cont = cont;
// Compute the result type from the type immediate so
// `getMaximallyUninhabitable` called from `finalize` has a valid starting
// point in case `cont` is null.

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 we do this kind of thing in other parts of wasm-builder? This looks a lot more complex than I am used to seeing in this file.

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.

How about passing in the type of the last param, which I think the caller has?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmmm maybe. The current implementation is consistent with makeResume and makeResumeThrow in that it takes the type of the continuation being passed to the instruction. It's also consistent with all makeXYZNew instructions in that it takes the same type that appears as the type immediate on the corresponding Wasm instruction. The complexity here comes from the inherent complexity of figuring out switch's result type from its type immediates, so is not necessarily unexpected or a problem.

OTOH, makeStructGet and makeArrayGet just take their expected result types as parameters. They don't take parameters corresponding to their Wasm type immediates and they don't do any kind of lookup at all. We could update all the stack switching builder methods to do the same. If you prefer that, I could do it as a preliminary PR.

WDYT?

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.

Yeah, I guess this complexity is reasonable, given the spec. sgtm as is.

@tlively
tlively merged commit c384852 into main Oct 30, 2026
16 of 17 checks passed
@tlively
tlively deleted the ir-builder-stack-switch-bottom-cont branch October 30, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants