Skip to content

Fix invalid tag index when parsing declarative element segments with … - #9093

Open
ArkadySkv wants to merge 1 commit into
WebAssembly:mainfrom
ArkadySkv:fix-wasm-opt-rejects-valid-module-with-invalid-tag-index-on-declared-typed-elem-segments-8540
Open

Fix invalid tag index when parsing declarative element segments with …#9093
ArkadySkv wants to merge 1 commit into
WebAssembly:mainfrom
ArkadySkv:fix-wasm-opt-rejects-valid-module-with-invalid-tag-index-on-declared-typed-elem-segments-8540

Conversation

@ArkadySkv

Copy link
Copy Markdown

Summary

Fix a parser bug where wasm-opt rejected valid modules containing
declarative element segments with GC reftypes, failing with
parse exception: invalid tag index.

Problem

Binaryen's binary reader (WasmBinaryReader::readElementSegments in
src/wasm/wasm-binary.cpp) misread the type field of declarative
element segments (flag 0x07).

In the binary format, a declarative segment is encoded as:

0x07 reftype vec(expr)

where reftype can be a single byte (e.g. nullref) or a prefix byte
plus a heap-type LEB (e.g. 0x63 0x02 for (ref null 2)).

The reader only consumed a single getU32LEB() for the type. When the
reftype was (ref null 2) — i.e. a prefixed heap type — the reader
would consume the prefix byte as if it were the whole type, then
misinterpret the following bytes as the vector length. This misaligned
the stream, and downstream parsing eventually tried to resolve a value
against the tag table, producing:
parse exception:
invalid tag index (at 0:51)

Minimal repro (from the issue):

(module
  (rec
    (type (array i16))
    (type (sub (struct)))
    (type (array (mut nullexternref)))
    (type (sub (func (param i64))))
    (type (array (mut f32)))
    (type (sub (array (mut v128))))
  )
  (elem declare (ref null 2))
  (elem declare (ref null 3) (ref.null 3))
  (elem declare nullref)
  (func (type 3) (param i64))
  (func (type 3) (param i64))
)

wasm-tools validate accepts this module; Binaryen rejects it.

Fix

Apply the same logic that is already used for passive and table-indexed
segments to the declarative case:

If usesExpressions is set (flag 0x07), read a full reftype using
getType().

Otherwise (flag 0x03), read the single-byte elemkind and validate
it is 0 (funcref).

Previously, the declarative branch unconditionally called
getU32LEB() for the type, which works only for flag 0x03.

if (isDeclarative) {
  // Declared segments are needed in wasm text and binary, but not in
  // Binaryen IR; skip over the segment.
  if (usesExpressions) {
    [[maybe_unused]] auto type = getType();
  } else {
    auto elemKind = getU32LEB();
    if (elemKind != 0x0) {
      throwError("Invalid kind (!= funcref(0)) since !usesExpressions.");
    }
  }
  auto num = getU32LEB();
  for (Index i = 0; i < num; i++) {
    if (usesExpressions) {
      readExpression();
    } else {
      getU32LEB();
    }
  }
  continue;
}

Why this approach: The fix is minimal and localized to the
declarative branch. It reuses the existing, correct reftype-reading
path rather than introducing a new helper. Declared segments are
intentionally dropped from Binaryen IR (they are not needed there), so
no IR representation changes are required — only the binary reader
needs to consume the correct number of bytes.

Testing

Manual verification:

Before the fix:

$ ./bin/wasm-opt repro.wasm --all-features -o /dev/null
[parse exception: invalid tag index (at 0:51)]
Fatal: error parsing wasm

After the fix:

$ ./bin/wasm-opt repro.wasm --all-features -o /dev/null
warning: no passes specified, not doing any work

Cross-checked against the reference toolchain:

