Skip to content

Commit

Permalink
fix(browser): Parse frames-only safari(-web)-extension stack (#3929)
Browse files Browse the repository at this point in the history
* fix(browser): Parse frames-only safari(-web)-extension stack

* Simplify function and add appropriate comment
  • Loading branch information
kamilogorek committed Sep 2, 2021
1 parent 88b96f6 commit 45508c0
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 57 deletions.
51 changes: 41 additions & 10 deletions packages/browser/src/tracekit.ts
Expand Up @@ -3,7 +3,7 @@
* largely modified and is now maintained as part of Sentry JS SDK.
*/

/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-member-access, max-lines */

/**
* An object representing a single stack frame.
Expand Down Expand Up @@ -124,16 +124,10 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null {
// Arpad: Working with the regexp above is super painful. it is quite a hack, but just stripping the `address at `
// prefix here seems like the quickest solution for now.
let url = parts[2] && parts[2].indexOf('address at ') === 0 ? parts[2].substr('address at '.length) : parts[2];

// Kamil: One more hack won't hurt us right? Understanding and adding more rules on top of these regexps right now
// would be way too time consuming. (TODO: Rewrite whole RegExp to be more readable)
let func = parts[1] || UNKNOWN_FUNCTION;
const isSafariExtension = func.indexOf('safari-extension') !== -1;
const isSafariWebExtension = func.indexOf('safari-web-extension') !== -1;
if (isSafariExtension || isSafariWebExtension) {
func = func.indexOf('@') !== -1 ? func.split('@')[0] : UNKNOWN_FUNCTION;
url = isSafariExtension ? `safari-extension:${url}` : `safari-web-extension:${url}`;
}
[func, url] = extractSafariExtensionDetails(func, url);

element = {
url,
Expand Down Expand Up @@ -165,9 +159,14 @@ function computeStackTraceFromStackProp(ex: any): StackTrace | null {
// NOTE: this hack doesn't work if top-most frame is eval
stack[0].column = (ex.columnNumber as number) + 1;
}

let url = parts[3];
let func = parts[1] || UNKNOWN_FUNCTION;
[func, url] = extractSafariExtensionDetails(func, url);

element = {
url: parts[3],
func: parts[1] || UNKNOWN_FUNCTION,
url,
func,
args: parts[2] ? parts[2].split(',') : [],
line: parts[4] ? +parts[4] : null,
column: parts[5] ? +parts[5] : null,
Expand Down Expand Up @@ -249,6 +248,38 @@ function computeStackTraceFromStacktraceProp(ex: any): StackTrace | null {
};
}

/**
* Safari web extensions, starting version unknown, can produce "frames-only" stacktraces.
* What it means, is that instead of format like:
*
* Error: wat
* at function@url:row:col
* at function@url:row:col
* at function@url:row:col
*
* it produces something like:
*
* function@url:row:col
* function@url:row:col
* function@url:row:col
*
* Because of that, it won't be captured by `chrome` RegExp and will fall into `Gecko` branch.
* This function is extracted so that we can use it in both places without duplicating the logic.
* Unfortunatelly "just" changing RegExp is too complicated now and making it pass all tests
* and fix this case seems like an impossible, or at least way too time-consuming task.
*/
const extractSafariExtensionDetails = (func: string, url: string): [string, string] => {
const isSafariExtension = func.indexOf('safari-extension') !== -1;
const isSafariWebExtension = func.indexOf('safari-web-extension') !== -1;

return isSafariExtension || isSafariWebExtension
? [
func.indexOf('@') !== -1 ? func.split('@')[0] : UNKNOWN_FUNCTION,
isSafariExtension ? `safari-extension:${url}` : `safari-web-extension:${url}`,
]
: [func, url];
};

/** Remove N number of frames from the stack */
function popFrames(stacktrace: StackTrace, popSize: number): StackTrace {
try {
Expand Down
166 changes: 119 additions & 47 deletions packages/browser/test/unit/tracekit/custom.test.ts
Expand Up @@ -24,58 +24,130 @@ describe('Tracekit - Custom Tests', () => {
]);
});

it('should parse exceptions for safari-extension', () => {
const SAFARI_EXTENSION_EXCEPTION = {
message: 'wat',
name: 'Error',
stack: `Error: wat
describe('Safari extensions', () => {
it('should parse exceptions for safari-extension', () => {
const SAFARI_EXTENSION_EXCEPTION = {
message: 'wat',
name: 'Error',
stack: `Error: wat
at ClipperError@safari-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js:223036:10)
at safari-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`,
};
const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION);
expect(stacktrace.stack).deep.equal([
{
url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js',
func: 'ClipperError',
args: [],
line: 223036,
column: 10,
},
{
url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js',
func: '?',
args: [],
line: 3313,
column: 26,
},
]);
});
};
const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION);
expect(stacktrace.stack).deep.equal([
{
url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js',
func: 'ClipperError',
args: [],
line: 223036,
column: 10,
},
{
url: 'safari-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js',
func: '?',
args: [],
line: 3313,
column: 26,
},
]);
});

it('should parse exceptions for safari-web-extension', () => {
const SAFARI_WEB_EXTENSION_EXCEPTION = {
message: 'wat',
name: 'Error',
stack: `Error: wat
it('should parse exceptions for safari-extension with frames-only stack', () => {
const SAFARI_EXTENSION_EXCEPTION = {
message: `undefined is not an object (evaluating 'e.groups.includes')`,
name: `TypeError`,
stack: `isClaimed@safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js:2:929865
safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js:2:1588410
promiseReactionJob@[native code]`,
};
const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION);

expect(stacktrace.stack).deep.equal([
{
url: 'safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js',
func: 'isClaimed',
args: [],
line: 2,
column: 929865,
},
{
url: 'safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js',
func: '?',
args: [],
line: 2,
column: 1588410,
},
{
url: '[native code]',
func: 'promiseReactionJob',
args: [],
line: null,
column: null,
},
]);
});

it('should parse exceptions for safari-web-extension', () => {
const SAFARI_WEB_EXTENSION_EXCEPTION = {
message: 'wat',
name: 'Error',
stack: `Error: wat
at ClipperError@safari-web-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js:223036:10)
at safari-web-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`,
};
const stacktrace = computeStackTrace(SAFARI_WEB_EXTENSION_EXCEPTION);
expect(stacktrace.stack).deep.equal([
{
url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js',
func: 'ClipperError',
args: [],
line: 223036,
column: 10,
},
{
url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js',
func: '?',
args: [],
line: 3313,
column: 26,
},
]);
};
const stacktrace = computeStackTrace(SAFARI_WEB_EXTENSION_EXCEPTION);
expect(stacktrace.stack).deep.equal([
{
url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/commons.js',
func: 'ClipperError',
args: [],
line: 223036,
column: 10,
},
{
url: 'safari-web-extension://3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js',
func: '?',
args: [],
line: 3313,
column: 26,
},
]);
});

it('should parse exceptions for safari-web-extension with frames-only stack', () => {
const SAFARI_EXTENSION_EXCEPTION = {
message: `undefined is not an object (evaluating 'e.groups.includes')`,
name: `TypeError`,
stack: `p_@safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js:29:33314
safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js:29:56027
promiseReactionJob@[native code]`,
};
const stacktrace = computeStackTrace(SAFARI_EXTENSION_EXCEPTION);

expect(stacktrace.stack).deep.equal([
{
url: 'safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js',
func: 'p_',
args: [],
line: 29,
column: 33314,
},
{
url: 'safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js',
func: '?',
args: [],
line: 29,
column: 56027,
},
{
url: '[native code]',
func: 'promiseReactionJob',
args: [],
line: null,
column: null,
},
]);
});
});

it('should parse exceptions for react-native-v8', () => {
Expand Down

0 comments on commit 45508c0

Please sign in to comment.