Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/browser/src/eventbuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ export function eventFromUnknownInput(
if (isDOMError(exception) || isDOMException(exception as DOMException)) {
const domException = exception as DOMException;

if ('stack' in (exception as Error)) {
if ((exception as Error).stack) {
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.

h: I think this will change how the dom exceptions are classified, I pushed a test to see if it would pass.

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.

Maybe something like this would keep the best of both worlds here, could you try something like:

// same check as before
if ('stack' in (exception as Error)) {
  event = eventFromError(stackParser, exception as Error);

  // preserves the classification while adding stacktraces if it exists...
  const firstException = event.exception?.values?.[0];
  if (attachStacktrace && syntheticException && firstException && !firstException.stacktrace) {
    const frames = parseStackFrames(stackParser, syntheticException);
    if (frames.length) {
      firstException.stacktrace = { frames };
      addExceptionMechanism(event, { synthetic: true });
    }
  }
} else {
  // existing fallback
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

DOMException type classification lost for empty stack traces

Medium Severity

Changing the check from 'stack' in (exception as Error) to a truthy check causes DOMExceptions with empty stack strings to fall into the else branch, which uses eventFromString + addExceptionTypeValue. This loses the specific DOMException type (e.g., 'SyntaxError', 'NotAllowedError') and replaces it with a generic 'Error' type, because addExceptionTypeValue defaults the type to 'Error'. The exception value also changes from just the message to a name: message format. This regression in error classification will affect how users group and identify DOMException errors in Sentry. The contradictory test at line 190 (expecting type: 'SyntaxError') confirms this is a recognized problem — it will fail because the code actually produces type: 'Error'.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 901e3ef. Configure here.

event = eventFromError(stackParser, exception as Error);
} else {
const name = domException.name || (isDOMError(domException) ? 'DOMError' : 'DOMException');
Comment thread
sentry[bot] marked this conversation as resolved.
Expand Down
85 changes: 85 additions & 0 deletions packages/browser/test/eventbuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,91 @@
},
});
});

it('add a synthetic stack trace to DOMException with empty stack traces if attachStacktrace is true', async () => {
const exception = new DOMException('The string did not match the expected pattern.', 'SyntaxError');
exception.stack = '';

const syntheticException = new Error('Test message');
const event = await eventFromUnknownInput(defaultStackParser, exception, syntheticException, true);
expect(event.exception?.values?.[0]).toEqual(
expect.objectContaining({
mechanism: { handled: true, synthetic: true, type: 'generic' },
stacktrace: {
frames: expect.arrayContaining([expect.any(Object), expect.any(Object)]),
},
type: 'Error',
value: 'SyntaxError: The string did not match the expected pattern.',
}),
);
});

it('preserves DOMException type when stack is an empty string', () => {
const exception = new DOMException('The string did not match the expected pattern.', 'SyntaxError');
exception.stack = '';

const event = eventFromUnknownInput(defaultStackParser, exception, new Error('synthetic'), true);

expect(event.exception?.values?.[0]).toMatchObject({

Check failure on line 196 in packages/browser/test/eventbuilder.test.ts

View workflow job for this annotation

GitHub Actions / Browser Unit Tests

test/eventbuilder.test.ts > eventFromUnknownInput > preserves DOMException type when stack is an empty string

AssertionError: expected { …(4) } to match object { type: 'SyntaxError', …(1) } (6 matching properties omitted from actual) - Expected + Received { - "type": "SyntaxError", - "value": "The string did not match the expected pattern.", + "type": "Error", + "value": "SyntaxError: The string did not match the expected pattern.", } ❯ test/eventbuilder.test.ts:196:42
type: 'SyntaxError',
value: 'The string did not match the expected pattern.',
});
});
Comment thread
sentry[bot] marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Contradictory test expects wrong branch behavior

Medium Severity

The test "preserves DOMException type when stack is an empty string" expects type: 'SyntaxError' and value: 'The string did not match the expected pattern.', but it has the same setup as the test at line 172 (stack = '', attachStacktrace = true), which expects type: 'Error' and value: 'SyntaxError: The string did not match the expected pattern.'. Tracing through the production code, the empty stack takes the eventFromString path, producing type: 'Error' and the combined name: message value. This test's expectations reflect the old eventFromError path behavior and will fail.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 83ae1e5. Configure here.

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.

This works against today's implementation, I'm just ensuring we lock it in and see if this PR changes it.


it('add a synthetic stack trace to DOMException without a stack traces property if attachStacktrace is true', async () => {
const exception = new DOMException('The string did not match the expected pattern.', 'SyntaxError');
delete exception.stack;

const syntheticException = new Error('Test message');
const event = await eventFromUnknownInput(defaultStackParser, exception, syntheticException, true);
expect(event.exception?.values?.[0]).toEqual(
expect.objectContaining({
mechanism: { handled: true, synthetic: true, type: 'generic' },
stacktrace: {
frames: expect.arrayContaining([expect.any(Object), expect.any(Object)]),
},
type: 'Error',
value: 'SyntaxError: The string did not match the expected pattern.',
}),
);
});

it("doesn't add a synthetic stack trace to DOMException with empty stack traces if attachStacktrace is false", async () => {
const exception = new DOMException('The string did not match the expected pattern.', 'SyntaxError');
exception.stack = '';

const syntheticException = new Error('Test message');
const event = await eventFromUnknownInput(defaultStackParser, exception, syntheticException, false);
expect(event.exception?.values?.[0]).toEqual({
type: 'Error',
value: 'SyntaxError: The string did not match the expected pattern.',
});
});

it("doesn't add a synthetic stack trace to DOMException with stack traces if attachStacktrace is true", async () => {
const exception = new DOMException('The string did not match the expected pattern.', 'SyntaxError');
exception.stack = 'SyntaxError\n at <anonymous>:1:2';

const syntheticException = new Error('Test message');
const event = await eventFromUnknownInput(defaultStackParser, exception, syntheticException, true);
expect(event.exception?.values?.[0]).toEqual(
expect.objectContaining({
stacktrace: {
frames: [
{
colno: 2,
filename: '<anonymous>',
function: '?',
in_app: true,
lineno: 1,
},
],
},
type: 'SyntaxError',
value: 'The string did not match the expected pattern.',
}),
);
});
});

describe('extractMessage', () => {
Expand Down
Loading