standardizing Error.stack or equivalent

# John Lenz (7 years ago)

I was recently modifying some code to be strict mode compliant and it reminded me that the primary use of the Function caller property and arguments.caller is to build stack traces. Now the latest Internet Explorer releases have support for stack traces, as of course do Chrome, FF, and Safari but only Chrome/V8, to my knowledge, has an actual API.

I know there was some initial work in this area and nothing is likely to happen in the ES6 time frame but can something to be done to make the stacks traces more usable?

# Rick Waldron (7 years ago)

Take a look at the work Erik Arvidsson has done so far:

strawman:error_stack

# Mark S. Miller (7 years ago)

see also my message at < esdiscuss/2014-March/036642> which

cites some of your work on sourcemaps.

# John Lenz (7 years ago)

Interesting sourcemap usage. But is there any hope for standardization of the existing stack handling for ES7? It wasn't clear to me why it stalled for ES6. There a few things I would like to see:

  1. standardization V8's Error.captureStackTrace API
  2. standardization of the stack format
  3. standardizaton of when the stack is added to the Error object (creation vs throw)
  4. specification as to whether throw (and re-throw) overwrite any existing stack property

More would be welcome but that is what I would actually have an immediate use for.

# Mark S. Miller (7 years ago)

Not only would I hope for all of this in ES7, I would add

5. sourcemaps

6. sourcemap extension to template strings, as in that old email

7. The sourceURL as explained at developers.google.com/chrome-developer-tools/docs/javascript-debugging#breakpoints-dynamic-javascript or something with equivalent functionality.

The main thing needed to get this into ES7 is a champion who will put in the time needed. That ain't me, but I'd be happy to help.

Btw, if I were looking to drop something from the list, I'd look first to simplify #1.

# Christian Plesner Hansen (7 years ago)

Just curious: do you have any particular parts of #1 in mind that could be simplified?

# Erik Arvidsson (7 years ago)

To be clear. Changing .stack is not an option. We need to introduce a new API.

On Tue Mar 25 2014 at 4:40:20 PM, John Lenz <concavelenz at gmail.com> wrote:

Interesting sourcemap usage. But is there any hope for standardization of the existing stack handling for ES7? It wasn't clear to me why it stalled for ES6. There a few things I would like to see:

  1. standardization V8's Error.captureStackTrace API

The V8 API has some issues that it returns objects (getThis, getFunction and getEvalOrigin). For security reasons we would want to limit what the API gives you to names and locations.

  1. standardization of the stack format

We can probably reuse V8's and Chakra's toString format here.

  1. standardizaton of when the stack is added to the Error object (creation vs throw)

Since we have to do a new API we can decide either way. Personally I prefer at creation maybe there are some performance benefits to only add it on throw?

  1. specification as to whether throw (and re-throw) overwrite any existing stack property

Same here. New API. What is the desired behavior?

# Mark Miller (7 years ago)

On Thu, Mar 27, 2014 at 9:11 AM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:

The V8 API has some issues that it returns objects (getThis, getFunction and getEvalOrigin). For security reasons we would want to limit what the API gives you to names and locations.

+1.

We can probably reuse V8's and Chakra's toString format here.

I think that's a good place to start, and code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/debug.js#158 attempts to normalize other browsers to the v8 format. However, in doing so we discovered that FF Nightly 30 includes useful extra information, regarding nested eval contexts, which this code throws away with the "FFEvalLineColPatterns" regexp. Before adopting the v8 format, we should discuss whether this extra information is useful enough that we should find a way to keep it. Mozillians, why did you add this extra information?

Also, rather than startline/startcolumn, we should really do what Smalltalk has done forever: startline/startcolumn/endline/endcolumn.

Same here. New API. What is the desired behavior?

The stack should not be accessible given only the error object. Rather, there should be a getStack function which, given an error object, returns the stack. That way, code which does not have access to the getStack function cannot see the stacks.

# Anne van Kesteren (7 years ago)

On Thu, Mar 27, 2014 at 4:31 PM, Mark Miller <erights at gmail.com> wrote:

