Skip to content

Tests: Reproducer for Swift 6.3 typed-throws async closure miscompile - #12

Draft
krodak wants to merge 2 commits into
kr/closures-throws-asyncfrom
kr/typed-throws-async-closure-repro
Draft

Tests: Reproducer for Swift 6.3 typed-throws async closure miscompile#12
krodak wants to merge 2 commits into
kr/closures-throws-asyncfrom
kr/typed-throws-async-closure-repro

Conversation

@krodak

@krodakkrodak commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Overview

A standalone, runnable reproducer for the Swift compiler bug that blocks the Swift-to-JS async throws reject path in the closures work (the "Known limitation" in the feature PR). This branch is stacked on top of that feature branch so reviewers see only the reproducer test in the diff.

Root cause identified: this is swiftlang/swift#89320 ("Crash with typed/untyped throwing async functions on Wasm"), with a fix in progress in swiftlang/swift#89715 (IRGen: fix async typed throws miscompiles on Wasm). On wasm32, IRGen places the indirect typed-error pointer after swiftself at thick call sites, but a captureless closure or function reference goes through thin_to_thick_function whose thin signature has no swiftself; LLVM's wasm swiftcc padding appends the missing parameters, producing equal-arity but permuted signatures. The callee writes the thrown error through the caller's null swiftself, and the caller reads a never-written slot.

The bug triggers only when both conditions hold:

  1. the typed error is too large for the direct error convention (roughly >16 bytes on wasm32; JSException is ~36 bytes), and
  2. the closure value was produced by thin_to_thick_function (a captureless closure literal or a function reference).

Tests/BridgeJSRuntimeTests/TypedThrowsAsyncClosureBugTests.swift demonstrates both conditions with labelled [REPRO] output. The suite stays green: the buggy cases are encoded as current-behaviour assertions with a comment to flip once swiftlang/swift#89715 lands (XCTExpectFailure is not available in the wasm XCTest runtime).

What it shows

[REPRO] jsexc / typed-throws async closure object!=nil: false <- payload lost
[REPRO] large / typed-throws async closure garbage values <- payload lost (no JavaScriptKit types involved)
[REPRO] jsexc / async function object!=nil: true
[REPRO] jsexc / capturing typed-throws closure object!=nil: true <- capturing closures are unaffected
[REPRO] jsexc / untyped async closure object!=nil: true
[REPRO] value / typed-throws async closure payload: ALIVE <- small errors ride the direct convention
[REPRO] ref / typed-throws async closure payload: ALIVE
caseerror sizecallee shaperesult
JSException from captureless async closure~36 Bthin_to_thickLOST
32-byte plain-Swift error from captureless async closure32 Bthin_to_thickLOST
JSException from async function~36 Bdirectpreserved
JSException from capturing async closure~36 Bpartial applypreserved
JSException from untyped async closure~36 Buntyped conventionpreserved
small struct / class-reference errors4-12 Bthin_to_thickpreserved

Two additional observations that raise severity beyond the trap documented in swiftlang/swift#89320:

  • the corruption is silent: the caller observes a zeroed or stale-garbage error value instead of trapping;
  • the garbage is dangerous to even read: a corrupted error carrying a String field trapped with UnsafeBufferPointer has a nil start and nonzero count during interpolation, and releasing corrupted references crashes in swift_release.

Relationship to existing Swift issues

Purpose

Internal review artifact to confirm the characterization before coordinating with the maintainers on the preferred path (merge with the documented limitation, box JSException storage to fit the direct error convention, or wait for swiftlang/swift#89715). Run with make unittest.

@krodak
krodakforce-pushed the kr/closures-throws-async branch from dad4c01 to 746c781CompareJune 10, 2026 08:47
@krodak
krodakforce-pushed the kr/typed-throws-async-closure-repro branch from 24e91ea to c98c67fCompareJune 10, 2026 08:47
@krodak
krodakforce-pushed the kr/closures-throws-async branch from 746c781 to 09bb61cCompareJune 10, 2026 09:45
@krodak
krodakforce-pushed the kr/typed-throws-async-closure-repro branch from c98c67f to 351fd63CompareJune 10, 2026 09:45
@krodak
krodakforce-pushed the kr/closures-throws-async branch from 09bb61c to 30a9c43CompareJune 10, 2026 10:34
@krodak
krodakforce-pushed the kr/typed-throws-async-closure-repro branch from 351fd63 to a52271aCompareJune 10, 2026 10:34
@krodak
krodakforce-pushed the kr/closures-throws-async branch from 30a9c43 to aa02b44CompareJune 10, 2026 18:10
@krodak
krodakforce-pushed the kr/typed-throws-async-closure-repro branch from a52271a to a628444CompareJune 10, 2026 18:10
@krodak
krodakforce-pushed the kr/closures-throws-async branch from aa02b44 to 982e9fbCompareJune 10, 2026 18:30
@krodak
krodakforce-pushed the kr/typed-throws-async-closure-repro branch from a628444 to 1e50386CompareJune 10, 2026 18:30
@krodak
krodakforce-pushed the kr/closures-throws-async branch from 982e9fb to 142d0c7CompareJune 11, 2026 07:24
@krodak
krodakforce-pushed the kr/typed-throws-async-closure-repro branch from 1e50386 to 19dbb08CompareJune 11, 2026 07:24
@krodak
krodakforce-pushed the kr/closures-throws-async branch from 142d0c7 to c020734CompareJune 11, 2026 09:42
Sign up for freeto 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.

1 participant

@krodak