Skip to content

BrowserWebGpu panics when decoding GPUInternalError #9914

Description

@arcman7

Description

wgpu 30.0.0's BrowserWebGpu error decoder handles GPUValidationError and GPUOutOfMemoryError, but not the WebGPU-specified GPUInternalError. If GPUDevice.popErrorScope() resolves with that third error subclass, wgpu::Error::from_js reaches panic!("Unexpected error") even though wgpu already exposes Error::Internal.

The vendored webgpu_sys subset also omits the generated GpuInternalError binding, so the decoder cannot add the corresponding typed branch without first vendoring/exporting it.

This is separate from the nullable clean-scope binding regression reported at wasm-bindgen/wasm-bindgen#5234.

Repro steps

The fixture is self-contained and MIT-licensed. No product code or other private code is required; the surrounding project repository is private, so the exact method and results are included here.

The deterministic BrowserWebGpu integration test:

  1. requests a real adapter/device and enters the production wgpu bootstrap/error-scope path;
  2. saves and wraps the real GPUDevice.prototype.popErrorScope;
  3. awaits the original browser promise;
  4. substitutes new GPUInternalError("alpine synthetic internal error") for the internal-scope result; and
  5. verifies that the patched Rust decoder produces wgpu::Error::Internal, preserves the message, and does not contain Unexpected error or unreachable.

This synthetic substitution exercises the real browser-native error class, real Promise boundary, and production Rust decoder. It intentionally proves only the returned-value type/mapping boundary; it does not claim to induce or reproduce an organic browser/driver internal fault.

The stock v30.0.0 source has no GpuInternalError branch and therefore deterministically falls through to the panic. The synthetic browser assertion was run against the patched build; the current stock build also encounters the independent clean-null regression before this injected internal result.

The standalone fixture's exact three-row clean-scope control matrix is:

row wgpu source wasm-bindgen / wasm-bindgen-futures / js-sys + web-sys wasm-bindgen CLI observed result
stock-old crates.io 30.0.0 0.2.122 / 0.4.72 / 0.3.99 0.2.122 exactly clean
stock-current crates.io 30.0.0 0.2.126 / 0.4.76 / 0.3.103 0.2.126 panic hook payload exactly Unexpected error plus browser WebAssembly.RuntimeError: unreachable; the exported async Promise remains pending
patched-current locally patched 30.0.0 0.2.126 / 0.4.76 / 0.3.103 0.2.126 exactly clean

The matrix is included to establish the exact runtime and explain why the patched-current row is used to reach the separate synthetic internal-error decoder test. It is not evidence that a browser organically produced GPUInternalError.

Expected vs observed behavior

Expected: a GPUInternalError result is returned as wgpu::Error::Internal and its browser message is preserved.

Observed in stock v30 decoding: the object matches neither of the two handled subclasses and reaches panic!("Unexpected error"). With the local three-part patch, the synthetic result maps to an internal GPU error containing the exact synthetic message and produces no panic, unreachable trap, or unhandled rejection.

Relevant source/spec locations:

Suggested fix:

  1. vendor and export gen_GpuInternalError.rs in the pinned BrowserWebGpu binding subset;
  2. add a dyn_ref::<GpuInternalError>() branch mapping its message to Error::Internal; and
  3. add a browser regression test covering all three WebGPU error subclasses.

Related but not duplicate: #2170 tracks broad historical spec compliance; #4866/#5160 add the input ErrorFilter::Internal; #2299 introduced error scopes; #9090 refreshed the vendored binding subset without this class; and #9683 is an organic producer-side MSL error rather than a Rust returned-value decoder bug.

Temporary workaround: pinning wasm-bindgen/CLI 0.2.122, wasm-bindgen-futures 0.4.72, and js-sys/web-sys 0.3.99 avoids the sibling clean-null regression, but it does not add GPUInternalError mapping. The internal-error path still needs a local wgpu patch or upstream fix.

Extra materials

No screenshots, validation logs, GPU captures, or product assets are required. The failure is at the JavaScript-class/Rust-decoder boundary.

Platform

  • Rust 1.96.0, target wasm32-unknown-unknown
  • wgpu 30.0.0, BrowserWebGpu backend
  • wasm-bindgen 0.2.126
  • wasm-bindgen-futures 0.4.76
  • js-sys/web-sys 0.3.103
  • wasm-bindgen CLI 0.2.126
  • Node 24.18.0
  • Playwright 1.58.2
  • Chromium 145.0.7632.6, Playwright browser revision 1208

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions