robertkrimen / otto

A JavaScript interpreter in Go (golang)
http://godoc.org/github.com/robertkrimen/otto
MIT License
8.04k stars 584 forks source link

No way to throw exceptions from native code #17

Open BenLubar opened 11 years ago

BenLubar commented 11 years ago

In a func(otto.FunctionCall) otto.Value, there is no (documented) way to throw a JavaScript exception that the caller can see.

robertkrimen commented 11 years ago

Yes, this is something that needs work. Do you have any examples of what you're trying to achieve?

cznic commented 11 years ago

FWIW, an example, schematically:

js helper

function mkfn(obj, fn) {
        var oldfn = obj[fn];
        obj[fn] = function() {
                var v = oldfn.apply(this, arguments);
                if (v.constructor == Error) {
                        throw v;
                };

                return v;
        };
}

Go errf

func (vm *VM) errf(e error) (r js.Value) {
        if e == nil {
                return js.UndefinedValue()
        }

        r, err := vm.jsvm.Run(fmt.Sprintf("new Error('%s')", template.JSEscapeString(e.Error())))
        if err != nil {
                log.Fatal(err)
        }

        return
}

Example native binding

func (vm *VM) dbArrays(o *js.Object, db *dbm.DB) {
        const (
                obj    = "db"
                method = "arrays"
        )
        if err := o.Set(method, func(call js.FunctionCall) (r js.Value) {
                defer func() {
                        if e := recover(); e != nil {
                                r = vm.errf(fmt.Errorf("%s.%s: %v", obj, method, e))
                        }
                }()

                a, err := db.Arrays()
                if err != nil {
                        panic(err)
                }

                o, err := vm.jsvm.Object(`new Object();`)
                if err != nil {
                        panic(err)
                }

                vm.array(o, &a)
                return o.Value()
        }); err != nil {
                panic(err)
        }

        if _, err := vm.mkfn.Call(js.UndefinedValue(), o, method); err != nil {
                panic(err)
        }
}

Full code: https://github.com/cznic/tmp/blob/14d211b7818be2225f12f80fa299f0176ae771cb/g/glue.go

robertkrimen commented 11 years ago

So what you're trying to do is throw a panic from the inner Go code to the other Go code?

cznic commented 11 years ago

No, the machinery allows the javascript clients to catch errors reported by Go native functions. On May 19, 2013 6:56 PM, "Robert Krimen" notifications@github.com wrote:

So what you're trying to do is initiate a panic from the inner Go code to the other Go code?

— Reply to this email directly or view it on GitHubhttps://github.com/robertkrimen/otto/issues/17#issuecomment-18120767 .

robertkrimen commented 11 years ago

Yes, I think a Go panic should be visible within a try { ... } catch { ... }, probably as a PanicError, with no extra work necessary.

I also think a (special) piercing panic should be possible, something that can bypass JavaScript.

cznic commented 11 years ago

My example is not about Go panicking. It's about Go errors reporting. The particular implementation reads data from a disk based database. Read errors or format corruption may occur. The native funtion needs to throw a javascript exception in that case - instead of returning, say invalid data via the javasript wrapped native function - as that cannot support multiple return values, which Go can. On May 19, 2013 8:06 PM, "Robert Krimen" notifications@github.com wrote:

Yes, I think a Go panic should be visible within a try { ... } catch { ... }, probably as a PanicError, with no extra work necessary.

I also think a (special) piercing panic should be possible, something that can bypass JavaScript.

— Reply to this email directly or view it on GitHubhttps://github.com/robertkrimen/otto/issues/17#issuecomment-18121942 .

robertkrimen commented 11 years ago

This is partially addressed in e2e79bb6974958a59a61c5c179bd3f88645bc36a

If you panic() something of type otto.Value, then it will behave as if you did a "throw" in JavaScript.

For example: panic(Otto.ToValue("Hello, World.")) is the same as: throw "Hello, World.";

robertkrimen commented 11 years ago

This is further addressed in 83c56dd73d07607907bdc72e0d4998023855adc7

I've added a .Call method to Otto, which allows you to call arbitrary JavaScript and return the result.

You can now do something like this in your Go code:

value, _ := call.Otto.Call("new Error", nil, "Something bad happened.")
panic(value)

If you want, a possible convenience function is:

func throw(value Value, _ error) Value {
     panic(value)
     return UndefinedValue()
}

With these two, you can do:

return throw(call.Otto.Call("new Error", nil, "Something bad happened.")

And it'll throw that exception in the JavaScript environment like you would expect

cznic commented 11 years ago

Looks quite good to me. Thanks!

robertkrimen commented 10 years ago

Be careful when doing a raw panic of a complex object:

panic(someObject)
panic(valueOfAnObject)

The above may cause memory issues with the Go runtime, see #59

BenLubar commented 10 years ago

If panic(someObject) uses memory even after recover() is called, that's a Go bug.