diff --git a/exception.go b/exception.go new file mode 100644 index 000000000..faed9e847 --- /dev/null +++ b/exception.go @@ -0,0 +1,109 @@ +// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package v8go + +import ( + // #include + // #include "v8go.h" + "C" + + "fmt" + "unsafe" +) + +// NewRangeError creates a RangeError. +func NewRangeError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_RANGE, msg) +} + +// NewReferenceError creates a ReferenceError. +func NewReferenceError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_REFERENCE, msg) +} + +// NewSyntaxError creates a SyntaxError. +func NewSyntaxError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_SYNTAX, msg) +} + +// NewTypeError creates a TypeError. +func NewTypeError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_TYPE, msg) +} + +// NewWasmCompileError creates a WasmCompileError. +func NewWasmCompileError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_WASM_COMPILE, msg) +} + +// NewWasmLinkError creates a WasmLinkError. +func NewWasmLinkError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_WASM_LINK, msg) +} + +// NewWasmRuntimeError creates a WasmRuntimeError. +func NewWasmRuntimeError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_WASM_RUNTIME, msg) +} + +// NewError creates an Error, which is the common thing to throw from +// user code. +func NewError(iso *Isolate, msg string) *Exception { + return newExceptionError(iso, C.ERROR_GENERIC, msg) +} + +func newExceptionError(iso *Isolate, typ C.ErrorTypeIndex, msg string) *Exception { + cmsg := C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + eptr := C.NewValueError(iso.ptr, typ, cmsg) + if eptr == nil { + panic(fmt.Errorf("invalid error type index: %d", typ)) + } + return &Exception{&Value{ptr: eptr}} +} + +// An Exception is a JavaScript exception. +type Exception struct { + *Value +} + +// value implements Valuer. +func (e *Exception) value() *Value { + return e.Value +} + +// Error implements error. +func (e *Exception) Error() string { + return e.String() +} + +// As provides support for errors.As. +func (e *Exception) As(target interface{}) bool { + ep, ok := target.(**Exception) + if !ok { + return false + } + *ep = e + return true +} + +// Is provides support for errors.Is. +func (e *Exception) Is(err error) bool { + eerr, ok := err.(*Exception) + if !ok { + return false + } + return eerr.String() == e.String() +} + +// String implements fmt.Stringer. +func (e *Exception) String() string { + if e.Value == nil { + return "" + } + s := C.ExceptionGetMessageString(e.ptr) + defer C.free(unsafe.Pointer(s)) + return C.GoString(s) +} diff --git a/exception_test.go b/exception_test.go new file mode 100644 index 000000000..1b661ba9b --- /dev/null +++ b/exception_test.go @@ -0,0 +1,79 @@ +// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package v8go_test + +import ( + "errors" + "strings" + "testing" + + v8 "rogchap.com/v8go" +) + +func TestNewError(t *testing.T) { + t.Parallel() + + tsts := []struct { + New func(*v8.Isolate, string) *v8.Exception + WantType string + }{ + {v8.NewRangeError, "RangeError"}, + {v8.NewReferenceError, "ReferenceError"}, + {v8.NewSyntaxError, "SyntaxError"}, + {v8.NewTypeError, "TypeError"}, + {v8.NewWasmCompileError, "CompileError"}, + {v8.NewWasmLinkError, "LinkError"}, + {v8.NewWasmRuntimeError, "RuntimeError"}, + {v8.NewError, "Error"}, + } + for _, tst := range tsts { + t.Run(tst.WantType, func(t *testing.T) { + iso := v8.NewIsolate() + defer iso.Dispose() + + got := tst.New(iso, "amessage") + if !got.IsNativeError() { + t.Error("IsNativeError returned false, want true") + } + if got := got.Error(); !strings.Contains(got, " "+tst.WantType+":") { + t.Errorf("Error(): got %q, want containing %q", got, tst.WantType) + } + }) + } +} + +func TestExceptionAs(t *testing.T) { + iso := v8.NewIsolate() + defer iso.Dispose() + + want := v8.NewRangeError(iso, "faked error") + + var got *v8.Exception + if !want.As(&got) { + t.Fatalf("As failed") + } + + if got != want { + t.Errorf("As: got %#v, want %#v", got, want) + } +} + +func TestExceptionIs(t *testing.T) { + iso := v8.NewIsolate() + defer iso.Dispose() + + t.Run("ok", func(t *testing.T) { + ex := v8.NewRangeError(iso, "faked error") + if !ex.Is(v8.NewRangeError(iso, "faked error")) { + t.Fatalf("Is: got false, want true") + } + }) + + t.Run("notok", func(t *testing.T) { + if (&v8.Exception{}).Is(errors.New("other error")) { + t.Fatalf("Is: got true, want false") + } + }) +} diff --git a/v8go.cc b/v8go.cc index 144470fc2..92632731a 100644 --- a/v8go.cc +++ b/v8go.cc @@ -660,6 +660,48 @@ RtnValue NewValueBigIntFromWords(IsolatePtr iso, return rtn; } +ValuePtr NewValueError(IsolatePtr iso, ErrorTypeIndex idx, const char* message) { + ISOLATE_SCOPE_INTERNAL_CONTEXT(iso); + Local local_ctx = ctx->ptr.Get(iso); + Context::Scope context_scope(local_ctx); + + Local local_msg = String::NewFromUtf8(iso, message).ToLocalChecked(); + Local v; + switch (idx) { + case ERROR_RANGE: + v = Exception::RangeError(local_msg); + break; + case ERROR_REFERENCE: + v = Exception::ReferenceError(local_msg); + break; + case ERROR_SYNTAX: + v = Exception::SyntaxError(local_msg); + break; + case ERROR_TYPE: + v = Exception::TypeError(local_msg); + break; + case ERROR_WASM_COMPILE: + v = Exception::WasmCompileError(local_msg); + break; + case ERROR_WASM_LINK: + v = Exception::WasmLinkError(local_msg); + break; + case ERROR_WASM_RUNTIME: + v = Exception::WasmRuntimeError(local_msg); + break; + case ERROR_GENERIC: + v = Exception::Error(local_msg); + break; + default: + return nullptr; + } + m_value* val = new m_value; + val->iso = iso; + val->ctx = ctx; + val->ptr = Persistent>(iso, v); + return tracked_value(ctx, val); +} + const uint32_t* ValueToArrayIndex(ValuePtr ptr) { LOCAL_VALUE(ptr); Local array_index; @@ -1030,6 +1072,17 @@ int ValueIsModuleNamespaceObject(ValuePtr ptr) { return value->IsModuleNamespaceObject(); } +/********** Exception **********/ + +const char* ExceptionGetMessageString(ValuePtr ptr) { + LOCAL_VALUE(ptr); + + Local local_msg = Exception::CreateMessage(iso, value); + Local local_str = local_msg->Get(); + String::Utf8Value utf8(iso, local_str); + return CopyString(utf8); +} + /********** Object **********/ #define LOCAL_OBJECT(ptr) \ diff --git a/v8go.h b/v8go.h index 339f424b2..6fe23b5e1 100644 --- a/v8go.h +++ b/v8go.h @@ -30,6 +30,17 @@ typedef m_ctx* ContextPtr; typedef m_value* ValuePtr; typedef m_template* TemplatePtr; +typedef enum { + ERROR_RANGE = 1, + ERROR_REFERENCE, + ERROR_SYNTAX, + ERROR_TYPE, + ERROR_WASM_COMPILE, + ERROR_WASM_LINK, + ERROR_WASM_RUNTIME, + ERROR_GENERIC, +} ErrorTypeIndex; + typedef struct { const char* msg; const char* location; @@ -115,6 +126,7 @@ extern RtnValue NewValueBigIntFromWords(IsolatePtr iso_ptr, int sign_bit, int word_count, const uint64_t* words); +extern ValuePtr NewValueError(IsolatePtr iso_ptr, ErrorTypeIndex idx, const char* message); const char* ValueToString(ValuePtr ptr); const uint32_t* ValueToArrayIndex(ValuePtr ptr); int ValueToBoolean(ValuePtr ptr); @@ -181,6 +193,8 @@ int ValueIsProxy(ValuePtr ptr); int ValueIsWasmModuleObject(ValuePtr ptr); int ValueIsModuleNamespaceObject(ValuePtr ptr); +const char* ExceptionGetMessageString(ValuePtr ptr); + extern void ObjectSet(ValuePtr ptr, const char* key, ValuePtr val_ptr); extern void ObjectSetIdx(ValuePtr ptr, uint32_t idx, ValuePtr val_ptr); extern RtnValue ObjectGet(ValuePtr ptr, const char* key); diff --git a/value.go b/value.go index dcfc0ab11..e6153c144 100644 --- a/value.go +++ b/value.go @@ -562,6 +562,13 @@ func (v *Value) AsPromise() (*Promise, error) { return &Promise{&Object{v}}, nil } +func (v *Value) AsException() (*Exception, error) { + if !v.IsNativeError() { + return nil, errors.New("v8go: value is not an Error") + } + return &Exception{v}, nil +} + func (v *Value) AsFunction() (*Function, error) { if !v.IsFunction() { return nil, errors.New("v8go: value is not a Function") diff --git a/value_test.go b/value_test.go index 4b2c019c6..c7a1ee7d8 100644 --- a/value_test.go +++ b/value_test.go @@ -452,6 +452,27 @@ func TestValuePromise(t *testing.T) { } +func TestValueAsException(t *testing.T) { + t.Parallel() + + ctx := v8.NewContext() + defer ctx.Isolate().Dispose() + defer ctx.Close() + + val, _ := ctx.RunScript("1", "") + if _, err := val.AsException(); err == nil { + t.Error("Expected error but got ") + } + val, err := ctx.RunScript("new Error('foo')", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if _, err := val.AsException(); err != nil { + t.Errorf("Expected success but got: %v", err) + } + +} + func TestValueFunction(t *testing.T) { t.Parallel()