While you're reading this, keep in mind that I'm available for hire stupid!
Popular JavaScript runtimes (V8 1, SpiderMonkey 2, IE 3) provide a
stack
property on Error
objects. In these engines, this property holds a
string representation of the call stack at the time the error was constructed.
Unfortunately, there’s no specification on how this is meant to work, so every
browser kind of just wings it and does whatever they want.
This is some code that executes a couple of functions, and throws an exception
a few levels deep in the call stack. It catches that exception and prints out
the stack
property of the error.
function A() { throw new Error(); }
function B() { A(); }
function C() { B(); }
try { C() } catch (e) { console.log(e.stack); }
Here’s what the stack trace looks like in a few different engines. I trimmed out any console-related stack frames, as each browser actually implements its console as a JavaScript program. This is just to reduce the noise a little bit - it makes no functional difference.
V8
Error
at A (<anonymous>:1:22)
at B (<anonymous>:2:16)
at C (<anonymous>:3:16)
at <anonymous>:4:7
Yep, okay. That looks fine. If this code was in a file, anonymous
would be a
filename instead.
SpiderMonkey
A@debugger eval code:1:22
B@debugger eval code:2:16
C@debugger eval code:3:16
@debugger eval code:4:7
Fair enough. Not as readable, but not too bad. I can still find where the error came from. Again, if there were a file involved, this would be even better.
Safari
A
B
C
eval code
Safari. Please. Even for you, this is disappointing. No line numbers, no file information. Nearly useless.
Otto Implementation
Otto actually already has stack trace formatting, since we provide that as the
String()
method on otto.Error
.
This is what otto stack traces look like. They’re very similar to V8 stack traces. They will change in the near future though, once my patch for adding native functions to the call stack lands in master.
Error
at A (<anonymous>:1:22)
at B (<anonymous>:2:16)
at C (<anonymous>:3:16)
at <anonymous>:4:7
Today I wired that up to
Error.stack
so it’s accessible from JavaScript code. I put it behind a
getter function so it’s not generated until it’s needed - I’m pretty sure this
is also what V8 does, but I haven’t read the source yet to confirm that.
Below is the main section where this is implemented. As you can see, this is mostly made up of private methods. I’d like to expose most of these to user code, but that’ll have to be a job for another day.
func (rt *_runtime) newErrorObject(name string, message Value) *_object {
self := rt.newClassObject("Error")
// ...
self.defineOwnProperty("stack", _property{
value: _propertyGetSet{
rt.newNativeFunction("get", func(FunctionCall) Value {
return toValue_string(self.value.(_error).formatWithStack())
}),
&_nilGetSetObject,
},
mode: modeConfigureMask & modeOnMask,
}, false)
}