Skip to content

Commit d891e06

Browse files
trzeciaks1gr1dlforst
authored
fix(wasm): Integration wasm uncaught WebAssembly.Exception (#13787) (#13854)
Add support for wasm WebAssembly.Exception (uncaught exception in emscripten). ## References - [WebAssembly.Exception | MDN Web Docs](https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception) - #13787 --------- Co-authored-by: s1gr1d <[email protected]> Co-authored-by: Sigrid Huemer <[email protected]> Co-authored-by: Luca Forstner <[email protected]>
1 parent db77c11 commit d891e06

File tree

4 files changed

+115
-3
lines changed

4 files changed

+115
-3
lines changed

packages/browser/src/eventbuilder.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function exceptionFromError(stackParser: StackParser, ex: Error): Excepti
3333
const frames = parseStackFrames(stackParser, ex);
3434

3535
const exception: Exception = {
36-
type: ex && ex.name,
36+
type: extractType(ex),
3737
value: extractMessage(ex),
3838
};
3939

@@ -159,19 +159,59 @@ function getPopFirstTopFrames(ex: Error & { framesToPop?: unknown }): number {
159159
return 0;
160160
}
161161

162+
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception
163+
// @ts-expect-error - WebAssembly.Exception is a valid class
164+
function isWebAssemblyException(exception: unknown): exception is WebAssembly.Exception {
165+
// Check for support
166+
// @ts-expect-error - WebAssembly.Exception is a valid class
167+
if (typeof WebAssembly !== 'undefined' && typeof WebAssembly.Exception !== 'undefined') {
168+
// @ts-expect-error - WebAssembly.Exception is a valid class
169+
return exception instanceof WebAssembly.Exception;
170+
} else {
171+
return false;
172+
}
173+
}
174+
175+
/**
176+
* Extracts from errors what we use as the exception `type` in error events.
177+
*
178+
* Usually, this is the `name` property on Error objects but WASM errors need to be treated differently.
179+
*/
180+
export function extractType(ex: Error & { message: { error?: Error } }): string | undefined {
181+
const name = ex && ex.name;
182+
183+
// The name for WebAssembly.Exception Errors needs to be extracted differently.
184+
// Context: https://github.com/getsentry/sentry-javascript/issues/13787
185+
if (!name && isWebAssemblyException(ex)) {
186+
// Emscripten sets array[type, message] to the "message" property on the WebAssembly.Exception object
187+
const hasTypeInMessage = ex.message && Array.isArray(ex.message) && ex.message.length == 2;
188+
return hasTypeInMessage ? ex.message[0] : 'WebAssembly.Exception';
189+
}
190+
191+
return name;
192+
}
193+
162194
/**
163195
* There are cases where stacktrace.message is an Event object
164196
* https://github.com/getsentry/sentry-javascript/issues/1949
165197
* In this specific case we try to extract stacktrace.message.error.message
166198
*/
167-
function extractMessage(ex: Error & { message: { error?: Error } }): string {
199+
export function extractMessage(ex: Error & { message: { error?: Error } }): string {
168200
const message = ex && ex.message;
201+
169202
if (!message) {
170203
return 'No error message';
171204
}
205+
172206
if (message.error && typeof message.error.message === 'string') {
173207
return message.error.message;
174208
}
209+
210+
// Emscripten sets array[type, message] to the "message" property on the WebAssembly.Exception object
211+
if (isWebAssemblyException(ex) && Array.isArray(ex.message) && ex.message.length == 2) {
212+
return ex.message[1];
213+
}
214+
175215
return message;
176216
}
177217

packages/browser/test/eventbuilder.test.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import { afterEach, describe, expect, it, vi } from 'vitest';
66

77
import { defaultStackParser } from '../src';
8-
import { eventFromUnknownInput } from '../src/eventbuilder';
8+
import { eventFromUnknownInput, extractMessage, extractType } from '../src/eventbuilder';
99

1010
vi.mock('@sentry/core', async requireActual => {
1111
return {
@@ -169,3 +169,65 @@ describe('eventFromUnknownInput', () => {
169169
});
170170
});
171171
});
172+
173+
describe('extractMessage', () => {
174+
it('should extract message from a standard Error object', () => {
175+
const error = new Error('Test error message');
176+
const message = extractMessage(error);
177+
expect(message).toBe('Test error message');
178+
});
179+
180+
it('should extract message from a WebAssembly.Exception object', () => {
181+
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception#examples
182+
// @ts-expect-error - WebAssembly.Tag is a valid constructor
183+
const tag = new WebAssembly.Tag({ parameters: ['i32', 'f32'] });
184+
// @ts-expect-error - WebAssembly.Exception is a valid constructor
185+
const wasmException = new WebAssembly.Exception(tag, [42, 42.3]);
186+
187+
const message = extractMessage(wasmException);
188+
expect(message).toBe('wasm exception');
189+
});
190+
191+
it('should extract nested error message', () => {
192+
const nestedError = {
193+
message: {
194+
error: new Error('Nested error message'),
195+
},
196+
};
197+
const message = extractMessage(nestedError as any);
198+
expect(message).toBe('Nested error message');
199+
});
200+
201+
it('should return "No error message" if message is undefined', () => {
202+
const error = new Error();
203+
error.message = undefined as any;
204+
const message = extractMessage(error);
205+
expect(message).toBe('No error message');
206+
});
207+
});
208+
209+
describe('extractName', () => {
210+
it('should extract name from a standard Error object', () => {
211+
const error = new Error('Test error message');
212+
const name = extractType(error);
213+
expect(name).toBe('Error');
214+
});
215+
216+
it('should extract name from a WebAssembly.Exception object', () => {
217+
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception#examples
218+
// @ts-expect-error - WebAssembly.Tag is a valid constructor
219+
const tag = new WebAssembly.Tag({ parameters: ['i32', 'f32'] });
220+
// @ts-expect-error - WebAssembly.Exception is a valid constructor
221+
const wasmException = new WebAssembly.Exception(tag, [42, 42.3]);
222+
223+
const name = extractType(wasmException);
224+
expect(name).toBe('WebAssembly.Exception');
225+
});
226+
227+
it('should return undefined if name is not present', () => {
228+
const error = new Error('Test error message');
229+
error.name = undefined as any;
230+
const name = extractType(error);
231+
expect(name).toBeUndefined();
232+
});
233+
});

packages/utils/src/is.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function isError(wat: unknown): wat is Error {
1717
case '[object Error]':
1818
case '[object Exception]':
1919
case '[object DOMException]':
20+
case '[object WebAssembly.Exception]':
2021
return true;
2122
default:
2223
return isInstanceOf(wat, Error);

packages/utils/test/is.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '../src/is';
1212
import { supportsDOMError, supportsDOMException, supportsErrorEvent } from '../src/supports';
1313
import { resolvedSyncPromise } from '../src/syncpromise';
14+
import { testOnlyIfNodeVersionAtLeast } from './testutils';
1415

1516
class SentryError extends Error {
1617
public name: string;
@@ -56,6 +57,14 @@ describe('isError()', () => {
5657
expect(isError('')).toEqual(false);
5758
expect(isError(true)).toEqual(false);
5859
});
60+
61+
testOnlyIfNodeVersionAtLeast(18)('should detect WebAssembly.Exceptions', () => {
62+
// https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception#examples
63+
// @ts-expect-error - WebAssembly.Tag is a valid constructor
64+
const tag = new WebAssembly.Tag({ parameters: ['i32', 'f32'] });
65+
// @ts-expect-error - WebAssembly.Exception is a valid constructor
66+
expect(isError(new WebAssembly.Exception(tag, [42, 42.3]))).toBe(true);
67+
});
5968
});
6069

6170
if (supportsErrorEvent()) {

0 commit comments

Comments
 (0)