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