forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprepare_stack_trace.js
More file actions
203 lines (187 loc) · 7.18 KB
/
Copy pathprepare_stack_trace.js
File metadata and controls
203 lines (187 loc) · 7.18 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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
'use strict';
const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
ErrorPrototypeToString,
SafeStringIterator,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeStartsWith,
} = primordials;
let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => {
debug = fn;
});
const { getStringWidth } = require('internal/util/inspect');
const { findSourceMap, getSourceLine } = require('internal/source_map/source_map_cache');
const {
kIsNodeError,
} = require('internal/errors');
const { fileURLToPath } = require('internal/url');
const { setGetSourceMapErrorSource } = internalBinding('errors');
const kStackLineAt = '\n at ';
// Create a prettified stacktrace, inserting context from source maps
// if possible.
function prepareStackTraceWithSourceMaps(error, trace) {
let errorString;
if (kIsNodeError in error) {
errorString = `${error.name} [${error.code}]: ${error.message}`;
} else {
errorString = ErrorPrototypeToString(error);
}
if (trace.length === 0) {
return errorString;
}
let lastSourceMap;
let lastFileName;
const preparedTrace = ArrayPrototypeJoin(ArrayPrototypeMap(trace, (callSite, i) => {
try {
// A stack trace will often have several call sites in a row within the
// same file, cache the source map and file content accordingly:
let fileName = callSite.getFileName();
if (fileName === undefined) {
fileName = callSite.getEvalOrigin();
}
const sm = fileName === lastFileName ?
lastSourceMap :
findSourceMap(fileName);
// Only when a source map is found, cache it for the next iteration.
// This is a performance optimization to avoid interleaving with JS builtin function
// invalidating the cache.
// - at myFunc (file:///path/to/file.js:1:2)
// - at Array.map (<anonymous>)
// - at myFunc (file:///path/to/file.js:3:4)
if (sm) {
lastSourceMap = sm;
lastFileName = fileName;
return `${kStackLineAt}${serializeJSStackFrame(sm, callSite, trace[i + 1])}`;
}
} catch (err) {
debug(err);
}
return `${kStackLineAt}${callSite}`;
}), '');
return `${errorString}${preparedTrace}`;
}
/**
* Serialize a single call site in the stack trace.
* Refer to SerializeJSStackFrame in deps/v8/src/objects/call-site-info.cc for
* more details about the default ToString(CallSite).
* The CallSite API is documented at https://v8.dev/docs/stack-trace-api.
* @param {import('internal/source_map/source_map').SourceMap} sm
* @param {CallSite} callSite - the CallSite object to be serialized
* @param {CallSite} callerCallSite - caller site info
* @returns {string} - the serialized call site
*/
function serializeJSStackFrame(sm, callSite, callerCallSite) {
// Source Map V3 lines/columns start at 0/0 whereas stack traces
// start at 1/1:
const {
originalLine,
originalColumn,
originalSource,
} = sm.findEntry(callSite.getLineNumber() - 1, callSite.getColumnNumber() - 1);
if (originalSource === undefined || originalLine === undefined ||
originalColumn === undefined) {
return `${callSite}`;
}
const name = getOriginalSymbolName(sm, callSite, callerCallSite);
const originalSourceNoScheme =
StringPrototypeStartsWith(originalSource, 'file://') ?
fileURLToPath(originalSource) : originalSource;
// Construct call site name based on: v8.dev/docs/stack-trace-api:
const fnName = callSite.getFunctionName() ?? callSite.getMethodName();
let prefix = '';
if (callSite.isAsync()) {
// Promise aggregation operation frame has no locations. This must be an
// async stack frame.
prefix = 'async ';
} else if (callSite.isConstructor()) {
prefix = 'new ';
}
const typeName = callSite.getTypeName();
const namePrefix = typeName !== null && typeName !== 'global' ? `${typeName}.` : '';
const originalName = `${fnName || '<anonymous>'}`;
const mappedName = `${namePrefix}${name || originalName}` || '';
// Replace the transpiled call site with the original:
return `${prefix}${mappedName} (` +
`${originalSourceNoScheme}:${originalLine + 1}:` +
`${originalColumn + 1})`;
}
// Transpilers may have removed the original symbol name used in the stack
// trace, if possible restore it from the names field of the source map:
function getOriginalSymbolName(sourceMap, callSite, callerCallSite) {
// First check for a symbol name associated with the enclosing function:
const enclosingEntry = sourceMap.findEntry(
callSite.getEnclosingLineNumber() - 1,
callSite.getEnclosingColumnNumber() - 1,
);
if (enclosingEntry.name) return enclosingEntry.name;
// Fallback to using the symbol name attached to the caller site:
const currentFileName = callSite.getFileName();
if (callerCallSite && currentFileName === callerCallSite.getFileName()) {
const { name } = sourceMap.findEntry(
callerCallSite.getLineNumber() - 1,
callerCallSite.getColumnNumber() - 1,
);
return name;
}
}
/**
* Return a snippet of code from where the exception was originally thrown
* above the stack trace. This called from GetErrorSource in node_errors.cc.
* @param {import('internal/source_map/source_map').SourceMap} sourceMap - the source map to be used
* @param {string} originalSourcePath - path or url of the original source
* @param {number} originalLine - line number in the original source
* @param {number} originalColumn - column number in the original source
* @returns {string | undefined} - the exact line in the source content or undefined if file not found
*/
function getErrorSource(
sourceMap,
originalSourcePath,
originalLine,
originalColumn,
) {
const line = getSourceLine(sourceMap, originalSourcePath, originalLine);
if (!line) {
return;
}
const originalSourcePathNoScheme =
StringPrototypeStartsWith(originalSourcePath, 'file://') ?
fileURLToPath(originalSourcePath) : originalSourcePath;
// Display ^ in appropriate position, regardless of whether tabs or
// spaces are used:
let prefix = '';
for (const character of new SafeStringIterator(
StringPrototypeSlice(line, 0, originalColumn + 1))) {
prefix += character === '\t' ? '\t' :
StringPrototypeRepeat(' ', getStringWidth(character));
}
prefix = StringPrototypeSlice(prefix, 0, -1); // The last character is '^'.
const exceptionLine =
`${originalSourcePathNoScheme}:${originalLine + 1}\n${line}\n${prefix}^\n`;
return exceptionLine;
}
/**
* Retrieve exact line in the original source code from the source map's `sources` list or disk.
* @param {string} fileName - actual file name
* @param {number} lineNumber - actual line number
* @param {number} columnNumber - actual column number
* @returns {string | undefined} - the source content or undefined if file not found
*/
function getSourceMapErrorSource(fileName, lineNumber, columnNumber) {
const sm = findSourceMap(fileName);
if (sm === undefined) {
return;
}
const {
originalLine,
originalColumn,
originalSource,
} = sm.findEntry(lineNumber - 1, columnNumber);
const errorSource = getErrorSource(sm, originalSource, originalLine, originalColumn);
return errorSource;
}
setGetSourceMapErrorSource(getSourceMapErrorSource);
module.exports = {
prepareStackTraceWithSourceMaps,
};