Bug 13646 - Implement Error.prototype.stack
Summary: Implement Error.prototype.stack
Status: RESOLVED DUPLICATE of bug 66994
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: 523.x (Safari 3)
Hardware: Macintosh OS X 10.4
: P2 Normal
Assignee: Juan C. Montemayor
URL: javascript:alert(new Error("foo").stack)
Keywords: InRadar
: 18515 (view as bug list)
Depends on:
Blocks: 21181
  Show dependency treegraph
 
Reported: 2007-05-09 12:12 PDT by Garrett Smith
Modified: 2012-03-07 18:12 PST (History)
21 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Garrett Smith 2007-05-09 12:12:51 PDT
javascript:alert(new Error("foo").stack) // should provide a stack trace.

getStack() would be fine. Just want a way to get a stack trace when debugging.

Error.prototype.stack should also be detectable without throwing an error.

"stack" in Error.prototype;
Comment 1 David Kilzer (:ddkilzer) 2007-05-12 10:53:22 PDT
Hi Garrett, is this implemented in other browsers?  Is there a specification for how it should behave?

Comment 2 Garrett Smith 2007-05-12 11:10:10 PDT
Get a stack off an error instance in FireFox.

var e = new Error('foo');
e.stack;

http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Error
Comment 3 Jeff Walden (remove +bwo to email) 2007-08-19 01:38:38 PDT
The "stack" property in Firefox (do note the lack of caps on the second "f", Garrett) is not on Error.prototype -- it's a per-instance property computed when the exception is created (I think).

