Path: blob/trunk/third_party/closure/goog/testing/stacktrace.js
4503 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Tools for parsing and pretty printing error stack traces.8*/910goog.setTestOnly('goog.testing.stacktrace');11goog.provide('goog.testing.stacktrace');12goog.provide('goog.testing.stacktrace.Frame');13141516/**17* Class representing one stack frame.18* @final19* @unrestricted20*/21goog.testing.stacktrace.Frame = class {22/**23* @param {string} context Context object, empty in case of global functions24* or if the browser doesn't provide this information.25* @param {string} name Function name, empty in case of anonymous functions.26* @param {string} alias Alias of the function if available. For example the27* function name will be 'c' and the alias will be 'b' if the function is28* defined as <code>a.b = function c() {};</code>.29* @param {string} path File path or URL including line number and optionally30* column number separated by colons.31*/32constructor(context, name, alias, path) {33'use strict';34this.context_ = context;35this.name_ = name;36this.alias_ = alias;37this.path_ = path;38}3940/**41* @return {string} The function name or empty string if the function is42* anonymous and the object field which it's assigned to is unknown.43*/44getName() {45'use strict';46return this.name_;47}4849/**50* @return {boolean} Whether the stack frame contains an anonymous function.51*/52isAnonymous() {53'use strict';54return !this.name_ || this.context_ == '[object Object]';55}5657/**58* Brings one frame of the stack trace into a common format across browsers.59* @return {string} Pretty printed stack frame.60*/61toCanonicalString() {62'use strict';63const htmlEscape = goog.testing.stacktrace.htmlEscape_;64const deobfuscate = goog.testing.stacktrace.maybeDeobfuscateFunctionName_;6566const canonical = [67this.context_ ? htmlEscape(this.context_) + '.' : '',68this.name_ ? htmlEscape(deobfuscate(this.name_)) : 'anonymous',69this.alias_ ? ' [as ' + htmlEscape(deobfuscate(this.alias_)) + ']' : ''70];7172if (this.path_) {73canonical.push(' at ');74canonical.push(htmlEscape(this.path_));75}76return canonical.join('');77}78};79808182/**83* Maximum number of steps while the call chain is followed.84* @private {number}85* @const86*/87goog.testing.stacktrace.MAX_DEPTH_ = 20;888990/**91* Maximum length of a string that can be matched with a RegExp on92* Firefox 3x. Exceeding this approximate length will cause string.match93* to exceed Firefox's stack quota. This situation can be encountered94* when goog.globalEval is invoked with a long argument; such as95* when loading a module.96* @private {number}97* @const98*/99goog.testing.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000;100101102/**103* RegExp pattern for JavaScript identifiers. We don't support Unicode104* identifiers defined in ECMAScript v3.105* @private {string}106* @const107*/108goog.testing.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*';109110111/**112* RegExp pattern for function name alias in the V8 stack trace.113* @private {string}114* @const115*/116goog.testing.stacktrace.V8_ALIAS_PATTERN_ =117'(?: \\[as (' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?';118119120/**121* RegExp pattern for the context of a function call in a V8 stack trace.122* Creates an optional submatch for the namespace identifier including the123* "new" keyword for constructor calls (e.g. "new foo.Bar").124* @private {string}125* @const126*/127goog.testing.stacktrace.V8_CONTEXT_PATTERN_ =128'(?:((?:new )?(?:\\[object Object\\]|' +129goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +130goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*))\\.)?';131132133/**134* RegExp pattern for function names and constructor calls in the V8 stack135* trace.136* @private {string}137* @const138*/139goog.testing.stacktrace.V8_FUNCTION_NAME_PATTERN_ =140'(?:new )?(?:' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ +141'|<anonymous>)';142143144/**145* RegExp pattern for function call in the V8 stack trace. Creates 3 submatches146* with context object (optional), function name and function alias (optional).147* @private {string}148* @const149*/150goog.testing.stacktrace.V8_FUNCTION_CALL_PATTERN_ = ' ' +151goog.testing.stacktrace.V8_CONTEXT_PATTERN_ + '(' +152goog.testing.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' +153goog.testing.stacktrace.V8_ALIAS_PATTERN_;154155156/**157* RegExp pattern for an URL + position inside the file.158* @private {string}159* @const160*/161goog.testing.stacktrace.URL_PATTERN_ =162'((?:http|https|file)://[^\\s)]+|javascript:.*)';163164165/**166* RegExp pattern for an URL + line number + column number in V8.167* The URL is either in submatch 1 or submatch 2.168* @private {string}169* @const170*/171goog.testing.stacktrace.CHROME_URL_PATTERN_ = ' (?:' +172'\\(unknown source\\)' +173'|' +174'\\(native\\)' +175'|' +176'\\((.+)\\)|(.+))';177178179/**180* Regular expression for parsing one stack frame in V8. For more information181* on V8 stack frame formats, see182* https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi.183* @private {!RegExp}184* @const185*/186goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp(187'^ at' +188'(?:' + goog.testing.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +189goog.testing.stacktrace.CHROME_URL_PATTERN_ + '$');190191192/**193* RegExp pattern for function call in the Firefox stack trace.194* Creates 2 submatches with function name (optional) and arguments.195*196* Modern FF produces stack traces like:197* foo@url:1:2198* a.b.foo@url:3:4199*200* @private {string}201* @const202*/203goog.testing.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ = '(' +204goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +205goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*' +206')?' +207'(\\(.*\\))?@';208209210/**211* Regular expression for parsing one stack frame in Firefox.212* @private {!RegExp}213* @const214*/215goog.testing.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp(216'^' + goog.testing.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ + '(?::0|' +217goog.testing.stacktrace.URL_PATTERN_ + ')$');218219220/**221* RegExp pattern for an anonymous function call in an Opera stack frame.222* Creates 2 (optional) submatches: the context object and function name.223* @private {string}224* @const225*/226goog.testing.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ =227'<anonymous function(?:\\: ' +228'(?:(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +229goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' +230'(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '))?>';231232233/**234* RegExp pattern for a function call in an Opera stack frame.235* Creates 4 (optional) submatches: the function name (if not anonymous),236* the aliased context object and function name (if anonymous), and the237* function call arguments.238* @private {string}239* @const240*/241goog.testing.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ = '(?:(?:(' +242goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')|' +243goog.testing.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ +244')(\\(.*\\)))?@';245246247/**248* Regular expression for parsing on stack frame in Opera 11.68 - 12.17.249* Newer versions of Opera use V8 and stack frames should match against250* goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_.251* @private {!RegExp}252* @const253*/254goog.testing.stacktrace.OPERA_STACK_FRAME_REGEXP_ = new RegExp(255'^' + goog.testing.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ +256goog.testing.stacktrace.URL_PATTERN_ + '?$');257258259/**260* Regular expression for finding the function name in its source.261* @private {!RegExp}262* @const263*/264goog.testing.stacktrace.FUNCTION_SOURCE_REGEXP_ = new RegExp(265'^function (' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')');266267268/**269* RegExp pattern for function call in a IE stack trace. This expression allows270* for identifiers like 'Anonymous function', 'eval code', and 'Global code'.271* @private {string}272* @const273*/274goog.testing.stacktrace.IE_FUNCTION_CALL_PATTERN_ = '(' +275goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\.' +276goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*' +277'(?:\\s+\\w+)*)';278279280/**281* Regular expression for parsing a stack frame in IE.282* @private {!RegExp}283* @const284*/285goog.testing.stacktrace.IE_STACK_FRAME_REGEXP_ = new RegExp(286'^ at ' + goog.testing.stacktrace.IE_FUNCTION_CALL_PATTERN_ + '\\s*\\(' +287'(' +288'eval code:[^)]*' +289'|' +290'Unknown script code:[^)]*' +291'|' + goog.testing.stacktrace.URL_PATTERN_ + ')\\)?$');292293294/**295* Creates a stack trace by following the call chain. Based on296* {@link goog.debug.getStacktrace}.297* @return {!Array<!goog.testing.stacktrace.Frame>} Stack frames.298* @private299* @suppress {es5Strict}300*/301goog.testing.stacktrace.followCallChain_ = function() {302'use strict';303const frames = [];304let fn = arguments.callee.caller;305let depth = 0;306307while (fn && depth < goog.testing.stacktrace.MAX_DEPTH_) {308const fnString = Function.prototype.toString.call(fn);309const match =310fnString.match(goog.testing.stacktrace.FUNCTION_SOURCE_REGEXP_);311const functionName = match ? match[1] : '';312313frames.push(new goog.testing.stacktrace.Frame('', functionName, '', ''));314315316try {317fn = fn.caller;318} catch (e) {319break;320}321depth++;322}323324return frames;325};326327328/**329* Parses one stack frame.330* @param {string} frameStr The stack frame as string.331* @return {goog.testing.stacktrace.Frame} Stack frame object or null if the332* parsing failed.333* @private334*/335goog.testing.stacktrace.parseStackFrame_ = function(frameStr) {336'use strict';337// This match includes newer versions of Opera (15+).338let m = frameStr.match(goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_);339if (m) {340return new goog.testing.stacktrace.Frame(341m[1] || '', m[2] || '', m[3] || '', m[4] || m[5] || m[6] || '');342}343344// TODO(johnlenz): remove this. It seems like if this was useful it would345// need to be before the V8 check.346if (frameStr.length >347goog.testing.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {348return null;349}350351m = frameStr.match(goog.testing.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);352if (m) {353return new goog.testing.stacktrace.Frame('', m[1] || '', '', m[3] || '');354}355356// Match against Presto Opera 11.68 - 12.17.357m = frameStr.match(goog.testing.stacktrace.OPERA_STACK_FRAME_REGEXP_);358if (m) {359return new goog.testing.stacktrace.Frame(360m[2] || '', m[1] || m[3] || '', '', m[5] || '');361}362363m = frameStr.match(goog.testing.stacktrace.IE_STACK_FRAME_REGEXP_);364if (m) {365return new goog.testing.stacktrace.Frame('', m[1] || '', '', m[2] || '');366}367368return null;369};370371372/**373* Function to deobfuscate function names.374* @type {function(string): string}375* @private376*/377goog.testing.stacktrace.deobfuscateFunctionName_;378379380/**381* Sets function to deobfuscate function names.382* @param {function(string): string} fn function to deobfuscate function names.383*/384goog.testing.stacktrace.setDeobfuscateFunctionName = function(fn) {385'use strict';386goog.testing.stacktrace.deobfuscateFunctionName_ = fn;387};388389390/**391* Deobfuscates a compiled function name with the function passed to392* {@link #setDeobfuscateFunctionName}. Returns the original function name if393* the deobfuscator hasn't been set.394* @param {string} name The function name to deobfuscate.395* @return {string} The deobfuscated function name.396* @private397*/398goog.testing.stacktrace.maybeDeobfuscateFunctionName_ = function(name) {399'use strict';400return goog.testing.stacktrace.deobfuscateFunctionName_ ?401goog.testing.stacktrace.deobfuscateFunctionName_(name) :402name;403};404405406/**407* Escapes the special character in HTML.408* @param {string} text Plain text.409* @return {string} Escaped text.410* @private411*/412goog.testing.stacktrace.htmlEscape_ = function(text) {413'use strict';414return text.replace(/&/g, '&')415.replace(/</g, '<')416.replace(/>/g, '>')417.replace(/"/g, '"');418};419420421/**422* Converts the stack frames into canonical format. Chops the beginning and the423* end of it which come from the testing environment, not from the test itself.424* @param {!Array<goog.testing.stacktrace.Frame>} frames The frames.425* @return {string} Canonical, pretty printed stack trace.426* @private427*/428goog.testing.stacktrace.framesToString_ = function(frames) {429'use strict';430// Removes the anonymous calls from the end of the stack trace (they come431// from testrunner.js, testcase.js and asserts.js), so the stack trace will432// end with the test... method.433let lastIndex = frames.length - 1;434while (frames[lastIndex] && frames[lastIndex].isAnonymous()) {435lastIndex--;436}437438// Removes the beginning of the stack trace until the call of the private439// _assert function (inclusive), so the stack trace will begin with a public440// asserter. Does nothing if _assert is not present in the stack trace.441let privateAssertIndex = -1;442for (let i = 0; i < frames.length; i++) {443if (frames[i] && frames[i].getName() == '_assert') {444privateAssertIndex = i;445break;446}447}448449const canonical = [];450for (let i = privateAssertIndex + 1; i <= lastIndex; i++) {451canonical.push('> ');452if (frames[i]) {453canonical.push(frames[i].toCanonicalString());454} else {455canonical.push('(unknown)');456}457canonical.push('\n');458}459return canonical.join('');460};461462463/**464* Parses the browser's native stack trace.465* @param {string} stack Stack trace.466* @return {!Array<goog.testing.stacktrace.Frame>} Stack frames. The467* unrecognized frames will be nulled out.468* @private469*/470goog.testing.stacktrace.parse_ = function(stack) {471'use strict';472const lines = stack.replace(/\s*$/, '').split('\n');473const frames = [];474for (let i = 0; i < lines.length; i++) {475frames.push(goog.testing.stacktrace.parseStackFrame_(lines[i]));476}477return frames;478};479480481/**482* Brings the stack trace into a common format across browsers.483* @param {string} stack Browser-specific stack trace.484* @return {string} Same stack trace in common format.485*/486goog.testing.stacktrace.canonicalize = function(stack) {487'use strict';488const frames = goog.testing.stacktrace.parse_(stack);489return goog.testing.stacktrace.framesToString_(frames);490};491492493/**494* Returns the native stack trace.495* @return {string|!Array<!CallSite>}496* @private497*/498goog.testing.stacktrace.getNativeStack_ = function() {499'use strict';500const tmpError = new Error();501if (tmpError.stack) {502return tmpError.stack;503}504505// IE10 will only create a stack trace when the Error is thrown.506// We use null.x() to throw an exception because the closure compiler may507// replace "throw" with a function call in an attempt to minimize the binary508// size, which in turn has the side effect of adding an unwanted stack frame.509try {510null.x();511} catch (e) {512return e.stack;513}514return '';515};516517518/**519* Gets the native stack trace if available otherwise follows the call chain.520* @return {string} The stack trace in canonical format.521*/522goog.testing.stacktrace.get = function() {523'use strict';524const stack = goog.testing.stacktrace.getNativeStack_();525let frames;526if (!stack) {527frames = goog.testing.stacktrace.followCallChain_();528} else if (Array.isArray(stack)) {529frames = goog.testing.stacktrace.callSitesToFrames_(stack);530} else {531frames = goog.testing.stacktrace.parse_(stack);532}533return goog.testing.stacktrace.framesToString_(frames);534};535536537/**538* Converts an array of CallSite (elements of a stack trace in V8) to an array539* of Frames.540* @param {!Array<!CallSite>} stack The stack as an array of CallSites.541* @return {!Array<!goog.testing.stacktrace.Frame>} The stack as an array of542* Frames.543* @private544*/545goog.testing.stacktrace.callSitesToFrames_ = function(stack) {546'use strict';547const frames = [];548for (let i = 0; i < stack.length; i++) {549const callSite = stack[i];550const functionName = callSite.getFunctionName() || 'unknown';551const fileName = callSite.getFileName();552const path = fileName ? fileName + ':' + callSite.getLineNumber() + ':' +553callSite.getColumnNumber() :554'unknown';555frames.push(new goog.testing.stacktrace.Frame('', functionName, '', path));556}557return frames;558};559560561goog.exportSymbol(562'setDeobfuscateFunctionName',563goog.testing.stacktrace.setDeobfuscateFunctionName);564565566