New Otto Feature: Error.stack
Apr 30, 2016
3 minutes read

While you're reading this, keep in mind that I'm available for hire! If you've got a JavaScript project getting out of hand, or a Golang program that's more "stop" than "go," feel free to get in touch with me. I might be able to help you. You can find my resume here.

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)
}

Back to posts