1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/trace",
  5     "firebug/lib/url",
  6     "firebug/lib/locale",
  7     "firebug/lib/wrapper",
  8     "firebug/js/sourceLink",
  9     "firebug/lib/deprecated",
 10     "firebug/lib/options",
 11 ],
 12 function (FBTrace, Url, Locale, Wrapper, SourceLink, Deprecated, Options) {
 13 
 14 // ********************************************************************************************* //
 15 // Constants
 16 
 17 // ********************************************************************************************* //
 18 // Implementation
 19 
 20 var StackFrame = {};
 21 
 22 StackFrame.getStackTrace = Deprecated.deprecated("name change for self-documentation",
 23     StackFrame.getCorrectedStackTrace);
 24 
 25 /**
 26  * Converts a Mozilla stack frame to a frameXB
 27  */
 28 StackFrame.getCorrectedStackTrace = function(frame, context)
 29 {
 30     try
 31     {
 32         var trace = new StackFrame.StackTrace();
 33         var newestFrame = null;
 34         var nextOlderFrame = null;
 35         for (; frame && frame.isValid; frame = frame.callingFrame)
 36         {
 37             if (!(Options.get("filterSystemURLs") &&
 38                 Url.isSystemURL(Url.normalizeURL(frame.script.fileName))))
 39             {
 40                 var stackFrame = StackFrame.getStackFrame(frame, context, newestFrame);
 41                 if (stackFrame)
 42                 {
 43                     if (!newestFrame)
 44                         newestFrame = stackFrame;
 45 
 46                     if (context.currentFrame && context.currentFrame === frame)
 47                         trace.currentFrameIndex = trace.length;
 48 
 49                     stackFrame.setCallingFrame(nextOlderFrame, trace.frames.length);
 50                     nextOlderFrame = stackFrame;
 51                     trace.frames.push(stackFrame);
 52                 }
 53             }
 54             else
 55             {
 56                 if (FBTrace.DBG_STACK)
 57                     FBTrace.sysout("lib.getCorrectedStackTrace isSystemURL frame.script.fileName "+
 58                         frame.script.fileName+"\n");
 59             }
 60         }
 61 
 62         if (trace.frames.length > 100)  // TODO in the loop above
 63         {
 64             var originalLength = trace.frames.length;
 65             trace.frames.splice(50, originalLength - 100, null);
 66             var excuse = "(eliding "+(originalLength - 100)+" frames)";
 67 
 68             trace.frames[50] = new StackFrame.StackFrame({href: excuse}, 0, excuse,
 69                 [], null, null, context, newestFrame);
 70         }
 71 
 72     }
 73     catch (exc)
 74     {
 75         if (FBTrace.DBG_ERRORS)
 76             FBTrace.sysout("getCorrectedStackTrace FAILS "+exc, exc);
 77     }
 78     return trace;
 79 };
 80 
 81 /*
 82  * Converts from Mozilla stack frame to frameXB
 83  */
 84 StackFrame.getStackFrame = function(frame, context, newestFrameXB)
 85 {
 86     if (frame.isNative || frame.isDebugger)
 87     {
 88         var excuse = (frame.isNative) ?  "(native)" : "(debugger)";
 89         if (FBTrace.DBG_STACK)
 90             FBTrace.sysout("lib.getStackFrame "+excuse+" frame\n");
 91 
 92         return new StackFrame.StackFrame({href: excuse}, 0, excuse, [],
 93             null, null, context, newestFrameXB);
 94     }
 95     try
 96     {
 97         var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, frame.script);
 98         if (sourceFile)
 99         {
100             var url = sourceFile.href;
101             var analyzer = sourceFile.getScriptAnalyzer(frame.script);
102 
103             var lineNo = analyzer.getSourceLineFromFrame(context, frame);
104             var fncSpec = analyzer.getFunctionDescription(frame.script, context, frame);
105             if (!fncSpec.name || fncSpec.name === "anonymous")
106             {
107                 fncSpec.name = StackFrame.guessFunctionName(url, frame.script.baseLineNumber, context);
108                 if (!fncSpec.name)
109                     fncSpec.name = "?";
110             }
111 
112             if (FBTrace.DBG_STACK)
113                 FBTrace.sysout("lib.getStackFrame "+fncSpec.name, {sourceFile: sourceFile,
114                     script: frame.script, fncSpec: fncSpec, analyzer: analyzer});
115 
116             return new StackFrame.StackFrame(sourceFile, lineNo, fncSpec.name, fncSpec.args, frame,
117                 frame.pc, sourceFile.context, newestFrameXB);
118         }
119         else
120         {
121             if (FBTrace.DBG_STACK)
122                 FBTrace.sysout("lib.getStackFrame NO sourceFile tag@file:"+frame.script.tag+
123                     "@"+frame.script.fileName, frame.script.functionSource);
124 
125             var script = frame.script;
126             return new StackFrame.StackFrame({href: Url.normalizeURL(script.fileName)}, frame.line,
127                 script.functionName, [], frame, frame.pc, context, newestFrameXB);
128         }
129     }
130     catch (exc)
131     {
132         if (FBTrace.DBG_STACK)
133             FBTrace.sysout("getStackFrame fails: "+exc, exc);
134         return null;
135     }
136 };
137 
138 // ********************************************************************************************* //
139 // frameXB, cross-browser frame
140 
141 StackFrame.StackFrame = function(sourceFile, lineNo, functionName, args, nativeFrame, pc,
142     context, newestFrame)
143 {
144     // Essential fields
145     this.sourceFile = sourceFile;
146     this.line = lineNo;
147 
148     var fn = StackFrame.getDisplayName(nativeFrame ? nativeFrame.scope : null);
149     this.fn = fn || functionName;  // cache?
150 
151     this.context = context;
152 
153     // the newest frame in the stack containing 'this' frame
154     this.newestFrame = (newestFrame ? newestFrame : this);
155 
156     // optional
157     this.args = args;
158 
159     // Derived from sourceFile
160     this.href = sourceFile.href;
161 
162     // Mozilla
163     this.nativeFrame = nativeFrame;
164     this.pc = pc;
165     this.script = nativeFrame ? nativeFrame.script : null;  // TODO-XB
166 };
167 
168 StackFrame.StackFrame.prototype =
169 {
170     getURL: function()
171     {
172         return this.href;
173     },
174 
175     getCompilationUnit: function()
176     {
177         return this.context.getCompilationUnit(this.href);
178     },
179 
180     getStackNewestFrame: function()
181     {
182         return this.newestFrame;
183     },
184 
185     getFunctionName: function()
186     {
187         return this.fn;
188     },
189 
190     toSourceLink: function()
191     {
192         return new SourceLink.SourceLink(this.sourceFile.href, this.line, "js");
193     },
194 
195     toString: function()
196     {
197         return this.fn+", "+this.sourceFile.href+"@"+this.line;
198     },
199 
200     setCallingFrame: function(caller, frameIndex)
201     {
202         this.callingFrame = caller;
203         this.frameIndex = frameIndex;
204     },
205 
206     getCallingFrame: function()
207     {
208         if (FBTrace.DBG_STACK)
209             FBTrace.sysout("getCallingFrame "+this, this);
210 
211         if (!this.callingFrame && this.nativeFrame && this.nativeFrame.isValid)
212         {
213             var nativeCallingFrame = this.nativeFrame.callingFrame;
214             if (nativeCallingFrame)
215                 this.callingFrame = StackFrame.getStackFrame(nativeCallingFrame, this.context,
216                     this.newestFrame);
217         }
218         return this.callingFrame;
219     },
220 
221     getFrameIndex: function()
222     {
223         return this.frameIndex;
224     },
225 
226     getLineNumber: function()
227     {
228         return this.line;
229     },
230 
231     destroy: function()
232     {
233         if (FBTrace.DBG_STACK)
234             FBTrace.sysout("StackFrame destroyed:"+this.uid+"\n");
235 
236         this.script = null;
237         this.nativeFrame = null;
238         this.context = null;
239     },
240 
241     signature: function()
242     {
243         return this.script.tag + "." + this.pc;
244     },
245 
246     getThisValue: function()
247     {
248         if (this.nativeFrame && !this.thisVar)
249             this.thisVar = Wrapper.unwrapIValue(this.nativeFrame.thisValue, Firebug.viewChrome);
250         return this.thisVar;
251     },
252 
253     getScopes: function(viewChrome)
254     {
255         if (this.nativeFrame && !this.scope)
256             this.scope = this.generateScopeChain(this.nativeFrame.scope, viewChrome);
257         return this.scope;
258     },
259 
260     clearScopes: function(viewChrome)
261     {
262         // Clears cached scope chain, so that it regenerates the next time
263         // getScopes() is executed.
264         this.scope = null;
265     },
266 
267     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
268     // Private
269 
270     generateScopeChain: function (scope, viewChrome)
271     {
272         var ret = [];
273         while (scope)
274         {
275             var scopeVars;
276 
277             // getWrappedValue will not contain any variables for closure
278             // scopes, so we want to special case this to get all variables
279             // in all cases.
280             if (scope.jsClassName == "Call")
281             {
282                 scopeVars = Wrapper.unwrapIValueObject(scope, viewChrome)
283                 scopeVars.toString = function() {return Locale.$STR("Closure Scope");}
284             }
285             else if (scope.jsClassName == "Block")
286             {
287                 scopeVars = Wrapper.unwrapIValueObject(scope, viewChrome)
288                 scopeVars.toString = function() {return Locale.$STR("Block Scope");}
289             }
290             else
291             {
292                 scopeVars = Wrapper.unwrapIValue(scope, Firebug.viewChrome);
293                 if (scopeVars && scopeVars.hasOwnProperty)
294                 {
295                     if (!scopeVars.hasOwnProperty("toString"))
296                     {
297                         (function() {
298                             var className = scope.jsClassName;
299                             scopeVars.toString = function()
300                             {
301                                 return Locale.$STR(className + " Scope");
302                             };
303                         })();
304                     }
305                 }
306                 else
307                 {
308                     // do not trace scopeVars, you will get a uncatchable exception
309                     if (FBTrace.DBG_ERRORS)
310                         FBTrace.sysout("dom .generateScopeChain: bad scopeVars for " +
311                             "scope.jsClassName:" + scope.jsClassName);
312 
313                     scopeVars = {error: "Mozilla error: invalid scope variables"};
314                 }
315             }
316 
317             if (scopeVars)
318                 ret.push(scopeVars);
319 
320             scope = scope.jsParent;
321         }
322 
323         ret.toString = function()
324         {
325             return Locale.$STR("Scope Chain");
326         };
327 
328         return ret;
329     },
330 };
331 
332 //-----------------------111111----222222-----33---444  1 All 'Not a (' followed by (; 2 All 'Not a )' followed by a ); 3 text between @ and : digits
333 
334 var reErrorStackLine = /^(.*)@(.*):(\d*)$/;
335 var reErrorStackLine2 = /^([^\(]*)\((.*)\)$/;
336 
337 StackFrame.parseToStackFrame = function(line, context) // function name (arg, arg, arg)@fileName:lineNo
338 {
339     var last255 = line.length - 255;
340     if (last255 > 0)
341         line = line.substr(last255);   // avoid regexp on monster compressed source (issue 4135)
342 
343     var m = reErrorStackLine.exec(line);
344     if (m)
345     {
346         var m2 = reErrorStackLine2.exec(m[1]);
347         if (m2)
348         {
349             var params = m2[2].split(',');
350             //FBTrace.sysout("parseToStackFrame",{line:line,paramStr:m2[2],params:params});
351             //var params = JSON.parse("["+m2[2]+"]");
352             return new StackFrame.StackFrame({href:m[2]}, m[3], m2[1], params, null, null, context);
353         }
354         else
355         {
356             // Firefox 14 removes arguments from <exception-object>.stack.toString()
357             // That's why the m2 reg doesn't match
358             // See: https://bugzilla.mozilla.org/show_bug.cgi?id=744842
359             return new StackFrame.StackFrame({href:m[2]}, m[3], m[1], [], null, null, context);
360         }
361     }
362 }
363 
364 StackFrame.parseToStackTrace = function(stack, context)
365 {
366      var lines = stack.split('\n');
367      var trace = new StackFrame.StackTrace();
368      for (var i = 0; i < lines.length; i++)
369      {
370          var frame = StackFrame.parseToStackFrame(lines[i],context);
371 
372          if (FBTrace.DBG_STACK)
373              FBTrace.sysout("parseToStackTrace i "+i+" line:"+lines[i]+ "->frame: "+frame, frame);
374 
375          if (frame)
376              trace.frames.push(frame);
377      }
378      return trace;
379 }
380 
381 StackFrame.cleanStackTraceOfFirebug = function(trace)
382 {
383     if (trace && trace.frames)
384     {
385         while (trace.frames.length &&
386             (
387              /^_[fF]irebug/.test(trace.frames[trace.frames.length - 1].fn) ||
388              /^\s*with\s*\(\s*_[fF]irebug/.test(trace.frames[trace.frames.length - 1].sourceFile.source)
389             )
390         )
391         {
392             trace.frames.pop();
393         }
394         if (trace.frames.length == 0)
395             trace = undefined;
396     }
397     return trace;
398 }
399 
400 StackFrame.getStackDump = function()
401 {
402     var lines = [];
403     for (var frame = Components.stack; frame; frame = frame.caller)
404         lines.push(frame.filename + " (" + frame.lineNumber + ")");
405 
406     return lines.join("\n");
407 };
408 
409 StackFrame.getJSDStackDump = function(newestFrame)
410 {
411     var lines = [];
412     for (var frame = newestFrame; frame; frame = frame.callingFrame)
413         lines.push(frame.script.fileName + " (" + frame.line + ")");
414 
415     return lines.join("\n");
416 };
417 
418 StackFrame.getStackSourceLink = function()
419 {
420     for (var frame = Components.stack; frame; frame = frame.caller)
421     {
422         if (frame.filename && frame.filename.indexOf("://firebug/") > 0)
423         {
424             for (; frame; frame = frame.caller)
425             {
426                 var firebugComponent = "/modules/firebug-";
427                 if (frame.filename && frame.filename.indexOf("://firebug/") < 0 &&
428                     frame.filename.indexOf(firebugComponent) == -1)
429                     break;
430             }
431             break;
432         }
433     }
434     return StackFrame.getFrameSourceLink(frame);
435 }
436 
437 StackFrame.getFrameSourceLink = function(frame)
438 {
439     if (frame && frame.filename && frame.filename.indexOf("XPCSafeJSObjectWrapper") == -1)
440         return new SourceLink.SourceLink(frame.filename, frame.lineNumber, "js");
441     else
442         return null;
443 };
444 
445 // TODO delete this, only used by console and console injector.
446 StackFrame.getStackFrameId = function()
447 {
448     for (var frame = Components.stack; frame; frame = frame.caller)
449     {
450         if (frame.languageName == "JavaScript"
451             && !(frame.filename && frame.filename.indexOf("://firebug/") > 0))
452         {
453             return frame.filename + "/" + frame.lineNumber;
454         }
455     }
456     return null;
457 };
458 
459 // ********************************************************************************************* //
460 
461 StackFrame.StackTrace = function(adoptFrames)
462 {
463     this.frames = adoptFrames || [];
464 };
465 
466 StackFrame.StackTrace.prototype =
467 {
468     toString: function()
469     {
470         var trace = "<top>\n";
471         for (var i = 0; i < this.frames.length; i++)
472         {
473             trace += "[" + i + "]"+ this.frames[i]+"\n";
474         }
475         trace += "<bottom>\n";
476         return trace;
477     },
478 
479     reverse: function()
480     {
481         this.frames.reverse();
482         return this;
483     },
484 
485     destroy: function()
486     {
487         for (var i = 0; i < this.frames.length; i++)
488             this.frames[i].destroy();
489 
490         if (FBTrace.DBG_STACK)
491             FBTrace.sysout("lib.StackTrace destroy "+this.uid+"\n");
492     },
493 
494     toSourceLink: function()
495     {
496         if (this.frames.length > 0)
497             return this.frames[0];
498     }
499 };
500 
501 // ********************************************************************************************* //
502 
503 StackFrame.traceToString = function(trace)
504 {
505     var str = "<top>";
506     for(var i = 0; i < trace.frames.length; i++)
507         str += "\n" + trace.frames[i];
508     str += "\n<bottom>";
509     return str;
510 };
511 
512 StackFrame.buildStackTrace = function(frame)
513 {
514     var trace = new StackFrame.StackTrace();
515     while (frame)
516     {
517         trace.frames.push(frame);
518         frame.frameIndex = trace.frames.length;
519         frame = frame.getCallingFrame();
520     }
521 
522     // Set the first frame (the one passed into this function) as the current one (issue 4249).
523     if (trace.frames.length > 0)
524         trace.currentFrameIndex = 1;
525 
526     return trace;
527 };
528 
529 // ********************************************************************************************* //
530 
531 StackFrame.getFunctionName = function(script, context, frame, noArgs)
532 {
533     if (!script)
534     {
535         if (FBTrace.DBG_STACK)
536             FBTrace.sysout("stackFrame.getFunctionName FAILS typeof(script)="+typeof(script)+"\n");
537         return "(no script)";
538     }
539 
540     var name = this.getDisplayName(frame ? frame.scope : null, script);
541     if (name)
542         return name;
543 
544     name = script.functionName;
545     if (!name || (name == "anonymous"))
546     {
547         name = null;
548         var analyzer = Firebug.SourceFile.getScriptAnalyzer(context, script);
549         if (analyzer && frame)
550         {
551             if (FBTrace.DBG_STACK)
552                 FBTrace.sysout("getFunctionName analyzer.sourceFile:", analyzer.sourceFile);
553 
554             var functionSpec = analyzer.getFunctionDescription(script, context, frame);
555             if (functionSpec.name)
556                 name = functionSpec.name + (noArgs ? "" : "("+functionSpec.args.join(',')+")");
557         }
558 
559         if (!name || name == "anonymous")
560         {
561             if (FBTrace.DBG_STACK)
562                 FBTrace.sysout("getFunctionName no analyzer, "+script.baseLineNumber+"@"+
563                     script.fileName+"\n");
564             name = StackFrame.guessFunctionName(Url.normalizeURL(script.fileName),
565                 script.baseLineNumber, context);
566         }
567     }
568 
569     if (FBTrace.DBG_STACK)
570         FBTrace.sysout("getFunctionName "+script.tag+" ="+name+"\n");
571 
572     return name;
573 }
574 
575 StackFrame.getDisplayName = function(scope, script)
576 {
577     try
578     {
579         if (scope)
580         {
581             return Wrapper.unwrapIValue(scope).arguments.callee.displayName;
582         }
583         else if (script)
584         {
585             var fnObj = Wrapper.unwrapIValue(script.functionObject);
586             return (fnObj && fnObj.displayName) ? fnObj.displayName : script.functionName;
587         }
588     }
589     catch (err)
590     {
591         if (FBTrace.DBG_STACK)
592             FBTrace.sysout("stackFrame.getDisplayName; EXCEPTION " + err, err);
593     }
594 }
595 
596 StackFrame.guessFunctionName = function(url, lineNo, context)
597 {
598     if (context)
599     {
600         if (context.sourceCache)
601             return StackFrame.guessFunctionNameFromLines(url, lineNo, context.sourceCache);
602     }
603     return "? in "+Url.getFileName(url)+"@"+lineNo;
604 }
605 
606 var reGuessFunction = /['"]?([$0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
607 var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
608 StackFrame.guessFunctionNameFromLines = function(url, lineNo, sourceCache)
609 {
610     // Walk backwards from the first line in the function until we find the line which
611     // matches the pattern above, which is the function definition
612     var line = "";
613     if (FBTrace.DBG_FUNCTION_NAMES)
614         FBTrace.sysout("getFunctionNameFromLines for line@URL="+lineNo+"@"+url+"\n");
615 
616     for (var i = 0; i < 4; ++i)
617     {
618         line = sourceCache.getLine(url, lineNo-i) + line;
619         if (line != undefined)
620         {
621             var m = reGuessFunction.exec(line);
622             if (m)
623             {
624                 return m[1];
625             }
626             else
627             {
628                 if (FBTrace.DBG_FUNCTION_NAMES)
629                     FBTrace.sysout("lib.guessFunctionName re failed for lineNo-i="+lineNo+
630                         "-"+i+" line="+line+"\n");
631             }
632 
633             m = reFunctionArgNames.exec(line);
634             if (m && m[1])
635                 return m[1];
636         }
637     }
638     return "(?)";
639 }
640 
641 // Mozilla
642 StackFrame.getFunctionArgValues = function(frame)
643 {
644     if (frame.isValid && frame.scope.jsClassName == "Call")
645         var values = StackFrame.getArgumentsFromCallScope(frame);
646     else
647         var values = StackFrame.getArgumentsFromObjectScope(frame);
648 
649     if (FBTrace.DBG_STACK)
650         FBTrace.sysout("stackFrame.getFunctionArgValues "+frame+" scope: "+frame.scope.jsClassName,
651             {values: values});
652 
653     return values;
654 }
655 
656 // Mozilla
657 StackFrame.getArgumentsFromObjectScope = function(frame)
658 {
659     var argNames = frame.script.getParameterNames();
660     var scope = Wrapper.unwrapIValue(frame.scope, Firebug.viewChrome);
661 
662     var values = [];
663 
664     for (var i = 0; i < argNames.length; ++i)
665     {
666         var argName = argNames[i];
667         if (scope)
668         {
669             var pvalue = scope[argName];
670             //?? XXXjjb why are we unwrapping here, scope is a normal object
671             //var value = pvalue ? Wrapper.unwrapIValue(pvalue.value) : undefined;
672             values.push({name: argName, value: pvalue});
673         }
674         else
675         {
676             values.push({name: argName});
677         }
678     }
679 
680     return values;
681 };
682 
683 StackFrame.getArgumentsFromCallScope = function(frame)
684 {
685     var argNames = frame.script.getParameterNames();
686     var scope = frame.scope;
687     var values = [];
688     for (var i = 0; i < argNames.length; ++i)
689     {
690         var argName = argNames[i];
691         var pvalue = scope.getProperty(argName); // jsdIValue in jsdIDebuggerService
692         var value = pvalue ? Wrapper.unwrapIValue(pvalue.value, Firebug.viewChrome) : undefined;
693         values.push({name: argName, value: value});
694     }
695 
696     return values;
697 };
698 
699 // ********************************************************************************************* //
700 
701 var saveShowStackTrace;
702 
703 /**
704  * use in the try{} around a call to getInterface to prevent fbs from generating stack traces
705  */
706 StackFrame.suspendShowStackTrace = function()
707 {
708     saveShowStackTrace = Firebug.showStackTrace;
709     Firebug.showStackTrace = false;
710 };
711 
712 /**
713  * use in the finally{} to undo the suspendShowStackTrace
714  */
715 StackFrame.resumeShowStackTrace = function()
716 {
717     if (saveShowStackTrace)
718     {
719         Firebug.showStackTrace = saveShowStackTrace;
720         saveShowStackTrace = null;
721     }
722 };
723 
724 // ********************************************************************************************* //
725 // Registration
726 
727 return StackFrame;
728 
729 // ********************************************************************************************* //
730 });
731