Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/testing/stacktrace.js
4503 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview Tools for parsing and pretty printing error stack traces.
9
*/
10
11
goog.setTestOnly('goog.testing.stacktrace');
12
goog.provide('goog.testing.stacktrace');
13
goog.provide('goog.testing.stacktrace.Frame');
14
15
16
17
/**
18
* Class representing one stack frame.
19
* @final
20
* @unrestricted
21
*/
22
goog.testing.stacktrace.Frame = class {
23
/**
24
* @param {string} context Context object, empty in case of global functions
25
* or if the browser doesn't provide this information.
26
* @param {string} name Function name, empty in case of anonymous functions.
27
* @param {string} alias Alias of the function if available. For example the
28
* function name will be 'c' and the alias will be 'b' if the function is
29
* defined as <code>a.b = function c() {};</code>.
30
* @param {string} path File path or URL including line number and optionally
31
* column number separated by colons.
32
*/
33
constructor(context, name, alias, path) {
34
'use strict';
35
this.context_ = context;
36
this.name_ = name;
37
this.alias_ = alias;
38
this.path_ = path;
39
}
40
41
/**
42
* @return {string} The function name or empty string if the function is
43
* anonymous and the object field which it's assigned to is unknown.
44
*/
45
getName() {
46
'use strict';
47
return this.name_;
48
}
49
50
/**
51
* @return {boolean} Whether the stack frame contains an anonymous function.
52
*/
53
isAnonymous() {
54
'use strict';
55
return !this.name_ || this.context_ == '[object Object]';
56
}
57
58
/**
59
* Brings one frame of the stack trace into a common format across browsers.
60
* @return {string} Pretty printed stack frame.
61
*/
62
toCanonicalString() {
63
'use strict';
64
const htmlEscape = goog.testing.stacktrace.htmlEscape_;
65
const deobfuscate = goog.testing.stacktrace.maybeDeobfuscateFunctionName_;
66
67
const canonical = [
68
this.context_ ? htmlEscape(this.context_) + '.' : '',
69
this.name_ ? htmlEscape(deobfuscate(this.name_)) : 'anonymous',
70
this.alias_ ? ' [as ' + htmlEscape(deobfuscate(this.alias_)) + ']' : ''
71
];
72
73
if (this.path_) {
74
canonical.push(' at ');
75
canonical.push(htmlEscape(this.path_));
76
}
77
return canonical.join('');
78
}
79
};
80
81
82
83
/**
84
* Maximum number of steps while the call chain is followed.
85
* @private {number}
86
* @const
87
*/
88
goog.testing.stacktrace.MAX_DEPTH_ = 20;
89
90
91
/**
92
* Maximum length of a string that can be matched with a RegExp on
93
* Firefox 3x. Exceeding this approximate length will cause string.match
94
* to exceed Firefox's stack quota. This situation can be encountered
95
* when goog.globalEval is invoked with a long argument; such as
96
* when loading a module.
97
* @private {number}
98
* @const
99
*/
100
goog.testing.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000;
101
102
103
/**
104
* RegExp pattern for JavaScript identifiers. We don't support Unicode
105
* identifiers defined in ECMAScript v3.
106
* @private {string}
107
* @const
108
*/
109
goog.testing.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*';
110
111
112
/**
113
* RegExp pattern for function name alias in the V8 stack trace.
114
* @private {string}
115
* @const
116
*/
117
goog.testing.stacktrace.V8_ALIAS_PATTERN_ =
118
'(?: \\[as (' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?';
119
120
121
/**
122
* RegExp pattern for the context of a function call in a V8 stack trace.
123
* Creates an optional submatch for the namespace identifier including the
124
* "new" keyword for constructor calls (e.g. "new foo.Bar").
125
* @private {string}
126
* @const
127
*/
128
goog.testing.stacktrace.V8_CONTEXT_PATTERN_ =
129
'(?:((?:new )?(?:\\[object Object\\]|' +
130
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +
131
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*))\\.)?';
132
133
134
/**
135
* RegExp pattern for function names and constructor calls in the V8 stack
136
* trace.
137
* @private {string}
138
* @const
139
*/
140
goog.testing.stacktrace.V8_FUNCTION_NAME_PATTERN_ =
141
'(?:new )?(?:' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ +
142
'|<anonymous>)';
143
144
145
/**
146
* RegExp pattern for function call in the V8 stack trace. Creates 3 submatches
147
* with context object (optional), function name and function alias (optional).
148
* @private {string}
149
* @const
150
*/
151
goog.testing.stacktrace.V8_FUNCTION_CALL_PATTERN_ = ' ' +
152
goog.testing.stacktrace.V8_CONTEXT_PATTERN_ + '(' +
153
goog.testing.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' +
154
goog.testing.stacktrace.V8_ALIAS_PATTERN_;
155
156
157
/**
158
* RegExp pattern for an URL + position inside the file.
159
* @private {string}
160
* @const
161
*/
162
goog.testing.stacktrace.URL_PATTERN_ =
163
'((?:http|https|file)://[^\\s)]+|javascript:.*)';
164
165
166
/**
167
* RegExp pattern for an URL + line number + column number in V8.
168
* The URL is either in submatch 1 or submatch 2.
169
* @private {string}
170
* @const
171
*/
172
goog.testing.stacktrace.CHROME_URL_PATTERN_ = ' (?:' +
173
'\\(unknown source\\)' +
174
'|' +
175
'\\(native\\)' +
176
'|' +
177
'\\((.+)\\)|(.+))';
178
179
180
/**
181
* Regular expression for parsing one stack frame in V8. For more information
182
* on V8 stack frame formats, see
183
* https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi.
184
* @private {!RegExp}
185
* @const
186
*/
187
goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp(
188
'^ at' +
189
'(?:' + goog.testing.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +
190
goog.testing.stacktrace.CHROME_URL_PATTERN_ + '$');
191
192
193
/**
194
* RegExp pattern for function call in the Firefox stack trace.
195
* Creates 2 submatches with function name (optional) and arguments.
196
*
197
* Modern FF produces stack traces like:
198
* foo@url:1:2
199
* a.b.foo@url:3:4
200
*
201
* @private {string}
202
* @const
203
*/
204
goog.testing.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ = '(' +
205
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +
206
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*' +
207
')?' +
208
'(\\(.*\\))?@';
209
210
211
/**
212
* Regular expression for parsing one stack frame in Firefox.
213
* @private {!RegExp}
214
* @const
215
*/
216
goog.testing.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp(
217
'^' + goog.testing.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ + '(?::0|' +
218
goog.testing.stacktrace.URL_PATTERN_ + ')$');
219
220
221
/**
222
* RegExp pattern for an anonymous function call in an Opera stack frame.
223
* Creates 2 (optional) submatches: the context object and function name.
224
* @private {string}
225
* @const
226
*/
227
goog.testing.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ =
228
'<anonymous function(?:\\: ' +
229
'(?:(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +
230
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' +
231
'(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '))?>';
232
233
234
/**
235
* RegExp pattern for a function call in an Opera stack frame.
236
* Creates 4 (optional) submatches: the function name (if not anonymous),
237
* the aliased context object and function name (if anonymous), and the
238
* function call arguments.
239
* @private {string}
240
* @const
241
*/
242
goog.testing.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ = '(?:(?:(' +
243
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')|' +
244
goog.testing.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ +
245
')(\\(.*\\)))?@';
246
247
248
/**
249
* Regular expression for parsing on stack frame in Opera 11.68 - 12.17.
250
* Newer versions of Opera use V8 and stack frames should match against
251
* goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_.
252
* @private {!RegExp}
253
* @const
254
*/
255
goog.testing.stacktrace.OPERA_STACK_FRAME_REGEXP_ = new RegExp(
256
'^' + goog.testing.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ +
257
goog.testing.stacktrace.URL_PATTERN_ + '?$');
258
259
260
/**
261
* Regular expression for finding the function name in its source.
262
* @private {!RegExp}
263
* @const
264
*/
265
goog.testing.stacktrace.FUNCTION_SOURCE_REGEXP_ = new RegExp(
266
'^function (' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')');
267
268
269
/**
270
* RegExp pattern for function call in a IE stack trace. This expression allows
271
* for identifiers like 'Anonymous function', 'eval code', and 'Global code'.
272
* @private {string}
273
* @const
274
*/
275
goog.testing.stacktrace.IE_FUNCTION_CALL_PATTERN_ = '(' +
276
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +
277
goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*' +
278
'(?:\\s+\\w+)*)';
279
280
281
/**
282
* Regular expression for parsing a stack frame in IE.
283
* @private {!RegExp}
284
* @const
285
*/
286
goog.testing.stacktrace.IE_STACK_FRAME_REGEXP_ = new RegExp(
287
'^ at ' + goog.testing.stacktrace.IE_FUNCTION_CALL_PATTERN_ + '\\s*\\(' +
288
'(' +
289
'eval code:[^)]*' +
290
'|' +
291
'Unknown script code:[^)]*' +
292
'|' + goog.testing.stacktrace.URL_PATTERN_ + ')\\)?$');
293
294
295
/**
296
* Creates a stack trace by following the call chain. Based on
297
* {@link goog.debug.getStacktrace}.
298
* @return {!Array<!goog.testing.stacktrace.Frame>} Stack frames.
299
* @private
300
* @suppress {es5Strict}
301
*/
302
goog.testing.stacktrace.followCallChain_ = function() {
303
'use strict';
304
const frames = [];
305
let fn = arguments.callee.caller;
306
let depth = 0;
307
308
while (fn && depth < goog.testing.stacktrace.MAX_DEPTH_) {
309
const fnString = Function.prototype.toString.call(fn);
310
const match =
311
fnString.match(goog.testing.stacktrace.FUNCTION_SOURCE_REGEXP_);
312
const functionName = match ? match[1] : '';
313
314
frames.push(new goog.testing.stacktrace.Frame('', functionName, '', ''));
315
316
317
try {
318
fn = fn.caller;
319
} catch (e) {
320
break;
321
}
322
depth++;
323
}
324
325
return frames;
326
};
327
328
329
/**
330
* Parses one stack frame.
331
* @param {string} frameStr The stack frame as string.
332
* @return {goog.testing.stacktrace.Frame} Stack frame object or null if the
333
* parsing failed.
334
* @private
335
*/
336
goog.testing.stacktrace.parseStackFrame_ = function(frameStr) {
337
'use strict';
338
// This match includes newer versions of Opera (15+).
339
let m = frameStr.match(goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_);
340
if (m) {
341
return new goog.testing.stacktrace.Frame(
342
m[1] || '', m[2] || '', m[3] || '', m[4] || m[5] || m[6] || '');
343
}
344
345
// TODO(johnlenz): remove this. It seems like if this was useful it would
346
// need to be before the V8 check.
347
if (frameStr.length >
348
goog.testing.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {
349
return null;
350
}
351
352
m = frameStr.match(goog.testing.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);
353
if (m) {
354
return new goog.testing.stacktrace.Frame('', m[1] || '', '', m[3] || '');
355
}
356
357
// Match against Presto Opera 11.68 - 12.17.
358
m = frameStr.match(goog.testing.stacktrace.OPERA_STACK_FRAME_REGEXP_);
359
if (m) {
360
return new goog.testing.stacktrace.Frame(
361
m[2] || '', m[1] || m[3] || '', '', m[5] || '');
362
}
363
364
m = frameStr.match(goog.testing.stacktrace.IE_STACK_FRAME_REGEXP_);
365
if (m) {
366
return new goog.testing.stacktrace.Frame('', m[1] || '', '', m[2] || '');
367
}
368
369
return null;
370
};
371
372
373
/**
374
* Function to deobfuscate function names.
375
* @type {function(string): string}
376
* @private
377
*/
378
goog.testing.stacktrace.deobfuscateFunctionName_;
379
380
381
/**
382
* Sets function to deobfuscate function names.
383
* @param {function(string): string} fn function to deobfuscate function names.
384
*/
385
goog.testing.stacktrace.setDeobfuscateFunctionName = function(fn) {
386
'use strict';
387
goog.testing.stacktrace.deobfuscateFunctionName_ = fn;
388
};
389
390
391
/**
392
* Deobfuscates a compiled function name with the function passed to
393
* {@link #setDeobfuscateFunctionName}. Returns the original function name if
394
* the deobfuscator hasn't been set.
395
* @param {string} name The function name to deobfuscate.
396
* @return {string} The deobfuscated function name.
397
* @private
398
*/
399
goog.testing.stacktrace.maybeDeobfuscateFunctionName_ = function(name) {
400
'use strict';
401
return goog.testing.stacktrace.deobfuscateFunctionName_ ?
402
goog.testing.stacktrace.deobfuscateFunctionName_(name) :
403
name;
404
};
405
406
407
/**
408
* Escapes the special character in HTML.
409
* @param {string} text Plain text.
410
* @return {string} Escaped text.
411
* @private
412
*/
413
goog.testing.stacktrace.htmlEscape_ = function(text) {
414
'use strict';
415
return text.replace(/&/g, '&amp;')
416
.replace(/</g, '&lt;')
417
.replace(/>/g, '&gt;')
418
.replace(/"/g, '&quot;');
419
};
420
421
422
/**
423
* Converts the stack frames into canonical format. Chops the beginning and the
424
* end of it which come from the testing environment, not from the test itself.
425
* @param {!Array<goog.testing.stacktrace.Frame>} frames The frames.
426
* @return {string} Canonical, pretty printed stack trace.
427
* @private
428
*/
429
goog.testing.stacktrace.framesToString_ = function(frames) {
430
'use strict';
431
// Removes the anonymous calls from the end of the stack trace (they come
432
// from testrunner.js, testcase.js and asserts.js), so the stack trace will
433
// end with the test... method.
434
let lastIndex = frames.length - 1;
435
while (frames[lastIndex] && frames[lastIndex].isAnonymous()) {
436
lastIndex--;
437
}
438
439
// Removes the beginning of the stack trace until the call of the private
440
// _assert function (inclusive), so the stack trace will begin with a public
441
// asserter. Does nothing if _assert is not present in the stack trace.
442
let privateAssertIndex = -1;
443
for (let i = 0; i < frames.length; i++) {
444
if (frames[i] && frames[i].getName() == '_assert') {
445
privateAssertIndex = i;
446
break;
447
}
448
}
449
450
const canonical = [];
451
for (let i = privateAssertIndex + 1; i <= lastIndex; i++) {
452
canonical.push('> ');
453
if (frames[i]) {
454
canonical.push(frames[i].toCanonicalString());
455
} else {
456
canonical.push('(unknown)');
457
}
458
canonical.push('\n');
459
}
460
return canonical.join('');
461
};
462
463
464
/**
465
* Parses the browser's native stack trace.
466
* @param {string} stack Stack trace.
467
* @return {!Array<goog.testing.stacktrace.Frame>} Stack frames. The
468
* unrecognized frames will be nulled out.
469
* @private
470
*/
471
goog.testing.stacktrace.parse_ = function(stack) {
472
'use strict';
473
const lines = stack.replace(/\s*$/, '').split('\n');
474
const frames = [];
475
for (let i = 0; i < lines.length; i++) {
476
frames.push(goog.testing.stacktrace.parseStackFrame_(lines[i]));
477
}
478
return frames;
479
};
480
481
482
/**
483
* Brings the stack trace into a common format across browsers.
484
* @param {string} stack Browser-specific stack trace.
485
* @return {string} Same stack trace in common format.
486
*/
487
goog.testing.stacktrace.canonicalize = function(stack) {
488
'use strict';
489
const frames = goog.testing.stacktrace.parse_(stack);
490
return goog.testing.stacktrace.framesToString_(frames);
491
};
492
493
494
/**
495
* Returns the native stack trace.
496
* @return {string|!Array<!CallSite>}
497
* @private
498
*/
499
goog.testing.stacktrace.getNativeStack_ = function() {
500
'use strict';
501
const tmpError = new Error();
502
if (tmpError.stack) {
503
return tmpError.stack;
504
}
505
506
// IE10 will only create a stack trace when the Error is thrown.
507
// We use null.x() to throw an exception because the closure compiler may
508
// replace "throw" with a function call in an attempt to minimize the binary
509
// size, which in turn has the side effect of adding an unwanted stack frame.
510
try {
511
null.x();
512
} catch (e) {
513
return e.stack;
514
}
515
return '';
516
};
517
518
519
/**
520
* Gets the native stack trace if available otherwise follows the call chain.
521
* @return {string} The stack trace in canonical format.
522
*/
523
goog.testing.stacktrace.get = function() {
524
'use strict';
525
const stack = goog.testing.stacktrace.getNativeStack_();
526
let frames;
527
if (!stack) {
528
frames = goog.testing.stacktrace.followCallChain_();
529
} else if (Array.isArray(stack)) {
530
frames = goog.testing.stacktrace.callSitesToFrames_(stack);
531
} else {
532
frames = goog.testing.stacktrace.parse_(stack);
533
}
534
return goog.testing.stacktrace.framesToString_(frames);
535
};
536
537
538
/**
539
* Converts an array of CallSite (elements of a stack trace in V8) to an array
540
* of Frames.
541
* @param {!Array<!CallSite>} stack The stack as an array of CallSites.
542
* @return {!Array<!goog.testing.stacktrace.Frame>} The stack as an array of
543
* Frames.
544
* @private
545
*/
546
goog.testing.stacktrace.callSitesToFrames_ = function(stack) {
547
'use strict';
548
const frames = [];
549
for (let i = 0; i < stack.length; i++) {
550
const callSite = stack[i];
551
const functionName = callSite.getFunctionName() || 'unknown';
552
const fileName = callSite.getFileName();
553
const path = fileName ? fileName + ':' + callSite.getLineNumber() + ':' +
554
callSite.getColumnNumber() :
555
'unknown';
556
frames.push(new goog.testing.stacktrace.Frame('', functionName, '', path));
557
}
558
return frames;
559
};
560
561
562
goog.exportSymbol(
563
'setDeobfuscateFunctionName',
564
goog.testing.stacktrace.setDeobfuscateFunctionName);
565
566