Skip to content

Commit a67b991

Browse files
committed
Better extraction of eval frames on Firefox, Chrome
1 parent f162c9e commit a67b991

File tree

6 files changed

+87
-21
lines changed

6 files changed

+87
-21
lines changed

example/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<button onclick="divide(1, 0)">Sourcemap breakage</button>
3434
<button onclick="derp()">window.onerror</button>
3535
<button onclick="testOptions()">test options</button>
36+
<button onclick="throwEval()">throw eval</button>
3637
<button onclick="testSynthetic()">test synthetic</button>
3738
<button onclick="throwString()">throw string</button>
3839
<button onclick="showDialog()">show dialog</button>

example/scratch.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ function throwString() {
4141
throw 'oops';
4242
}
4343

44+
function throwEval() {
45+
eval('derp();');
46+
}
47+
4448
function showDialog() {
4549
broken();
4650
Raven.showReportDialog();

test/vendor/fixtures/captured-errors.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,18 @@ CapturedExceptions.FIREFOX_31 = {
223223
columnNumber: 12
224224
};
225225

226+
CapturedExceptions.FIREFOX_43_EVAL = {
227+
columnNumber: 30,
228+
fileName: 'http://localhost:8080/file.js line 25 > eval line 2 > eval',
229+
lineNumber: 1,
230+
message: 'message string',
231+
stack: 'baz@http://localhost:8080/file.js line 26 > eval line 2 > eval:1:30\n' +
232+
'foo@http://localhost:8080/file.js line 26 > eval:2:96\n' +
233+
'@http://localhost:8080/file.js line 26 > eval:4:18\n' +
234+
'speak@http://localhost:8080/file.js:26:17\n' +
235+
'@http://localhost:8080/file.js:33:9'
236+
};
237+
226238
// Internal errors sometimes thrown by Firefox
227239
// More here: https://developer.mozilla.org/en-US/docs/Mozilla/Errors
228240
//
@@ -241,6 +253,17 @@ CapturedExceptions.FIREFOX_44_NS_EXCEPTION = {
241253
result: 2147500037
242254
};
243255

256+
CapturedExceptions.FIREFOX_50_RESOURCE_URL = {
257+
stack: 'render@resource://path/data/content/bundle.js:5529:16\n' +
258+
'dispatchEvent@resource://path/data/content/vendor.bundle.js:18:23028\n' +
259+
'wrapped@resource://path/data/content/bundle.js:7270:25',
260+
fileName: 'resource://path/data/content/bundle.js',
261+
lineNumber: 5529,
262+
columnNumber: 16,
263+
message: 'this.props.raw[this.state.dataSource].rows is undefined',
264+
name: 'TypeError'
265+
};
266+
244267
CapturedExceptions.SAFARI_6 = {
245268
message: "'null' is not an object (evaluating 'x.undef')",
246269
stack: "@http://path/to/file.js:48\n" +
@@ -333,24 +356,24 @@ CapturedExceptions.CHROME_48_BLOB = {
333356
" at n.handle (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:7:2863)"
334357
};
335358

359+
CapturedExceptions.CHROME_48_EVAL = {
360+
message: 'message string',
361+
name: 'Error',
362+
stack: 'Error: message string\n' +
363+
'at baz (eval at foo (eval at speak (http://localhost:8080/file.js:21:17)), <anonymous>:1:30)\n' +
364+
'at foo (eval at speak (http://localhost:8080/file.js:21:17), <anonymous>:2:96)\n' +
365+
'at eval (eval at speak (http://localhost:8080/file.js:21:17), <anonymous>:4:18)\n' +
366+
'at Object.speak (http://localhost:8080/file.js:21:17)\n' +
367+
'at http://localhost:8080/file.js:31:13\n'
368+
};
369+
336370
CapturedExceptions.PHANTOMJS_1_19 = {
337371
stack: "Error: foo\n" +
338372
" at file:///path/to/file.js:878\n" +
339373
" at foo (http://path/to/file.js:4283)\n" +
340374
" at http://path/to/file.js:4287"
341375
};
342376

343-
CapturedExceptions.FIREFOX_50_RESOURCE_URL = {
344-
stack: 'render@resource://path/data/content/bundle.js:5529:16\n' +
345-
'dispatchEvent@resource://path/data/content/vendor.bundle.js:18:23028\n' +
346-
'wrapped@resource://path/data/content/bundle.js:7270:25',
347-
fileName: 'resource://path/data/content/bundle.js',
348-
lineNumber: 5529,
349-
columnNumber: 16,
350-
message: 'this.props.raw[this.state.dataSource].rows is undefined',
351-
name: 'TypeError'
352-
};
353-
354377
CapturedExceptions.ANDROID_REACT_NATIVE = {
355378
message: 'Error: test',
356379
name: 'Error',

test/vendor/tracekit-parser.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ describe('TraceKit', function () {
123123
assert.deepEqual(stackFrames.stack[2], { url: 'http://localhost:8080/file.js', func: 'I.e.fn.(anonymous function) [as index]', args: [], line: 10, column: 3651 });
124124
});
125125

126+
it('should parse nested eval() from Chrome', function() {
127+
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.CHROME_48_EVAL);
128+
assert.ok(stackFrames);
129+
assert.deepEqual(stackFrames.stack.length, 5);
130+
assert.deepEqual(stackFrames.stack[0], { url: 'http://localhost:8080/file.js', func: 'baz', args: [], line: 21, column: 17});
131+
assert.deepEqual(stackFrames.stack[1], { url: 'http://localhost:8080/file.js', func: 'foo', args: [], line: 21, column: 17});
132+
assert.deepEqual(stackFrames.stack[2], { url: 'http://localhost:8080/file.js', func: 'eval', args: [], line: 21, column: 17});
133+
assert.deepEqual(stackFrames.stack[3], { url: 'http://localhost:8080/file.js', func: 'Object.speak', args: [], line: 21, column: 17});
134+
assert.deepEqual(stackFrames.stack[4], { url: 'http://localhost:8080/file.js', func: '?', args: [], line: 31, column: 13});
135+
});
136+
126137
it('should parse Chrome error with blob URLs', function () {
127138
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.CHROME_48_BLOB);
128139
assert.ok(stackFrames);
@@ -216,6 +227,18 @@ describe('TraceKit', function () {
216227
assert.deepEqual(stackFrames.stack.length, 3);
217228
assert.deepEqual(stackFrames.stack[0], { url: 'resource://path/data/content/bundle.js', func: 'render', args: [], line: 5529, column: 16 });
218229
});
230+
231+
it('should parse Firefox errors with eval URLs', function () {
232+
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.FIREFOX_43_EVAL);
233+
assert.ok(stackFrames);
234+
assert.deepEqual(stackFrames.stack.length, 5);
235+
assert.deepEqual(stackFrames.stack[0], { url: 'http://localhost:8080/file.js', func: 'baz', args:[], line: 26, column: null});
236+
assert.deepEqual(stackFrames.stack[1], { url: 'http://localhost:8080/file.js', func: 'foo', args:[], line: 26, column: null});
237+
assert.deepEqual(stackFrames.stack[2], { url: 'http://localhost:8080/file.js', func: '?', args:[], line: 26, column: null});
238+
assert.deepEqual(stackFrames.stack[3], { url: 'http://localhost:8080/file.js', func: 'speak', args:[], line: 26, column: 17});
239+
assert.deepEqual(stackFrames.stack[4], { url: 'http://localhost:8080/file.js', func: '?', args:[], line: 33, column: 9});
240+
});
241+
219242
it('should parse React Native errors on Android', function () {
220243
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.ANDROID_REACT_NATIVE);
221244
assert.ok(stackFrames);

test/vendor/tracekit.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ describe('TraceKit', function(){
7373

7474
assert.equal(trace.stack[2].func, 'eval');
7575
// TODO: fix nested evals
76-
assert.equal(trace.stack[2].url, 'eval at <anonymous> (http://example.com/js/test.js:26:5), <anonymous>');
77-
assert.equal(trace.stack[2].line, 1); // second set of line/column numbers used
78-
assert.equal(trace.stack[2].column, 26);
76+
assert.equal(trace.stack[2].url, 'http://example.com/js/test.js');
77+
assert.equal(trace.stack[2].line, 26);
78+
assert.equal(trace.stack[2].column, 5);
7979
});
8080
});
8181

vendor/TraceKit/tracekit.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -392,16 +392,25 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
392392

393393
var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|<anonymous>|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,
394394
gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|resource|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i,
395+
geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i,
395396
winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
396397
lines = ex.stack.split('\n'),
397398
stack = [],
399+
submatch,
398400
parts,
399401
element,
400402
reference = /^(.*) is undefined$/.exec(ex.message);
401403

402404
for (var i = 0, j = lines.length; i < j; ++i) {
403405
if ((parts = chrome.exec(lines[i]))) {
404406
var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line
407+
var isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line
408+
if (isEval && (submatch = /\((\S*)(?::(\d+))(?::(\d+))\)/.exec(parts[2]))) {
409+
// throw out eval line/column and use top-most line/column number
410+
parts[2] = submatch[1]; // url
411+
parts[3] = submatch[2]; // line
412+
parts[4] = submatch[3]; // column
413+
}
405414
element = {
406415
'url': !isNative ? parts[2] : null,
407416
'func': parts[1] || UNKNOWN_FUNCTION,
@@ -418,6 +427,19 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
418427
'column': parts[4] ? +parts[4] : null
419428
};
420429
} else if ((parts = gecko.exec(lines[i]))) {
430+
var isEval = parts[3] && parts[3].indexOf(' > eval') > -1;
431+
if (isEval && (submatch = geckoEval.exec(parts[3]))) {
432+
// throw out eval line/column and use top-most line number
433+
parts[3] = submatch[1];
434+
parts[4] = submatch[2];
435+
parts[5] = null; // no column when eval
436+
} else if (i === 0 && !parts[5] && typeof ex.columnNumber !== 'undefined') {
437+
// FireFox uses this awesome columnNumber property for its top frame
438+
// Also note, Firefox's column number is 0-based and everything else expects 1-based,
439+
// so adding 1
440+
// NOTE: this hack doesn't work if top-most frame is eval
441+
stack[0].column = ex.columnNumber + 1;
442+
}
421443
element = {
422444
'url': parts[3],
423445
'func': parts[1] || UNKNOWN_FUNCTION,
@@ -440,13 +462,6 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
440462
return null;
441463
}
442464

443-
if (!stack[0].column && typeof ex.columnNumber !== 'undefined') {
444-
// FireFox uses this awesome columnNumber property for its top frame
445-
// Also note, Firefox's column number is 0-based and everything else expects 1-based,
446-
// so adding 1
447-
stack[0].column = ex.columnNumber + 1;
448-
}
449-
450465
return {
451466
'name': ex.name,
452467
'message': ex.message,

0 commit comments

Comments
 (0)