Skip to content

fix(wasm): map wasm:// stack frames to registered debug images - #23999

Open
d2anamaria wants to merge 14 commits into
developfrom
ana/fix/wasm-worker-non-streaming
Open

fix(wasm): map wasm:// stack frames to registered debug images#23999
d2anamaria wants to merge 14 commits into
developfrom
ana/fix/wasm-worker-non-streaming

Conversation

@d2anamaria

@d2anamaria d2anamaria commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Buffer-compiled wasm (fetch → arrayBuffer → instantiate, common in workers) shows up in Chrome stacks as wasm://wasm/<name>-<hash> instead of the fetch URL stored as code_file, so frames never link to the debug image and symbolication fails.

Solution

  • At registration, read the wasm name custom section into an internal moduleName on the image. It is stripped before the event is sent.
  • In patchFrames(), when the exact code_file lookup misses, compare the label's module name to the registered images. An image with a parsed moduleName matches on that name only, because V8 builds the label from the name section and nothing else. An image without one is guessed from the fetch URL basename, including the wasm-bindgen _bg.wasm.wasm alias.
  • Accept a match only when every candidate shares one debug_id, since a page and a worker can register the same binary under different URLs.
  • A label counts as hash-only when no -<hash> suffix was stripped, so hex-looking module names such as ed25519 still map.

Decisions

  • Every response body read is tagged with its URL, not only wasm-looking ones. feat(wasm): patch non-streaming load paths #23767 sniffed content type and file extension, which silently dropped modules served as application/octet-stream or from extension-less URLs. A tag is only read back after a WebAssembly compile succeeded, so tagging a non-wasm buffer is never observable. Body reads on responses without a URL are passed through untouched.
  • The browser test loads a new fixture, named.wasm, which is simple.wasm plus a module-name subsection. simple.wasm only carries function names, so Chrome labels it hash-only and the mapping cannot be exercised with it. Code offsets and build_id are unchanged.

Limitations

d2anamaria and others added 10 commits August 31, 2026 23:37
- Patch `Response.prototype.arrayBuffer` and `bytes` to tag wasm buffers with `response.url` in a `WeakMap`
- Hook `WebAssembly.instantiate` and `compile` to use tagged URL to register module
- Skip registration when `instantiate` receives an already-compiled `WebAssembly.Module`
- Split `patchWebAssembly` into response, non-streaming, and streaming setup; guard non-streaming with `nonStreamingPatched`

- Add `patchWebAssembly.test.ts` for fetch → arrayBuffer → instantiate/compile
- Extend `webworker.test.ts` to restore patched globals and assert `instantiate` is hooked
- Chrome may emit `wasm://wasm/<file>-<hash>` for buffer-compiled modules
  (non-streaming / workers) instead of the fetch URL stored as `code_file`
- Exact URL lookup then fails, so frames stay unlinked (`unknown_image`,
  no `debug_meta.images`) even when the module is registered
- Fall back to a unique basename match on page + worker images; rewrite
  `filename` to `code_file` and set `addr_mode`
- Same `code_file` on page and worker counts as one module (worker crash
  while the page also loaded the wasm)
- Do not guess when two different URLs share a filename
- Bare `wasm://` frames still need `instruction_addr` from the JS parser
- Only handles `wasm://wasm/<file>-<hash>`; unnamed `wasm://` hashes and
  other browsers are unchanged
- wasm:// matching required a unique `code_file`, so the same binary
  registered under two URLs (page + worker, CDN vs origin) was skipped
- Uniqueness is now `debug_id` — Symbolicator keys off the build, not URL
- Still skip when two binaries share a filename but differ in `debug_id`;
  Chrome's wasm:// hash cannot tell them apart
