forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse-stack.js
More file actions
155 lines (139 loc) · 5.39 KB
/
parse-stack.js
File metadata and controls
155 lines (139 loc) · 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
const _ = require('underscore');
// Given an Error (eg, 'new Error'), return the stack associated with
// that error as an array. More recently called functions appear first
// and each element is an object with keys:
// - file: filename as it appears in the stack
// - line: 1-indexed line number in file, as a Number
// - column: 1-indexed column in line, as a Number
// - func: name of the function in the frame (maybe null)
//
// Accomplishes this by parsing the text representation of the stack
// with regular expressions. Unlikely to work anywhere but v8.
//
// If a function on the stack has been marked with mark(), don't
// return anything past that function. We call this the "user portion"
// of the stack.
export function parse(err) {
// at least the first line is the exception
const frames = err.stack.split("\n").slice(1)
// longjohn adds lines of the form '---' (45 times) to separate
// the trace across async boundaries. It's not clear if we need to
// separate the trace in the same way we do for future boundaries below
// (it's not clear that that code is still useful either)
// so for now, we'll just remove such lines
.filter(f => ! f.match(/^\-{45}$/));
// " - - - - -"
// This is something added when you throw an Error through a Future. The
// stack above the dashes is the stack of the 'wait' call; the stack below
// is the stack inside the fiber where the Error is originally
// constructed.
// XXX This code assumes that the stack trace can only be split once. It's not
// clear whether this can happen multiple times.
const indexOfFiberSplit = frames.indexOf(' - - - - -');
if (indexOfFiberSplit === -1) {
// This is a normal stack trace, not a split fiber stack trace
return {
outsideFiber: parseStackFrames(frames)
}
}
// If this is a split stack trace from a future, parse the frames above and
// below the split separately.
const outsideFiber = parseStackFrames(frames);
const insideFiber = parseStackFrames(frames.slice(indexOfFiberSplit + 1));
return {
insideFiber,
outsideFiber
};
}
// Decorator. Mark the point at which a stack trace returned by
// parse() should stop: no frames earlier than this point will be
// included in the parsed stack. Confusingly, in the argot of the
// times, you'd say that frames "higher up" than this or "above" this
// will not be returned, but you'd also say that those frames are "at
// the bottom of the stack". Frames below the bottom are the outer
// context of the framework running the user's code.
export function markBottom(f) {
/* eslint-disable camelcase */
return function __bottom_mark__() {
return f.apply(this, arguments);
};
/* eslint-enable camelcase */
}
// Decorator. Mark the point at which a stack trace returned by
// parse() should begin: no frames later than this point will be
// included in the parsed stack. The opposite of markBottom().
// Frames above the top are helper functions defined by the
// framework and executed by user code whose internal behavior
// should not be exposed.
export function markTop(f) {
/* eslint-disable camelcase */
return function __top_mark__() {
return f.apply(this, arguments);
};
/* eslint-enable camelcase */
}
function parseStackFrames(frames) {
let stop = false;
let ret = [];
frames.some(frame => {
if (stop) {
return true;
}
let m;
/* eslint-disable max-len */
if (m = frame.match(/^\s*at\s*((new )?.+?)\s*(\[as\s*([^\]]*)\]\s*)?\((.*?)(:(\d+))?(:(\d+))?\)\s*$/)) {
// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
// " at My.Function (/path/to/myfile.js:532:39)"
// " at Array.forEach (native)"
// " at new My.Class (file.js:1:2)"
// " at [object Object].main.registerCommand.name [as func] (meteor/tools/commands.js:1225:19)"
// " at __top_mark__ [as matchErr] (meteor/tools/parse-stack.js:82:14)"
//
// In that last example, it is not at all clear to me what the
// 'as' stanza refers to, but it is in m[3] if you find a use for it.
if (m[1].match(/(?:^|\.)__top_mark__$/)) {
// m[1] could be Object.__top_mark__ or something like that
// depending on where exactly you put the function returned by
// markTop
ret = [];
return;
}
if (m[1].match(/(?:^|\.)__bottom_mark__$/)) {
return stop = true;
}
ret.push({
func: m[1],
file: m[5],
line: m[7] ? +m[7] : undefined,
column: m[9] ? +m[9] : undefined
});
return;
}
/* eslint-enable max-len */
if (m = frame.match(/^\s*at\s+(.+?)(:(\d+))?(:(\d+))?\s*$/)) {
// " at /path/to/myfile.js:532:39"
ret.push({
file: m[1],
line: m[3] ? +m[3] : undefined,
column: m[5] ? +m[5] : undefined
});
return;
}
if (m = frame.match(/^\s*-\s*-\s*-\s*-\s*-\s*$/)) {
// Stop parsing if we reach a stack split from a Future
return stop = true;
}
if (frame.startsWith(" => awaited here:")) {
// The meteor-promise library inserts " => awaited here:" lines to
// indicate async boundaries.
return stop = true;
}
if (_.isEmpty(ret)) {
// We haven't found any stack frames, so probably we have newlines in the
// error message. Just skip this line.
return;
}
throw new Error("Couldn't parse stack frame: '" + frame + "'");
});
return ret;
}