`--translate-to-exnref` / `--emit-exnref` produce invalid IR when a delegate target's unreachable `try` body ends in a none-typed instruction
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 76/100
Hướng nghiên cứu
Read TranslateToExnref::processDelegateTarget in src/passes/TranslateEH.cpp and the existing cases in test/lit/passes/translate-to-exnref.wast. Reproduce the invalid output with the supplied WAT and --translate-to-exnref, then add a regression case covering an unreachable delegate target body ending in a none-typed instruction. Done when the pass produces valid output and both RUN lines in the lit test pass.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
wasm-opt validates the module after running passes, and a pass should not turn a valid module into an invalid one. On the input below, --translate-to-exnref (also reached through --emit-exnref) does: the validator rejects the pass's output with "block with value must not have last element that is none" and wasm-opt aborts with Fatal: error after opts.
Versions: the version_133 release binary (binaryen-version_133-x86_64-linux.tar.gz) and main at 6e5257bd (2026-10-21).
The input is a try that is the target of a delegate, whose body is unreachable and ends in a none-typed instruction ((unreachable) (nop)). In TranslateToExnref::processDelegateTarget (src/passes/TranslateEH.cpp), no br is appended for an unreachable body, and Builder::blockifyWithName then reuses the unnamed body block as the (result exnref) trampoline block. That block still ends in (nop), which is invalid once the block has a value. This code has not changed since the pass was added in #6210.
Reproduction steps
poc.wat:
(module
(func (export "f")
(try $outer
(do
(try
(do)
(delegate $outer))
(unreachable)
(nop))
(catch_all))))
wasm-opt poc.wat -all -o before.wasm
# expected: exit 0
# version_133: exit 0 (the input is valid)
wasm-opt poc.wat --enable-exception-handling --translate-to-exnref -o out.wasm
# expected: exit 0, a valid out.wasm
# version_133: exit 1
# [wasm-validator error in function 0] none == none: block with value must not have last element that is none, on
# (block $outer0 (result exnref)
# (try_table (catch_all_ref $outer0)
# )
# (unreachable)
# (nop)
# )
# Fatal: error after opts
wasm-opt poc.wat -all --emit-exnref -o out.wasm
# expected: exit 0
# version_133: exit 1, same validator error
It does not trigger when:
wasm-opt before.wasm -all --translate-to-exnref -o out.wasm # binary input: the reader drops the dead (nop)
# version_133: exit 0
wasm-opt poc.wat -all -O1 --translate-to-exnref -o out.wasm # -O1 removes the dead code first
# version_133: exit 0
and it does not trigger when the body ends in (unreachable) itself (remove the (nop)).
Proposed Patch
The patch below wraps such a body in a new block instead of reusing it, and adds a lit test. With it, the input above and the full test/lit/passes/translate-to-exnref.wast (both RUN lines) pass.
Applies to main (6e5257bd) with git am: save the block below as fix.patch and run git am fix.patch.
fix.patch
From c9897d52b449323b25a8ad7bd791f1da42ad6781 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alperen=20Kele=C5=9F?= <[email protected]>
Date: Wed, 21 Oct 2026 17:38:20 -0400
Subject: [PATCH] [EH] Fix invalid trampoline block for unreachable delegate
target bodies
When a try that is the target of a delegate has an unreachable body,
TranslateToExnref::processDelegateTarget adds no br to the outer block,
and Builder::blockifyWithName reuses the unnamed body block as the
(result exnref) trampoline block. If that block ends with a none-typed
expression, e.g. `(unreachable) (nop)`, it becomes a block with a value
whose last element is none, and validation after the pass fails with
"block with value must not have last element that is none".
Wrap such a body in a new block instead of reusing it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
src/passes/TranslateEH.cpp | 12 ++++++-
test/lit/passes/translate-to-exnref.wast | 45 ++++++++++++++++++++++++
2 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp
index 23d21e6..94d6060 100644
--- a/src/passes/TranslateEH.cpp
+++ b/src/passes/TranslateEH.cpp
@@ -302,7 +302,17 @@ struct TranslateToExnref : public WalkerPass<PostWalker<TranslateToExnref>> {
auto* brToOuter = curr->body->type == Type::unreachable
? nullptr
: builder.makeBreak(outerBlock->name);
- innerBody = builder.blockifyWithName(curr->body,
+ // An unreachable body can be a block that ends with a none-typed
+ // expression, which cannot end a block with a value, so wrap it instead
+ // of reusing it as the trampoline block.
+ auto* bodyBlock = curr->body->dynCast<Block>();
+ Expression* body = curr->body;
+ if (!brToOuter && bodyBlock && !bodyBlock->name.is() &&
+ !bodyBlock->list.empty() &&
+ bodyBlock->list.back()->type != Type::unreachable) {
+ body = builder.makeBlock(curr->body);
+ }
+ innerBody = builder.blockifyWithName(body,
delegateTrampoline,
brToOuter,
Type(HeapType::exn, Nullable));
diff --git a/test/lit/passes/translate-to-exnref.wast b/test/lit/passes/translate-to-exnref.wast
index b416850..11b0ecb 100644
--- a/test/lit/passes/translate-to-exnref.wast
+++ b/test/lit/passes/translate-to-exnref.wast
@@ -1827,6 +1827,51 @@
)
)
+ ;; CHECK: (func $delegate-target-outer-try-unreachable-ends-with-none (type $1)
+ ;; CHECK-NEXT: (try_table
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l00 (result exnref)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; STACKIR-OPT: (func $delegate-target-outer-try-unreachable-ends-with-none (type $1)
+ ;; STACKIR-OPT-NEXT: try_table
+ ;; STACKIR-OPT-NEXT: block $l00 (result exnref)
+ ;; STACKIR-OPT-NEXT: try_table (catch_all_ref $l00)
+ ;; STACKIR-OPT-NEXT: call $foo
+ ;; STACKIR-OPT-NEXT: end
+ ;; STACKIR-OPT-NEXT: return
+ ;; STACKIR-OPT-NEXT: end
+ ;; STACKIR-OPT-NEXT: throw_ref
+ ;; STACKIR-OPT-NEXT: end
+ ;; STACKIR-OPT-NEXT: unreachable
+ ;; STACKIR-OPT-NEXT: )
+ (func $delegate-target-outer-try-unreachable-ends-with-none
+ ;; Same as above, but the unreachable body ends with a none-typed
+ ;; instruction. The body block should not be reused as the exnref-typed
+ ;; trampoline block, which would be invalid.
+ (try $l0
+ (do
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ (return)
+ (nop)
+ )
+ )
+ )
+
;; CHECK: (func $delegate-target-caller-none (type $1)
;; CHECK-NEXT: (throw_ref
;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref)
--
2.56.0
Notes
- Related, but different bugs in the same pass: #6274, #6518, #6519.
- Found by an automated differential tester (Ephyra); the cause analysis was drafted with AI assistance.
- Ngôn ngữ chính
- WebAssembly
- Star
- 8.7k
- Fork
- 900
- Merge trung bình
- 2 ngày 2 giờ
- Pull request đã merge (30 ngày)
- 101
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của WebAssembly/binaryen
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
WebAssembly/binaryen#9135 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 76/100
WebAssembly/binaryen#9018 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 62/100
WebAssembly/binaryen#9257 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 45/100
WebAssembly/binaryen#9249 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Memory64Lowering: table.get/table.set keep i64 index on lowered table64, output fails validationĐang mở
Độ khó 3/5 Nửa ngày Mức phù hợp với người mới 66/100
WebAssembly/binaryen#9245 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của WebAssembly/binaryen
Issue tương tự
-
Slang input: int8_t4_packed and uint8_t4_packed are copied into CrossGLCó thể đã có người làm @ninglinLiu đã nhận hôm nay. Đang mởarea:frontend backend:slang bug direction:input
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Decompiler: convertCharRef() in xml.y overflows its int4 accumulator on long character referencesCó thể đã có người làm @kaayzouee đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
NationalSecurityAgency/ghidra#10009 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
[Bug] tree-sitter-bash parses `$_` / `${_}` as variable_name instead of special_variable_nameĐang mở
Độ khó 2/5 Dưới một giờ Mức phù hợp với người mới 85/100
MoonshotAI/kimi-code#4367 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
clang:frontend code-cleanup HLSL
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
llvm/llvm-project#235100 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
paritytech/polkavm#442 ·