Skip to content

Commit cb5e0df

Browse files
authored
Fix line/column/url extraction from some eval frames (#907)
1 parent 7363eec commit cb5e0df

File tree

6 files changed

+92
-21
lines changed

6 files changed

+92
-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
@@ -234,6 +234,18 @@ CapturedExceptions.FIREFOX_31 = {
234234
columnNumber: 12
235235
};
236236

237+
CapturedExceptions.FIREFOX_43_EVAL = {
238+
columnNumber: 30,
239+
fileName: 'http://localhost:8080/file.js line 25 > eval line 2 > eval',
240+
lineNumber: 1,
241+
message: 'message string',
242+
stack: 'baz@http://localhost:8080/file.js line 26 > eval line 2 > eval:1:30\n' +
243+
'foo@http://localhost:8080/file.js line 26 > eval:2:96\n' +
244+
'@http://localhost:8080/file.js line 26 > eval:4:18\n' +
245+
'speak@http://localhost:8080/file.js:26:17\n' +
246+
'@http://localhost:8080/file.js:33:9'
247+
};
248+
237249
// Internal errors sometimes thrown by Firefox
238250
// More here: https://developer.mozilla.org/en-US/docs/Mozilla/Errors
239251
//
@@ -252,6 +264,17 @@ CapturedExceptions.FIREFOX_44_NS_EXCEPTION = {
252264
result: 2147500037
253265
};
254266

267+
CapturedExceptions.FIREFOX_50_RESOURCE_URL = {
268+
stack: 'render@resource://path/data/content/bundle.js:5529:16\n' +
269+
'dispatchEvent@resource://path/data/content/vendor.bundle.js:18:23028\n' +
270+
'wrapped@resource://path/data/content/bundle.js:7270:25',
271+
fileName: 'resource://path/data/content/bundle.js',
272+
lineNumber: 5529,
273+
columnNumber: 16,
274+
message: 'this.props.raw[this.state.dataSource].rows is undefined',
275+
name: 'TypeError'
276+
};
277+
255278
CapturedExceptions.SAFARI_6 = {
256279
message: "'null' is not an object (evaluating 'x.undef')",
257280
stack: "@http://path/to/file.js:48\n" +
@@ -344,24 +367,24 @@ CapturedExceptions.CHROME_48_BLOB = {
344367
" at n.handle (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:7:2863)"
345368
};
346369

370+
CapturedExceptions.CHROME_48_EVAL = {
371+
message: 'message string',
372+
name: 'Error',
373+
stack: 'Error: message string\n' +
374+
'at baz (eval at foo (eval at speak (http://localhost:8080/file.js:21:17)), <anonymous>:1:30)\n' +
375+
'at foo (eval at speak (http://localhost:8080/file.js:21:17), <anonymous>:2:96)\n' +
376+
'at eval (eval at speak (http://localhost:8080/file.js:21:17), <anonymous>:4:18)\n' +
377+
'at Object.speak (http://localhost:8080/file.js:21:17)\n' +
378+
'at http://localhost:8080/file.js:31:13\n'
379+
};
380+
347381
CapturedExceptions.PHANTOMJS_1_19 = {
348382
stack: "Error: foo\n" +
349383
" at file:///path/to/file.js:878\n" +
350384
" at foo (http://path/to/file.js:4283)\n" +
351385
" at http://path/to/file.js:4287"
352386
};
353387

354-
CapturedExceptions.FIREFOX_50_RESOURCE_URL = {
355-
stack: 'render@resource://path/data/content/bundle.js:5529:16\n' +
356-
'dispatchEvent@resource://path/data/content/vendor.bundle.js:18:23028\n' +
357-
'wrapped@resource://path/data/content/bundle.js:7270:25',
358-
fileName: 'resource://path/data/content/bundle.js',
359-
lineNumber: 5529,
360-
columnNumber: 16,
361-
message: 'this.props.raw[this.state.dataSource].rows is undefined',
362-
name: 'TypeError'
363-
};
364-
365388
CapturedExceptions.ANDROID_REACT_NATIVE = {
366389
message: 'Error: test',
367390
name: 'Error',

test/vendor/tracekit-parser.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ 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+
126127
it('should parse Chrome error with webpack URLs', function () {
127128
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.CHROME_XX_WEBPACK);
128129
assert.ok(stackFrames);
@@ -133,6 +134,17 @@ describe('TraceKit', function () {
133134
assert.deepEqual(stackFrames.stack[3], { url: 'webpack:///./~/react-proxy/modules/createPrototypeProxy.js?', func: 'TESTTESTTEST.proxiedMethod', args: [], line: 44, column: 30 });
134135
});
135136

137+
it('should parse nested eval() from Chrome', function() {
138+
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.CHROME_48_EVAL);
139+
assert.ok(stackFrames);
140+
assert.deepEqual(stackFrames.stack.length, 5);
141+
assert.deepEqual(stackFrames.stack[0], { url: 'http://localhost:8080/file.js', func: 'baz', args: [], line: 21, column: 17});
142+
assert.deepEqual(stackFrames.stack[1], { url: 'http://localhost:8080/file.js', func: 'foo', args: [], line: 21, column: 17});
143+
assert.deepEqual(stackFrames.stack[2], { url: 'http://localhost:8080/file.js', func: 'eval', args: [], line: 21, column: 17});
144+
assert.deepEqual(stackFrames.stack[3], { url: 'http://localhost:8080/file.js', func: 'Object.speak', args: [], line: 21, column: 17});
145+
assert.deepEqual(stackFrames.stack[4], { url: 'http://localhost:8080/file.js', func: '?', args: [], line: 31, column: 13});
146+
});
147+
136148
it('should parse Chrome error with blob URLs', function () {
137149
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.CHROME_48_BLOB);
138150
assert.ok(stackFrames);
@@ -226,6 +238,18 @@ describe('TraceKit', function () {
226238
assert.deepEqual(stackFrames.stack.length, 3);
227239
assert.deepEqual(stackFrames.stack[0], { url: 'resource://path/data/content/bundle.js', func: 'render', args: [], line: 5529, column: 16 });
228240
});
241+
242+
it('should parse Firefox errors with eval URLs', function () {
243+
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.FIREFOX_43_EVAL);
244+
assert.ok(stackFrames);
245+
assert.deepEqual(stackFrames.stack.length, 5);
246+
assert.deepEqual(stackFrames.stack[0], { url: 'http://localhost:8080/file.js', func: 'baz', args:[], line: 26, column: null});
247+
assert.deepEqual(stackFrames.stack[1], { url: 'http://localhost:8080/file.js', func: 'foo', args:[], line: 26, column: null});
248+
assert.deepEqual(stackFrames.stack[2], { url: 'http://localhost:8080/file.js', func: '?', args:[], line: 26, column: null});
249+
assert.deepEqual(stackFrames.stack[3], { url: 'http://localhost:8080/file.js', func: 'speak', args:[], line: 26, column: 17});
250+
assert.deepEqual(stackFrames.stack[4], { url: 'http://localhost:8080/file.js', func: '?', args:[], line: 33, column: 9});
251+
});
252+
229253
it('should parse React Native errors on Android', function () {
230254
var stackFrames = TraceKit.computeStackTrace(CapturedExceptions.ANDROID_REACT_NATIVE);
231255
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: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,15 +393,28 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
393393
var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,
394394
gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i,
395395
winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
396+
397+
// Used to additionally parse URL/line/column from eval frames
398+
geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i,
399+
chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/,
400+
396401
lines = ex.stack.split('\n'),
397402
stack = [],
403+
submatch,
398404
parts,
399405
element,
400406
reference = /^(.*) is undefined$/.exec(ex.message);
401407

402408
for (var i = 0, j = lines.length; i < j; ++i) {
403409
if ((parts = chrome.exec(lines[i]))) {
404410
var isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line
411+
var isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line
412+
if (isEval && (submatch = chromeEval.exec(parts[2]))) {
413+
// throw out eval line/column and use top-most line/column number
414+
parts[2] = submatch[1]; // url
415+
parts[3] = submatch[2]; // line
416+
parts[4] = submatch[3]; // column
417+
}
405418
element = {
406419
'url': !isNative ? parts[2] : null,
407420
'func': parts[1] || UNKNOWN_FUNCTION,
@@ -418,6 +431,19 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
418431
'column': parts[4] ? +parts[4] : null
419432
};
420433
} else if ((parts = gecko.exec(lines[i]))) {
434+
var isEval = parts[3] && parts[3].indexOf(' > eval') > -1;
435+
if (isEval && (submatch = geckoEval.exec(parts[3]))) {
436+
// throw out eval line/column and use top-most line number
437+
parts[3] = submatch[1];
438+
parts[4] = submatch[2];
439+
parts[5] = null; // no column when eval
440+
} else if (i === 0 && !parts[5] && typeof ex.columnNumber !== 'undefined') {
441+
// FireFox uses this awesome columnNumber property for its top frame
442+
// Also note, Firefox's column number is 0-based and everything else expects 1-based,
443+
// so adding 1
444+
// NOTE: this hack doesn't work if top-most frame is eval
445+
stack[0].column = ex.columnNumber + 1;
446+
}
421447
element = {
422448
'url': parts[3],
423449
'func': parts[1] || UNKNOWN_FUNCTION,
@@ -440,13 +466,6 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
440466
return null;
441467
}
442468

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-
450469
return {
451470
'name': ex.name,
452471
'message': ex.message,

0 commit comments

Comments
 (0)