- Parse wasm `name` custom section at registration into internal `moduleName`
- Extract `matchSyntheticWasmFilename` — prefer `moduleName`, then URL basename/`_bg` alias
- Accept synthetic matches only when all candidates share one `debug_id`
- Reject hash-only `wasm://wasm/<id>` labels (#23781)
- Strip `moduleName` via `toProtocolDebugImage` before attaching `debug_meta`
- Add `wasmNameSection`, `matchSyntheticWasmFilename`, and `registry` unit tests
- Add wasm module fixtures with `build_id` and optional `name` section
- Extend `processEvent` for bindgen `_bg` alias, ambiguous names, hash-only frames
- Extend `webworker` for worker images matched by `moduleName`
@d2anamaria
d2anamaria requested a review from a team as a code owner September 3, 2026 11:31
@d2anamaria
d2anamaria requested review from Lms24 and msonnb and removed request for a team September 3, 2026 11:31
@d2anamaria
d2anamaria marked this pull request as draft September 3, 2026 11:32
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 28.92 kB - -
@sentry/browser - with treeshaking flags 27.21 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.11 kB - -
@sentry/browser (incl. Tracing) 50.42 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 50.43 kB - -
@sentry/browser (incl. Tracing, Profiling) 53.41 kB - -
@sentry/browser (incl. Tracing, Replay) 89.93 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.05 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 94.64 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 107.65 kB - -
@sentry/browser (incl. Feedback) 46.41 kB - -
@sentry/browser (incl. sendFeedback) 33.97 kB - -
@sentry/browser (incl. FeedbackAsync) 39.08 kB - -
@sentry/browser (incl. Metrics) 29.93 kB - -
@sentry/browser (incl. Logs) 30.2 kB - -
@sentry/browser (incl. Metrics & Logs) 30.87 kB - -
@sentry/react 30.67 kB - -
@sentry/react (incl. Tracing) 52.73 kB - -
@sentry/vue 36.16 kB - -
@sentry/vue (incl. Tracing) 52.66 kB - -
@sentry/svelte 28.93 kB - -
CDN Bundle 30.66 kB - -
CDN Bundle (incl. Tracing) 50.93 kB - -
CDN Bundle (incl. Logs, Metrics) 32.93 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 52.91 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73.62 kB - -
CDN Bundle (incl. Tracing, Replay) 88.48 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 90.43 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 94.55 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 96.55 kB - -
CDN Bundle - uncompressed 90.73 kB - -
CDN Bundle (incl. Tracing) - uncompressed 152.17 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.31 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 158.13 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 226.72 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 271.74 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 277.69 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 285.44 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 291.38 kB - -
@sentry/nextjs (client) 55.05 kB - -
@sentry/sveltekit (client) 50.83 kB - -
@sentry/core/server 37.08 kB - -
@sentry/core/browser 13.66 kB - -
@sentry/node 129.77 kB +0.02% +19 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 81.83 kB - -
@sentry/node - without tracing 89.57 kB +0.04% +30 B 🔺
@sentry/node - without channel injection 108.65 kB +0.03% +22 B 🔺
@sentry/aws-serverless 97.78 kB +0.02% +17 B 🔺
@sentry/cloudflare (withSentry) - minified 203.02 kB - -
@sentry/cloudflare (withSentry) 505.43 kB - -

View base workflow run

@d2anamaria
d2anamaria requested review from Lms24 and andreiborza and removed request for Lms24 and andreiborza September 8, 2026 15:07
@d2anamaria
d2anamaria marked this pull request as ready for review September 9, 2026 10:50
- Remove `nonStreamingPatched` and `responseReadersPatched`; both entry
  points already patch once per realm
- Drop the two `_resetXForTests` exports the flags required
- Tag every response body with its URL instead of sniffing content type
  and file extension, which silently lost debug images
- Document the install-time and call-time guard phases on
  `patchWebAssembly`
- Merge `patchWebAssemblyGuards.test.ts` into `patchWebAssembly.test.ts`
@github-actions

Copy link
Copy Markdown
Contributor

👋 @msonnb — Please review this PR when you get a chance!

@github-actions

Copy link
Copy Markdown
Contributor

👋 @Lms24, @andreiborza — Please review this PR when you get a chance!

…to end

- Match on the parsed name-section module name only; guess from the fetch
  basename only for images without one
- Treat a label as hash-only when no `-<hash>` suffix was stripped, so
  hex-looking module names such as `ed25519` still map
- Drop the unreachable bare `wasm://` branch in patchFrames; Chrome always
  keeps `:wasm-function[N]:0xADDR` in the filename
- Skip tagging body reads of responses without a URL
- Feed unit tests the real Chrome frame shape and add a browser test that
  loads a module from fetched bytes and asserts the frame maps to its image
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.

3 participants