fix(wasm): map wasm:// stack frames to registered debug images - #23999
d2anamaria wants to merge 17 commits into
Conversation
- 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`
size-limit report 📦
|
- 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`
|
👋 @msonnb — Please review this PR when you get a chance! |
|
👋 @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
|
👋 @msonnb — Please review this PR when you get a chance! |
|
👋 @Lms24, @andreiborza — Please review this PR when you get a chance! |
|
👋 @msonnb — Please review this PR when you get a chance! |
|
👋 @Lms24, @andreiborza — Please review this PR when you get a chance! |
logaretm
left a comment
There was a problem hiding this comment.
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[] { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
Q: Tagging every body read seems unrelated to this fix? why do we need to do it?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
Can we add a worker case? Only the main thread is covered right now.
There was a problem hiding this comment.
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
|
👋 @msonnb — Please review this PR when you get a chance! |
|
👋 @Lms24, @andreiborza — Please review this PR when you get a chance! |
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 ascode_file, so frames never link to the debug image and symbolication fails.Solution
namecustom section into an internalmoduleNameon the image. It is stripped before the event is sent.patchFrames(), when the exactcode_filelookup misses, compare the label's module name to the registered images. An image with a parsedmoduleNamematches 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→.wasmalias.debug_id, since a page and a worker can register the same binary under different URLs.-<hash>suffix was stripped, so hex-looking module names such ased25519still map.Decisions
application/octet-streamor from extension-less URLs. A tag is only read back after aWebAssemblycompile succeeded, so tagging a non-wasm buffer is never observable. Body reads on responses without a URL are passed through untouched.named.wasm, which issimple.wasmplus a module-name subsection.simple.wasmonly carries function names, so Chrome labels it hash-only and the mapping cannot be exercised with it. Code offsets andbuild_idare unchanged.Limitations
wasm://wasm/<hex>labels are not mapped (@sentry/wasm:wasm://wasm/frames missdebug_metaon non-streaminginstantiate(buffer)#23781)debug_id) stay unmatched