Skip to content

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

Merged
timfish merged 17 commits into
developfrom
ana/fix/wasm-worker-non-streaming
Sep 25, 2026
Merged

timfish merged 17 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 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 29.16 kB - -
@sentry/browser - with treeshaking flags 27.42 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.33 kB - -
@sentry/browser (incl. Tracing) 50.62 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 50.64 kB - -
@sentry/browser (incl. Tracing, Profiling) 53.62 kB - -
@sentry/browser (incl. Tracing, Replay) 90.21 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.31 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 94.9 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 107.85 kB - -
@sentry/browser (incl. Feedback) 46.69 kB - -
@sentry/browser (incl. sendFeedback) 34.22 kB - -
@sentry/browser (incl. FeedbackAsync) 39.32 kB - -
@sentry/browser (incl. Metrics) 30.18 kB - -
@sentry/browser (incl. Logs) 30.43 kB - -
@sentry/browser (incl. Metrics & Logs) 31.09 kB - -
@sentry/react 30.91 kB - -
@sentry/react (incl. Tracing) 52.91 kB - -
@sentry/vue 36.41 kB - -
@sentry/vue (incl. Tracing) 52.9 kB - -
@sentry/svelte 29.18 kB - -
CDN Bundle 30.86 kB - -
CDN Bundle (incl. Tracing) 51.12 kB - -
CDN Bundle (incl. Logs, Metrics) 33.12 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 53.09 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73.84 kB - -
CDN Bundle (incl. Tracing, Replay) 88.7 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 90.63 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 94.76 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 96.75 kB - -
CDN Bundle - uncompressed 91.25 kB - -
CDN Bundle (incl. Tracing) - uncompressed 152.53 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.82 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 158.48 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 227.39 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 272.26 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 278.2 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 285.96 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 291.89 kB - -
@sentry/nextjs (client) 55.24 kB - -
@sentry/sveltekit (client) 51.04 kB - -
@sentry/core/server 39.93 kB - -
@sentry/core/browser 13.63 kB - -
@sentry/node 133.38 kB +0.02% +19 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 82.15 kB - -
@sentry/node - without tracing 89.93 kB +0.03% +22 B 🔺
@sentry/node - without channel injection 112.16 kB +0.02% +21 B 🔺
@sentry/aws-serverless 98.2 kB +0.02% +19 B 🔺
@sentry/cloudflare (withSentry) - minified 205.06 kB - -
@sentry/cloudflare (withSentry) 510.44 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
@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!

@timfish
timfish requested a review from logaretm September 17, 2026 10:32
@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!

@logaretm logaretm 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 for the PR, I think this might be a bit too much for what it fixes. I added a couple of questions and something flagged by the clanker.

* wasm-bindgen writes `foo_bg.wasm` next to `foo.js` but the stack label is often
* `foo.wasm`. Used only for images without a parsed name section.
*/
export function namesForRegisteredWasm(codeFile: string): string[] {

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.

Clanker flagged this: this can't be reached. V8 only names the label when the module has a name section, so an image without moduleName always gets a hash-only label.

Can we drop it or verify it in a real browser integration test?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. V8 only names the label from the name section, which we parse at registration, so the fallback could not fire. Removed it and its tests.

Comment thread packages/wasm/src/patchWasmResponse.ts Outdated
fill(Response.prototype, 'arrayBuffer', (original: (this: Response) => Promise<ArrayBuffer>) => {
return function arrayBuffer(this: Response): Promise<ArrayBuffer> {
const bufferPromise: Promise<ArrayBuffer> = original.call(this);
const url = responseUrl(this);

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.

Q: Tagging every body read seems unrelated to this fix? why do we need to do it?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was left over from splitting the non-streaming work out to #23767 and is not needed here. Reverted to develop's version.

// `named.wasm` is `../simple.wasm` plus a module-name subsection (`namedmodule`)
// in its `name` section. Chrome labels bytes-compiled modules with that name
// as `wasm://wasm/namedmodule-<hash>`, not with the fetch URL.
sentryTest(

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.

Can we add a worker case? Only the main thread is covered right now.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a bytes-compiled case to the existing webWorker suite, using named.wasm so Chrome produces the wasm://wasm/namedmodule-<hash> label.

- Drop the fetch-basename and `_bg.wasm` guess. V8 puts a name in the
  `wasm://wasm/<name>-<hash>` label only when the module has a name
  section, and that name is parsed at registration, so the guess could
  not fire
- Restore develop's response body tagging; the change was not needed for
  this fix
- Cover a worker that compiles a module from fetched bytes in the existing
  webWorker browser suite
Comment thread dev-packages/browser-integration-tests/suites/wasm/webWorker/assets/worker.js Dismissed
Comment thread dev-packages/browser-integration-tests/suites/wasm/webWorker/assets/worker.js Dismissed
@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!

@timfish
timfish merged commit 3844ba0 into develop Sep 25, 2026
67 checks passed
@timfish
timfish deleted the ana/fix/wasm-worker-non-streaming branch September 25, 2026 17:11
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.

5 participants