6 Replies Latest reply on Nov 21, 2015 8:32 AM by Marc Autret

    Getting the stack trace of an error

    dln385 Level 2

      When I catch an error, I would like to be able to log its stack trace. So far I’ve come up with this little trick:

       

      Error.prototype.toString = function() {
          if (typeof this.stack === "undefined" || this.stack === null) {
              this.stack = "placeholder";
              // The previous line is needed because the next line may indirectly call this method.
              this.stack = $.stack;
          }
          return "Error";
      }
      

       

      This attaches the stack to any manually-created error. For example, I can run:

       

      try {
          throw new Error("I'm an error.");
      }
      catch (e) {
          $.writeln("Stack: " + e.stack);
      }
      

       

      And this will print the correct stack for the error. However, it only works for errors that I create. Is there any way to get the stack trace of any error?

       

      Full disclosure: I’ve asked this on Stack Overflow without much success. See here for more info: http://stackoverflow.com/questions/16201574/getting-the-stack-trace-of-an-error-in-extends cript

        • 1. Re: Getting the stack trace of an error
          Vamitul Level 4

          hei!

           

          In extendscript there are some problems when extending some objects. Not sure it Error is one of them, but your problem sure looks like that.

          Here is a great source of info:

           

          http://forums.adobe.com/message/4133138#4133138

          1 person found this helpful
          • 2. Re: Getting the stack trace of an error
            Marc Autret Level 4

            Hi dln385,

             

            I have no answer for your question but what your code reveals is very educative: the throw statement always triggers the toString() method of the throwed object. I never realized that.

             

            // Throwing obj causes obj.toString() invokation
            //--------------------------------------
            
            var obj = {
                toString: function(){ alert("Hello guys!"); }
                };
            
            try{ throw obj; } // => Hello guys!
            catch(_){}
            

             

            Another fact I noticed is that throwing anything binds the caught reference to anything, whatever it refers to:

             

            // What you throw is what you catch!
            //--------------------------------------
            
            var anything = {}; // works with literals too, e.g. "foobar", or even 5!
            
            try{ throw anything; }
            catch(e){ alert( e===anything ); }  // true!
            

             

            For these reasons the actual Error(...) constructor—which basically behaves as a simple {message, name, etc.} object factory—doesn't seem essential to the try/throw/catch trio. Therefore, as long as you need to handle custom exceptions, you could as well entirely bypass the native Error object:

             

             

            // No need to use native Error class
            //--------------------------------------
            
            function MyException(msg,s)
            {
                this.message = msg || '';
                this.name = s || 'Error';
                this.stack = null;
            };
            
            MyException.prototype.toString = function()
            {
                // Hook $.stack *on first invokation*
                // ---
                this.stack || (this.stack = $.stack.
                            replace(/[\r\n]toString\(\)[\r\n]$/,'').
                            split(/[\r\n]/)
                            );
            
                // Make toString() ECMA-262 compliant
                // ---
                return this.name + ': ' + this.message;
            };
            
            
            // =================
            // Example code
            // =================
            
            function inner()
            {
                throw new MyException("Something's gone wrong!");
            }
            
            function outer()
            {
                inner();
            }
            
            function main()
            {
                try{ outer(); }
                catch(e)
                    {
                    alert( e );        // => Error: Something's gone wrong!
                    alert( e.stack );  // => [myScript.jsx],main(),outer(),inner()
                    }
            }
            
            main();
            

             

            Now, your original question still remains, about built-in Error types (Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError). The fact is that ExtendScript does not invoke Error.prototype.toString() when a "native error" occurs. The opposite would be somewhat embarrassing!

             

            // Pure errors (not explicitly thrown) do not involve toString
            //--------------------------------------
            
            // Disclaimer: overriding Error.prototype
            // is not recommended at all!
            Error.prototype.toString = function(){ alert( "Hello guys!" ); };
            
            // Let's cause a RangeError...
            try {
                new Array(-1); // silent!
                }
            catch(e)
                {
                alert( e.name ); // => RangeError
                }
            
            // By contrast:
            try {
                throw new Error("My error"); // => Hello guys!
                }
            catch(e)
                {
                alert( e.name ); // => Error
                }
            

             

            Hope that may help a bit.

             

            @+

            Marc

            1 person found this helpful
            • 3. Re: Getting the stack trace of an error
              MasterDomino Level 1

              Hi Marc Autret,
              I know this is quite an old thread, but hopefully you will get notified anyways about new answers.
              I am curious about the custom MyException error class you demonstrated above.

               

              Is there any way to also include the line and the script information in this class.

               

              When I simply put

               

              this.line = $.line           (how the heck do I put proper code blocks here?)

               

              in the function body, then it picks up the line where this.line is defined in the function, not the line where I throw the exception. Is there any way to work around this?
              (The only way I could see is making the line property one of the arguments, but then I would need to type $.line every time I throw my custom error. So, is there a more sophisticated way?

               

              Or is it in this case preferrable to augment the native Error class? (I seem to remember that in your blog you did *not* recommend that.)

               

              Thanks a lot!
              T R

              • 4. Re: Getting the stack trace of an error
                Trevorׅ Adobe Community Professional

                Hi

                 

                Try changing the code like this?

                 

                function MyException(msg,s, line)  
                {  
                    this.message = msg || '';  
                    this.name = s || 'Error';  
                    this.stack = null;  
                    this.line = line;
                };  
                
                
                // =================  
                // Example code  
                // =================  
                  
                function inner()  
                {  
                    throw new MyException("Something's gone wrong!", $.line);
                } 
                

                 

                 

                Trevor

                • 5. Re: Getting the stack trace of an error
                  MasterDomino Level 1

                  Hi Trevor,
                  thanks for your answer. However, this is what I actually meant by making the line property one of the arguments. I would prefer to avoid that, so that I don't have to type $.line every time I am calling the function. It feel, if I always have to call the exact same argument, there should be a more sophisticated way, as this feels kind of redundant.!

                   

                  Does anybody know?

                   

                  Thanks!

                  • 6. Re: Getting the stack trace of an error
                    Marc Autret Level 4

                    Hi MasterDomino,

                     

                    I get your point but I can't see any way to avoid the use of $.line in the calling function since $.line acts like an ExtendScript directive that explicitly returns the current line number. The returned value is then rigidly attached to the line from which you call the command, so if you don't call it at the relevant location for your purpose you will necessarily get an irrelevant value.

                     

                    And even if there were tricks—maybe there are—to somehow freeze $.line, or watch/backup changes in that scope, you still have to explicitly call some command or function to grab the desired line number at the desired location. Only the $.stack property allows you to remotely parse the hierarchy of function calls, because $.stack can be converted into an array and then you can 'pop' items which have been added due to the delegation mechanism. But I can't find out a similar approach for $.line.

                     

                    @+

                    Marc