The stack should not be accessible given only the error object. Rather, there should be a getStack function code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/debug.js#300 which, given an error object, returns the stack. That way, code which does not have access to the getStack function cannot see the stacks.

Given the existing API which we cannot remove, isn't it too late for that? Why would we want the asymmetry?

# Mark S. Miller (7 years ago)

Which existing API do you mean?

What asymmetry?

# John Lenz (7 years ago)

On Thu, Mar 27, 2014 at 9:11 AM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:

The V8 API has some issues that it returns objects (getThis, getFunction and getEvalOrigin). For security reasons we would want to limit what the API gives you to names and locations.

I think you mean prepareStackTrace here. "captureStackTrace" allows you apply the stack trace while ignoring zero or more known methods in the call stack.

We can probably reuse V8's and Chakra's toString format here.

That would be awesome by itself.

Since we have to do a new API we can decide either way. Personally I prefer at creation maybe there are some performance benefits to only add it on throw?

If "prepareStackTrace" is available and can reset a stack trace, adding it on throw if not set seems sufficient.

Same here. New API. What is the desired behavior?

If "prepareStackTrace" is available and can reset a stack trace, adding it on rethrow if not set seems sufficient. I haven't looked at existing behavior.

# Jason Orendorff (7 years ago)

On Thu, Mar 27, 2014 at 12:31 PM, Mark Miller <erights at gmail.com> wrote:

Mozillians, why did you add this extra information?

Well... if the point of Error.stack is to provide diagnostic information for exceptions, for the purpose of debugging, this information is critical.

Without this, eval() or Function() often won't appear on the stack anywhere. What we were doing before didn't reveal that eval or Function was involved at all. It was misleading and unhelpful.

# Mark S. Miller (7 years ago)

Is this extended format documented anywhere?

# Jason Orendorff (7 years ago)

Not formally, but it's straightforward:

At the time you call eval() or Function(), we capture the string

fileName + " line " + lineNumber + " > eval"

or "> Function" if you're calling Function. We use that string as the fileName for the dynamic eval or Function code. This affects both err.fileName and err.stack.

This implies these synthetic fileNames can nest. You can end up with a stack like:

js> try { eval("eval('FAIL')"); } catch (x) { print(x.stack); }
@typein line 2 > eval line 1 > eval:1:1
@typein line 2 > eval:1:1
@typein:2:7

I think the extended format was added in this bug: bugzilla.mozilla.org/show_bug.cgi?id=332176

# Mark Miller (7 years ago)
# Boris Zbarsky (7 years ago)

No. It's missing the Function case and doesn't do what I think you want it to be doing in the nested eval/Function case.

# Boris Zbarsky (7 years ago)

On 3/27/14 12:31 PM, Mark Miller wrote:

Mozillians, why did you add this extra information?

Looks like it was added in bugzilla.mozilla.org/show_bug.cgi?id=332176

Before that, the stack claimed the url of the caller of eval() but a line number which was the sum of the line number of the eval() call itself and the line number of the code throwing the exception inside the eval string, I believe, or some such insanity.

There's a lot of noise there in the discussion, but I think the key recent part is bugzilla.mozilla.org/show_bug.cgi?id=332176#c40 which summarizes what I think we implemented. Note that per bugzilla.mozilla.org/show_bug.cgi?id=332176#c42 we do something similar for new Function() as well, so you can get a stack trace like so:

anonymous at http://example.com line 3 > Function:1:1 @http://example.com:3:5

for a testcase like this:

<script>
   try {
     new Function("throw new Error()")();
   } catch (e) {
     document.write(e.stack);
   }
</script>
# Boris Zbarsky (7 years ago)

(Duplicate message)

# Christian Plesner Hansen (7 years ago)
js> try { eval("eval('FAIL')"); } catch (x) { print(x.stack); }
@typein line 2 > eval line 1 > eval:1:1
@typein line 2 > eval:1:1
@typein:2:7

