1 //Domain Public by Eric Wendelin http://eriwen.com/ (2008) 2// Luke Smith http://lucassmith.name/ (2008) 3// Loic Dachary <[email protected]> (2008) 4// Johan Euphrosine <[email protected]> (2008) 5// Ãyvind Sean Kinsey http://kinsey.no/blog (2010) 6// Victor Homyakov (2010) 7// 8// Information and discussions 9// http://jspoker.pokersource.info/skin/test-printstacktrace.html 10// http://eriwen.com/javascript/js-stack-trace/ 11// http://eriwen.com/javascript/stacktrace-update/ 12// http://pastie.org/253058 13// 14// guessFunctionNameFromLines comes from firebug 15// 16// Software License Agreement (BSD License) 17// 18// Copyright (c) 2007, Parakey Inc. 19// All rights reserved. 20// 21// Redistribution and use of this software in source and binary forms, with or without modification, 22// are permitted provided that the following conditions are met: 23// 24// * Redistributions of source code must retain the above 25// copyright notice, this list of conditions and the 26// following disclaimer. 27// 28// * Redistributions in binary form must reproduce the above 29// copyright notice, this list of conditions and the 30// following disclaimer in the documentation and/or other 31// materials provided with the distribution. 32// 33// * Neither the name of Parakey Inc. nor the names of its 34// contributors may be used to endorse or promote products 35// derived from this software without specific prior 36// written permission of Parakey Inc. 37// 38// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 39// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 40// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 41// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 42// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 43// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 44// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 45// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46 47/** 48 * Main function giving a function stack trace with a forced or passed in Error 49 * 50 * @cfg {Error} e The error to create a stacktrace from (optional) 51 * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions 52 * @return {Array} of Strings with functions, lines, files, and arguments where possible 53 */ 54function printStackTrace(options) { 55 var ex = (options && options.e) ? options.e : null; 56 var guess = options ? !!options.guess : true; 57 58 var p = new printStackTrace.implementation(); 59 var result = p.run(ex); 60 return (guess) ? p.guessFunctions(result) : result; 61} 62 63printStackTrace.implementation = function() {}; 64 65printStackTrace.implementation.prototype = { 66 run: function(ex) { 67 ex = ex || 68 (function() { 69 try { 70 var _err = __undef__ << 1; 71 } catch (e) { 72 return e; 73 } 74 })(); 75 // Use either the stored mode, or resolve it 76 var mode = this._mode || this.mode(ex); 77 if (mode === 'other') { 78 return this.other(arguments.callee); 79 } else { 80 return this[mode](ex); 81 } 82 }, 83 84 /** 85 * @return {String} mode of operation for the environment in question. 86 */ 87 mode: function(e) { 88 if (e['arguments']) { 89 return (this._mode = 'chrome'); 90 } else if (window.opera && e.stacktrace) { 91 return (this._mode = 'opera10'); 92 } else if (e.stack) { 93 return (this._mode = 'firefox'); 94 } else if (window.opera && !('stacktrace' in e)) { //Opera 9- 95 return (this._mode = 'opera'); 96 } 97 return (this._mode = 'other'); 98 }, 99 100 /** 101 * Given a context, function name, and callback function, overwrite it so that it calls 102 * printStackTrace() first with a callback and then runs the rest of the body. 103 * 104 * @param {Object} context of execution (e.g. window) 105 * @param {String} functionName to instrument 106 * @param {Function} function to call with a stack trace on invocation 107 */ 108 instrumentFunction: function(context, functionName, callback) { 109 context = context || window; 110 context['_old' + functionName] = context[functionName]; 111 context[functionName] = function() { 112 callback.call(this, printStackTrace()); 113 return context['_old' + functionName].apply(this, arguments); 114 }; 115 context[functionName]._instrumented = true; 116 }, 117 118 /** 119 * Given a context and function name of a function that has been 120 * instrumented, revert the function to it's original (non-instrumented) 121 * state. 122 * 123 * @param {Object} context of execution (e.g. window) 124 * @param {String} functionName to de-instrument 125 */ 126 deinstrumentFunction: function(context, functionName) { 127 if (context[functionName].constructor === Function && 128 context[functionName]._instrumented && 129 context['_old' + functionName].constructor === Function) { 130 context[functionName] = context['_old' + functionName]; 131 } 132 }, 133 134 /** 135 * Given an Error object, return a formatted Array based on Chrome's stack string. 136 * 137 * @param e - Error object to inspect 138 * @return Array<String> of function calls, files and line numbers 139 */ 140 chrome: function(e) { 141 return e.stack.replace(/^[^\(]+?[\n$]/gm, '').replace(/^\s+at\s+/gm, '').replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').split('\n'); 142 }, 143 144 /** 145 * Given an Error object, return a formatted Array based on Firefox's stack string. 146 * 147 * @param e - Error object to inspect 148 * @return Array<String> of function calls, files and line numbers 149 */ 150 firefox: function(e) { 151 return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n'); 152 }, 153 154 /** 155 * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. 156 * 157 * @param e - Error object to inspect 158 * @return Array<String> of function calls, files and line numbers 159 */ 160 opera10: function(e) { 161 var stack = e.stacktrace; 162 var lines = stack.split('\n'), ANON = '{anonymous}', 163 lineRE = /.*line (\d+), column (\d+) in ((<anonymous function\:?\s*(\S+))|([^\(]+)\([^\)]*\))(?: in )?(.*)\s*$/i, i, j, len; 164 for (i = 2, j = 0, len = lines.length; i < len - 2; i++) { 165 if (lineRE.test(lines[i])) { 166 var location = RegExp.$6 + ':' + RegExp.$1 + ':' + RegExp.$2; 167 var fnName = RegExp.$3; 168 fnName = fnName.replace(/<anonymous function\:?\s?(\S+)?>/g, ANON); 169 lines[j++] = fnName + '@' + location; 170 } 171 } 172 173 lines.splice(j, lines.length - j); 174 return lines; 175 }, 176 177 // Opera 7.x-9.x only! 178 opera: function(e) { 179 var lines = e.message.split('\n'), ANON = '{anonymous}', 180 lineRE = /Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i, 181 i, j, len; 182 183 for (i = 4, j = 0, len = lines.length; i < len; i += 2) { 184 //TODO: RegExp.exec() would probably be cleaner here 185 if (lineRE.test(lines[i])) { 186 lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i + 1].replace(/^\s+/, ''); 187 } 188 } 189 190 lines.splice(j, lines.length - j); 191 return lines; 192 }, 193 194 // Safari, IE, and others 195 other: function(curr) { 196 var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, 197 stack = [], fn, args, maxStackSize = 10; 198 199 while (curr && stack.length < maxStackSize) { 200 fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; 201 args = Array.prototype.slice.call(curr['arguments']); 202 stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; 203 curr = curr.caller; 204 } 205 return stack; 206 }, 207 208 /** 209 * Given arguments array as a String, subsituting type names for non-string types. 210 * 211 * @param {Arguments} object 212 * @return {Array} of Strings with stringified arguments 213 */ 214 stringifyArguments: function(args) { 215 for (var i = 0; i < args.length; ++i) { 216 var arg = args[i]; 217 if (arg === undefined) { 218 args[i] = 'undefined'; 219 } else if (arg === null) { 220 args[i] = 'null';
221 } else if (arg.constructor) { 222 if (arg.constructor === Array) { 223 if (arg.length < 3) { 224 args[i] = '[' + this.stringifyArguments(arg) + ']'; 225 } else { 226 args[i] = '[' + this.stringifyArguments(Array.prototype.slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(Array.prototype.slice.call(arg, -1)) + ']'; 227 } 228 } else if (arg.constructor === Object) { 229 args[i] = '#object'; 230 } else if (arg.constructor === Function) { 231 args[i] = '#function'; 232 } else if (arg.constructor === String) { 233 args[i] = '"' + arg + '"'; 234 } 235 } 236 } 237 return args.join(','); 238 }, 239 240 sourceCache: {}, 241 242 /** 243 * @return the text from a given URL. 244 */ 245 ajax: function(url) { 246 var req = this.createXMLHTTPObject(); 247 if (!req) { 248 return; 249 } 250 req.open('GET', url, false); 251 req.setRequestHeader('User-Agent', 'XMLHTTP/1.0'); 252 req.send(''); 253 return req.responseText; 254 }, 255 256 /** 257 * Try XHR methods in order and store XHR factory. 258 * 259 * @return <Function> XHR function or equivalent 260 */ 261 createXMLHTTPObject: function() { 262 var xmlhttp, XMLHttpFactories = [ 263 function() { 264 return new XMLHttpRequest(); 265 }, function() { 266 return new ActiveXObject('Msxml2.XMLHTTP'); 267 }, function() { 268 return new ActiveXObject('Msxml3.XMLHTTP'); 269 }, function() { 270 return new ActiveXObject('Microsoft.XMLHTTP'); 271 } 272 ]; 273 for (var i = 0; i < XMLHttpFactories.length; i++) { 274 try { 275 xmlhttp = XMLHttpFactories[i](); 276 // Use memoization to cache the factory 277 this.createXMLHTTPObject = XMLHttpFactories[i]; 278 return xmlhttp; 279 } catch (e) {} 280 } 281 }, 282 283 /** 284 * Given a URL, check if it is in the same domain (so we can get the source 285 * via Ajax). 286 * 287 * @param url <String> source url 288 * @return False if we need a cross-domain request 289 */ 290 isSameDomain: function(url) { 291 return url.indexOf(location.hostname) !== -1; 292 }, 293 294 /** 295 * Get source code from given URL if in the same domain. 296 * 297 * @param url <String> JS source URL 298 * @return <Array> Array of source code lines 299 */ 300 getSource: function(url) { 301 if (!(url in this.sourceCache)) { 302 this.sourceCache[url] = this.ajax(url).split('\n'); 303 } 304 return this.sourceCache[url]; 305 }, 306 307 guessFunctions: function(stack) { 308 for (var i = 0; i < stack.length; ++i) { 309 var reStack = /\{anonymous\}\(.*\)@(\w+:\/\/([\-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/; 310 var frame = stack[i], m = reStack.exec(frame); 311 if (m) { 312 var file = m[1], lineno = m[4]; //m[7] is character position in Chrome 313 if (file && this.isSameDomain(file) && lineno) { 314 var functionName = this.guessFunctionName(file, lineno); 315 stack[i] = frame.replace('{anonymous}', functionName); 316 } 317 } 318 } 319 return stack; 320 }, 321 322 guessFunctionName: function(url, lineNo) { 323 try { 324 return this.guessFunctionNameFromLines(lineNo, this.getSource(url)); 325 } catch (e) { 326 return 'getSource failed with url: ' + url + ', exception: ' + e.toString(); 327 } 328 }, 329 330 guessFunctionNameFromLines: function(lineNo, source) { 331 var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/; 332 var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/; 333 // Walk backwards from the first line in the function until we find the line which 334 // matches the pattern above, which is the function definition 335 var line = "", maxLines = 10; 336 for (var i = 0; i < maxLines; ++i) { 337 line = source[lineNo - i] + line; 338 if (line !== undefined) { 339 var m = reGuessFunction.exec(line); 340 if (m && m[1]) { 341 return m[1]; 342 } else { 343 m = reFunctionArgNames.exec(line); 344 if (m && m[1]) { 345 return m[1]; 346 } 347 } 348 } 349 } 350 return '(?)'; 351 } 352};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.