$ wasm-tools validate repro.wasm
$ wasm-tools print repro.wasm
(module
  (rec
    (type (;0;) (array i16))
    ...
  )
  (elem (;0;) declare (ref null 2))
  (elem (;1;) declare (ref null 3) (ref.null 3))
  (elem (;2;) declare nullref)
  ...
)

wasm-tools accepts the module, confirming the input is spec-compliant.

New regression test: test/lit/binary/gc-elem-declare.wast
exercises --roundtrip on the reproducer, which forces Binaryen to
write and re-read the module through the binary format. Before the
fix, --roundtrip would hit the same invalid tag index error.

$ ./bin/binaryen-lit test/lit/binary/gc-elem-declare.wast -v
PASS: Binaryen lit tests :: binary/gc-elem-declare.wast

Full test suite:

$ python3 check.py lit
...
PASS: Binaryen lit tests :: binary/gc-elem-declare.wast (94 of 994)
...

All lit tests pass.

Related Issues
Fixes #8540.

Follow-up Work
None. Declared segments remain intentionally dropped from Binaryen IR,
so no additional handling is needed beyond correct parsing.

@ArkadySkv
ArkadySkv requested a review from a team as a code owner September 10, 2026 09:44
@ArkadySkv
ArkadySkv requested review from tlively and removed request for a team September 10, 2026 09:44

@tlively tlively left a comment

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.

Thanks!

Comment thread src/wasm/wasm-binary.cpp Outdated
} else {
auto elemKind = getU32LEB();
if (elemKind != 0x0) {
throwError("Invalid kind (!= funcref(0)) since !usesExpressions.");

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.

Please rewrite this error message to be something like "unexpected passive segment elemkind, expected 0, got X". This will be a user-facing error.

Comment thread test/lit/binary/gc-elem-declare.wast Outdated
@@ -0,0 +1,24 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s

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.

Unfortunately --roundtrip won't work to test this because the declarative segments will be discarded after parsing and will not be present in the emitted binary format.

The best way to test this will be with a binary spec test. Can you add one to test/spec? Or even better, add one in the upstream spec tests (if there isn't one already) and we can pull it into Binaryen.

…GC reftypes

The binary reader in readElementSegments() read only a single LEB for
the type of declarative element segments (flag 0x07). For that flag
the type is a full reftype, which may be a prefix byte plus a heap-
type LEB (e.g. 0x63 0x02 for (ref null 2)). This misaligned the
stream and eventually caused a spurious 'invalid tag index' error.

The fix uses the same logic already applied to passive and
hasTableIdx segments: read a full reftype when usesExpressions is
true, and an elemkind (single byte) otherwise.

Fixes WebAssembly#8540.
@ArkadySkv
ArkadySkv force-pushed the fix-wasm-opt-rejects-valid-module-with-invalid-tag-index-on-declared-typed-elem-segments-8540 branch from 3c9b2c3 to 0e92cd6 Compare September 11, 2026 11:05
@ArkadySkv

Copy link
Copy Markdown
Author

@tlively, Thanks for the review. I've rewritten the elemkind error message to include the unexpected value, and replaced the lit test with a binary spec test in test/spec/gc-elem-declare.wast (the --roundtrip approach indeed couldn't reach the binary reader for declarative segments). Let me know if there's an existing upstream test I should point to instead.

;; Before the fix, parsing this module failed with "invalid tag index".
;; See https://github.com/WebAssembly/binaryen/issues/8540

(module binary "\00asm\01\00\00\00\01\1b\01N\06^w\00P\00_\00^r\01P\00`\01~\00^}\01P\00^{\01\03\03\02\03\03\09\0f\03\07c\02\00\07c\03\01\d0\03\0b\07q\00\0a\07\02\02\00\0b\02\00\0b")

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.

Please see the style used in other module binary instances in the test suite. They usually have explanatory comments on the side explaining the meaning of each byte sequence for the benefit of the reader.

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.

wasm-opt rejects valid module with invalid tag index on declared typed elem segments

2 participants