I'm unclear on what the problem is with nested evals -- you get essentially the same information from v8:

js> try { eval("eval('FAIL')"); } catch (x) { console.log(x.stack); }

ReferenceError: FAIL is not defined
    at eval (eval at <anonymous> (eval at <anonymous> (repl:1:7)),
    <anonymous>:1:1)
    at eval (eval at <anonymous> (repl:1:7), <anonymous>:1:1)
    at repl:1:7
# Mark Miller (7 years ago)

I wasn't aware that v8 does that. Is this format documented anywhere?

# Christian Plesner Hansen (7 years ago)
# Mark S. Miller (7 years ago)

[+google-caja-discuss]

Cool. I will fix the debug.js adaptor a) to fix the bug Boris reported, b) to map the FF nested format to the v8 nested format, and c) to preserve this information from the v8 API so that it appears correctly on v8 as well. This will require extending the Causeway stack trace format (an encoding of stack trace info into JSON), which looks like it should be straightforward. Thanks!

Once extended in this way, would it be useful to standardize the Causeway stack trace representation, so others can avoid trying to parse the stack trace strings with regexps?

# Jason Orendorff (7 years ago)

On Thu, Mar 27, 2014 at 5:23 PM, Christian Plesner Hansen <c7n at p5r.org> wrote:

I'm unclear on what the problem is with nested evals -- you get essentially the same information from v8:

No problem. Just pointing out that it happens.

# John Barton (7 years ago)

Notice that the eval stack trace is not very useful in the common case that the buffer is more complex than a single line and the eval is called more than once. For eval, new Function(), document.write(<script>), document.appendChild(<script>), and System.module() successful debugging -- and thus stack traces -- require unique, stable names for the buffers. With these names, debugging with these features is no different than normal source; without names, stack traces have limited value.

Stable automatic naming is difficult, since the code that uses these features often does so inside of asynchronous loops (for loading). Making it easy for developers to provide names is the simplest improvement for these dynamic features. We could have names provided to eval() and new Function(); we should ensure that new API has names, like System.module(). That would make a standard stack trace more useful.

# Mark S. Miller (7 years ago)

supposedly developers.google.com/chrome-developer-tools/docs/javascript-debugging#breakpoints-dynamic-javascript and www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl document the emerging de facto std for providing this name to the existing API. However, I have not seen it work on any browser. See point #5 at esdiscuss/2014-March/036642. Am I doing something wrong here that account for my not seeing this, or does it in fact not work anywhere?

# Sebastian Zartner (7 years ago)

sourceURL and displayName are also working in Firefox. Not consistently yet, though. FWIW Firebug is making use of these in it's 2.0 alpha 1 version. See www.softwareishard.com/blog/firebug/firebug-2-support-for-dynamic-scripts. Would be great if these could be standardized.

# John Lenz (7 years ago)

sourceUrl works in chrome, with eval at least. Is displayName for function naming?

# John Barton (7 years ago)

I implemented the first sourceURL support, for Firebug back in the day. It's a kludge we worked out because there was no alternative. It forces debuggers seek the ends of buffers even if they are millions of bytes and even if the the name does not turn out to be the one the dev was interested in. It forces developers to use a whacky naming 'API' involving funky characters and special strings.

//# sourceURL woulds well in Firebug, Chrome Devtools, and IE as far as I know. It fails in Chrome devtools in two cases, for early compiles before devtools is opened (known limitation) and when using the relatively new devtools workspaces in some complicated cases. Otherwise you should report bugs if it fails for you.

When new API arrives we don't have to miss the opportunity to do better.

Your references also include source-map discussion which uses same kludgy API. Based on my experience, I think the low reliability of source-maps is partly due to difficulty in testing and reporting bugs with the generated maps. I thought the maps I created for traceur were fine, but later I discovered that the ends of ASTs were not handled correctly but all the user can observe is "the sourcemap gives wacky results, sometimes".

jjb

# John Lenz (7 years ago)

John did you want to propose an alternative? Maybe: A "name" parameter for inline scripts and eval? This might work for "sourceUrl" but might be limiting for "soureMappingUrl".