Do also note this most likely degrades exception-throwing performance, unless you garbage-collect stack frames or something like it (not likely in the presence of native stack frames, and not otherwise useful in JS as defined by the standard and by the Real World).
Comment 4 Garrett Smith 2007-08-20 13:27:17 PDT
(In reply to comment #3)
> The "stack" property in Firefox (do note the lack of caps on the second "f",
> Garrett) is not on Error.prototype -- it's a per-instance property computed
> when the exception is created (I think).

Is a null-valued property is better or worse design than an undefined value (or dynamically added property)?

> Do also note this most likely degrades exception-throwing performance, unless
> you garbage-collect stack frames or something like it (not likely in the
> presence of native stack frames, and not otherwise useful in JS as defined by
> the standard and by the Real World).

Some people find e.stack useful when debugging others' code -- I know I have, and at work, too! (not academic discussion at MIT)

It's possible to create a findStack( error ) function to crawl up the function caller chain. But this code is ideally not deployed in a "real world" app. So what happens when deployed code has a bug. 

When there's a P1 bug to fix, or if the developer gets called back into work on a Fri night (tired), it's nice to find the error right away. Do I really need to explain why a stack trace is useful?

What is your useful, real world solution to obtaining a stack trace, Jeff? 
Comment 5 Jeff Walden (remove +bwo to email) 2007-08-20 15:48:45 PDT
(In reply to comment #4)
> Is a null-valued property is better or worse design than an undefined value
> (or dynamically added property)?

I think the general practice is to share methods on the prototype but not properties.  There doesn't seem to be a whole lot of point to adding "", null, or what-have-you to the prototype if it's simply going to be overridden for every new Error created.

> > Do also note this most likely degrades exception-throwing performance,
> > unless you garbage-collect stack frames or something like it (not likely
> > in the presence of native stack frames, and not otherwise useful in JS as
> > defined by the standard and by the Real World).
> 
> Some people find e.stack useful when debugging others' code -- I know I have,
> and at work, too! (not academic discussion at MIT)

I don't disagree; I'm simply saying that unless you do something like frame garbage-collection (*this* is what I said wasn't useful in real-world JS, not stack traces), you have to compute the value of the stack string when the exception is thrown.  You can't lazily compute it, so you pay the cost of creating it even if you never access the stack property.

> What is your useful, real world solution to obtaining a stack trace, Jeff? 

You misunderstood me.  I was simply indicating that implementing this will probably regress performance, and given the concern WebKit has over perf (must not regress, even with new functionality), that may be a problem for WebKit.  (I personally would say the benefits outweigh the slowdown, and exceptions being exceptional, you shouldn't be relying on their performance, but I personally don't *need* WebKit to implement this, as I'm served well enough elsewhere.)
Comment 6 Eric Seidel (no email) 2009-05-11 19:34:12 PDT
*** Bug 18515 has been marked as a duplicate of this bug. ***
Comment 7 Eric Seidel (no email) 2009-05-11 21:28:03 PDT
For what it's worth, V8 has an internal bug about this: http://b/issue?id=1128410  (The only reason it's internal is that I was filed 6 months before Chrome launched.  It really should be moved into a public bug.)

The conclusion was originally drawn that creating a stack trace on every exception throw was too expensive, and that it would be a large perf-hit for web apps which throw exceptions.

The way that WebCore/JSC seems to do this for the inspector:
http://trac.webkit.org/browser/trunk/WebCore/bindings/js/ScriptCallStack.cpp
where we only record the first call stack and lazily record the rest of them.  I don't know if the same approach would work for general exceptions in JSC as well or not.
Comment 8 Eric Seidel (no email) 2009-05-11 21:28:47 PDT
Adding two of the JSC engineers in case they care to comment about the possible perf hit of recording Error.stack information in JSC.
Comment 9 Geoffrey Garen 2009-05-11 22:31:21 PDT
(In reply to comment #8)
JavaScript implementations are already on the hook to be able to generate a stack trace on demand, to support arguments.caller and Function.caller. So, a "give me a stack trace, no really, I mean it" call / keyword should be no new burden.

However, I do agree that adding a stack trace to every exception would be serious new burden to apps that throw regularly. (Not sure how common those are.)

Why not do roughly this, using existing APIs, instead:

function stack() {
var stack = [];
var caller = arguments.caller;
while (caller != null) {
stack.push(caller);
caller = caller.caller;
}
}
Comment 10 Eric Seidel (no email) 2009-05-11 23:26:41 PDT
(In reply to comment #9)
> (In reply to comment #8)
> However, I do agree that adding a stack trace to every exception would be
> serious new burden to apps that throw regularly. (Not sure how common those
> are.)

error.stack is most interesting from a callback like window.onerror (see bug 8519).  So exposing a .stack accessor on every exception (which ideally could be implemented to lazily crawl the stack) would be the ideal situation.

> Why not do roughly this, using existing APIs, instead:
> 
> function stack() {
> var stack = [];
> var caller = arguments.caller;
> while (caller != null) {
> stack.push(caller);
> caller = caller.caller;
> }
> }

Sure!  And this method is already used by things like Google Web Toolkit (and likely many other JS frameworks):
http://google-web-toolkit.googlecode.com/svn/trunk/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java

But it doesn't provide you as much information as the Firefox or Opera Error.stack accessor does (including line numbers, file names, etc.)
Comment 11 Geoffrey Garen 2009-05-11 23:31:43 PDT
> (which ideally could be implemented to lazily crawl the stack) would be the ideal situation.

Ideally, indeed! :)

You can't lazily crawl the stack because the stack changes as execution continues.

Perhaps a given exception handling scope can be given a stack accessor that is lazy and exists only within the handler scope, though.
Comment 12 Eric Seidel (no email) 2009-05-11 23:36:34 PDT
(In reply to comment #11)
> Perhaps a given exception handling scope can be given a stack accessor that is
> lazy and exists only within the handler scope, though.

Sounds like we'd have to keep track of these lazy objects and be sure to replace them with non-lazy ones before continuing execution (i.e. just call their accessor to get them to cache the stack).  I think it's rare that handlers keep exception objects around, but I'm not sure that'd we'd know that they were gone out of scope w/o performing a GC, or?  (I'm speculating well outside of my area of expertise here.)

Comment 13 Geoffrey Garen 2009-05-11 23:52:52 PDT
(In reply to comment #12)
Imagine this:

try {
} catch (e) {
    if (showBacktraces)
        alert(__BACKTRACE__) // lazily generates a backtrace
}

The VM can say that "__BACKTRACE__" is in scope only inside catch blocks. Or we could allow its use anywhere, as a keyword.
Comment 14 Eric Seidel (no email) 2009-05-19 06:30:18 PDT
I think we should try implementing .stack to only return the topmost frame at time of throw.  Maybe even stack depth if that's easy to compute.  I think even just adding the top-most function name would be a huge win for web developer error reporting in the field.

We could even implement a full stack crawl behind an #if or a runtime check, and then perf test to see what the hit would be.
Comment 15 Julie Parent 2009-06-25 13:54:40 PDT
Just to give another strong vote on this:  we are hearing from many Google teams that the lack of stack traces is the biggest reason why apps are not as stable (more crashes, more bugs, etc) in Webkit browsers as they are in Firefox, because the cost of tracking down issues in the wild without stack traces is too high.
Comment 16 Geoffrey Garen 2009-06-25 17:29:33 PDT
(In reply to comment #15)
Do you know how they usually get stack traces? Do they surround all code with try/catch, use window.onerror, or something else?

One possibility would be to add stacks to Error objects, but only if some global flag was set -- for example, only if window.onerror was set to a function.
Comment 17 Bob Vawter 2009-06-26 06:30:05 PDT
(In reply to comment #16)
> Do you know how they usually get stack traces? Do they surround all code with
> try/catch, use window.onerror, or something else?

For applications written with Google Web Toolkit (e.g. Google Wave, the new Adwords interface), the generated JS contains regular JS try/catch blocks that correspond to try/catch block in the original Java source.

When a native JS Error object is caught, we wrap it in a JavaScriptException and expose the inferred stack trace to the developer via Throwable.getStackTrace().  As far as extracting the stack trace data, there are several implementations.

FireFox / Opera : Parse e.stack
Everything else : Crawl arguments.callee

The problem with crawling arguments.callee is that it tends to not work with reentrant functions.  Moreover, all we know is the function in which the catch block appears, so we're usually missing a few frames (adding a try/catch to every function would be prohibitively expensive).

As far as GWT goes, it would be preferable if the data were available off of the native JS Error object, but we can make it work with pretty much any API.

http://google-web-toolkit.googlecode.com/svn/trunk/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
Comment 18 Christian Plesner Hansen 2009-07-24 05:31:32 PDT
FYI: In v8, instances of Error now capture the topmost Error.stackTraceLimit stack frames, 10 by default.  The performance cost of this turned out to be negligible.

Here's an example of the format:

ReferenceError: FAIL is not defined
   at Constraint.execute (deltablue.js:525:2)
   at Constraint.recalculate (deltablue.js:424:21)
   at Planner.addPropagate (deltablue.js:701:6)
   at Constraint.satisfy (deltablue.js:184:15)
   at Planner.incrementalAdd (deltablue.js:591:21)
   at Constraint.addConstraint (deltablue.js:162:10)
   at Constraint.BinaryConstraint (deltablue.js:346:7)
   at Constraint.EqualityConstraint (deltablue.js:515:38)
   at chainTest (deltablue.js:807:6)
   at deltaBlue (deltablue.js:879:2)

We ended up using a format that is different from firefox' because we have different information available (for instance we have the receiver type which they don't whereas they have the arguments which we don't) and because it is just more readable that way.  It's not set in stone yet though and I hope that if you decide to implement this in JSC we can agree on compatible formats.
Comment 19 Mark Rowe (bdash) 2010-06-11 02:41:51 PDT
<rdar://problem/8082878>
Comment 20 kangax 2011-01-24 08:31:05 PST
Is anything blocking this issue? Any progress on this?
Comment 21 Oliver Hunt 2011-07-08 10:09:48 PDT
(In reply to comment #20)
> Is anything blocking this issue? Any progress on this?

The basic problem is that JSC currently lacks the ability to produce a complete stack trace, then there's the element of performance (people expect exceptions to be fast, but having to build this information is counter to that goal)
Comment 22 kangax 2011-07-08 10:13:43 PDT
(In reply to comment #21)
> (In reply to comment #20)
> > Is anything blocking this issue? Any progress on this?
> 
> The basic problem is that JSC currently lacks the ability to produce a complete stack trace, then there's the element of performance (people expect exceptions to be fast, but having to build this information is counter to that goal)

Is there a way to perform expensive work only on ("stack") property read?
Comment 23 Oliver Hunt 2011-07-08 10:30:55 PDT
(In reply to comment #22)
> (In reply to comment #21)
> > (In reply to comment #20)
> > > Is anything blocking this issue? Any progress on this?
> > 
> > The basic problem is that JSC currently lacks the ability to produce a complete stack trace, then there's the element of performance (people expect exceptions to be fast, but having to build this information is counter to that goal)
> 
> Is there a way to perform expensive work only on ("stack") property read?

No, because the whole point of the stack property is to give you the stack information for the error was created.

But the bigger problem at the moment is that we can't provide a complete stack trace even if we wanted to.
Comment 24 Garrett Smith 2011-07-10 20:32:48 PDT
(In reply to comment #23)
> (In reply to comment #22)
> > (In reply to comment #21)
> > > (In reply to comment #20)
> > > > Is anything blocking this issue? Any progress on this?
> > > 
> > > The basic problem is that JSC currently lacks the ability to produce a complete stack trace, then there's the element of performance (people expect exceptions to be fast, but having to build this information is counter to that goal)
> > 
> > Is there a way to perform expensive work only on ("stack") property read?
> 
> No, because the whole point of the stack property is to give you the stack information for the error was created.
> 

As comment #11 "Perhaps a given exception handling scope can be given a stack accessor that is lazy and exists only within the handler scope, though."

try {
  a();
} catch(e) {
// getting a stack trace only allowed in catch scope.
  alert(e.stack); 
}

By limiting access to the catch block, lexical analysis should be easy, right?
Comment 25 Oliver Hunt 2011-07-11 09:26:09 PDT
(In reply to comment #24)
> (In reply to comment #23)
> > (In reply to comment #22)
> > > (In reply to comment #21)
> > > > (In reply to comment #20)
> > > > > Is anything blocking this issue? Any progress on this?
> > > > 
> > > > The basic problem is that JSC currently lacks the ability to produce a complete stack trace, then there's the element of performance (people expect exceptions to be fast, but having to build this information is counter to that goal)
> > > 
> > > Is there a way to perform expensive work only on ("stack") property read?
> > 
> > No, because the whole point of the stack property is to give you the stack information for the error was created.
> > 
> 
> As comment #11 "Perhaps a given exception handling scope can be given a stack accessor that is lazy and exists only within the handler scope, though."
> 
> try {
>   a();
> } catch(e) {
> // getting a stack trace only allowed in catch scope.
>   alert(e.stack); 
> }
> 
> By limiting access to the catch block, lexical analysis should be easy, right?

Nope, the problem here would be that the exception object is created somewhere inside a function called by a() you create the error instance, and then start unwinding the stack to find the exception handler.  Until you know what the handler is you can't tell whether it might be doing .stack, and so you have to track the locations you're unwinding through regardless of any other stuff that's going on.   Although I guess you could save the total stack unwind :-/
Comment 26 Garrett Smith 2011-07-11 09:56:42 PDT
(In reply to comment #25)
> (In reply to comment #24)
> > (In reply to comment #23)
> > > (In reply to comment #22)
> > > > (In reply to comment #21)
> > > > > (In reply to comment #20)
> > > > > > Is anything blocking this issue? Any progress on this?
> > > > > 
> > > > > The basic problem is that JSC currently lacks the ability to produce a complete stack trace, then there's the element of performance (people expect exceptions to be fast, but having to build this information is counter to that goal)
> > > > 
> > > > Is there a way to perform expensive work only on ("stack") property read?
> > > 
> > > No, because the whole point of the stack property is to give you the stack information for the error was created.
> > > 
> > 
> > As comment #11 "Perhaps a given exception handling scope can be given a stack accessor that is lazy and exists only within the handler scope, though."
> > 
> > try {
> >   a();
> > } catch(e) {
> > // getting a stack trace only allowed in catch scope.
> >   alert(e.stack); 
> > }
> > 
> > By limiting access to the catch block, lexical analysis should be easy, right?
> 
> Nope, the problem here would be that the exception object is created somewhere inside a function called by a() you create the error instance, and then start unwinding the stack to find the exception handler.  Until you know what the handler is you can't tell whether it might be doing .stack, and so you have to track the locations you're unwinding through regardless of any other stuff that's going on.   Although I guess you could save the total stack unwind :-/


When entering the try block, analyze catch block to see if it accesses an error stack. If it does, function calls and getters in the try block are given an internal flag when called.

Does this work?
Comment 27 Oliver Hunt 2011-07-11 09:59:31 PDT
> When entering the try block, analyze catch block to see if it accesses an error stack. If it does, function calls and getters in the try block are given an internal flag when called.
> 
> Does this work?

Kills the concept of zero-cost exceptions ;)

That said I want to kill off our zero-cost exceptions in favour of a more old school setjmp/longjmp equivalent for other reasons.

Honestly I'm going to wait for JC to get stack traces implemented and see whether it turns out to be a substantial problem in practice.
Comment 28 Juan C. Montemayor 2011-08-24 22:35:36 PDT
First part of this patch has landed:

Bug 66571 - Keep track of topCallFrame for Stack traces
https://bugs.webkit.org/show_bug.cgi?id=66571
http://trac.webkit.org/changeset/93755

The second half should go up sometime this week.
Comment 29 Gavin Barraclough 2012-03-07 18:12:36 PST

*** This bug has been marked as a duplicate of bug 66994 ***