# John Barton (7 years ago)

My goal is more modest: to ensure that Loader/System function(s) that accept strings defining JS allow name parameters.

We should also work out how to include source-maps. The loader has the right position to obtain the sourcemap from the server or from transcoders. If we have API on the loader to tell the debugger about the map we can have a much more flexible solution.

# John Lenz (7 years ago)

I agree the loader provides a new place to inject this information.

# Christoph Martens (7 years ago)

Hey guys,

I wanted to chime in on the discussion about Error.stack standardization. For my lil' game engine (lycheejs.org) I tried a couple things out how a custom Debugger (for a binary WebSocket protocol) can be implemented properly. I need to write an own debugger, because I'm using NodeJS + libsdl2 to cross-compile to Android and other platforms (like gaming consoles). So the problem here is that I also need several features that are not available in the Browser context, with the same API for Browser context environments. Also, bug reports with snapshots of the game are a huge thing as you could guess.

Four things I found out:

  1. What purpose is Error.prepareStackTrace for? All tutorials do nothing more than returning the stack argument. You can't experiment with it, because it mostly crashes the VM in endless loops (at least on my RT Ubuntu) on coding mistakes. Can anyone explain it? Sorry for my bad Google Fu in this case.

  2. For writing a Debugger, you may want to give hints to users that try to debug their own code. Like "Hey, it seems you wanted to access global.addEventListener, but this is no function and it's not available". I came to the result that such a hint implementation for generic properties and their logged accesses is not possible without Proxies (correct me if I'm wrong). I tried to have a loop running around a with block to determine those properties generically - and running the loop around it as long as it fails, but yeah - you could guess the hackiness level of that code.

  3. A stack trace analysis tool might also want to auto-catch future occuring Errors and stop the VM. If I have a loop that has a try {} catch(e) { } finally {} block, and there is an Error happening inside the catch block (due to live coding and not being a perfect guy) - yeah... that sucks hard with Cache Reload stuff, because VM crashes before I can reload most times.

  4. Why has an Error NO information about the file and line where it occured? Seriously, throwing a new Error() and tracing it, just for getting error.stack[0]'s information is like a complete design fail in my opinion. I think the Error should have the callsite information attached where it occured, with an optional getStacktrace() method attached to it (might be better for performance reasons?). That's the simplest API I could imagine; In my opinion the prepareStackTrace API is a bit overengineered and it is hard to trace multiple errors intelligently with it, as it's static. If it would be attached to the Error itself, it would ease up things for a custom debugger.

That's what I had in mind...

Cheers from Heidelberg, ~Christoph

# Mark S. Miller (5 years ago)

On Thu, Mar 27, 2014 at 3:28 PM, Mark S. Miller <erights at google.com> wrote:

[+google-caja-discuss]

Cool. I will fix the debug.js adaptor a) to fix the bug Boris reported, b) to map the FF nested format to the v8 nested format, and c) to preserve this information from the v8 API so that it appears correctly on v8 as well. This will require extending the Causeway stack trace format (an encoding of stack trace info into JSON), which looks like it should be straightforward. Thanks!

Once extended in this way, would it be useful to standardize the Causeway stack trace representation, so others can avoid trying to parse the stack trace strings with regexps?

This was issue google/caja#1906 which we just closed with codereview.appspot.com/256790043 . See the definition of the Extended Causeway JSON stacktrace format starting at < google/caja/blob/master/src/com/google/caja/ses/debug.js#L36>,

line 36.

Due to code.google.com/p/v8/issues/detail?id=4268, we need to do

some unreliable scraping even on v8 to recover this info. This is open to attack. As it says at < google/caja/blob/master/src/com/google/caja/ses/debug.js#L184

:

There are a variety of user-triggered conditions that can cause this scraping to fail, such as a methodName that contains an "(" or "@" character.

Standardizing on the JSON for the Extended Causeway Stacktrace would make this necessarily-unreliable scraping unnecessary on all future std JS platforms.