From d9a0b1b6174604f7f0df6b6b1e29bea9b62c19f5 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Mon, 31 May 2021 16:12:24 -0700 Subject: [PATCH] Add Function.NewInstance to expose the corresponding V8 function (#134) --- CHANGELOG.md | 3 +++ context.go | 7 +++++++ function.go | 16 +++++++++++++++ function_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ v8go.cc | 32 ++++++++++++++++++++++++++---- v8go.h | 1 + 6 files changed, 106 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c5f6a9..c8b7a47a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Support for calling constructors functions with NewInstance on Function + ## [v0.6.0] - 2021-05-11 ### Added diff --git a/context.go b/context.go index a4190091..4c72d977 100644 --- a/context.go +++ b/context.go @@ -179,6 +179,13 @@ func getValue(ctx *Context, rtn C.RtnValue) *Value { return &Value{rtn.value, ctx} } +func getObject(ctx *Context, rtn C.RtnValue) *Object { + if rtn.value == nil { + return nil + } + return &Object{&Value{rtn.value, ctx}} +} + func getError(rtn C.RtnValue) error { if rtn.error.msg == nil { return nil diff --git a/function.go b/function.go index f529a697..bf403f1f 100644 --- a/function.go +++ b/function.go @@ -30,3 +30,19 @@ func (fn *Function) Call(args ...Valuer) (*Value, error) { fn.ctx.deregister() return getValue(fn.ctx, rtn), getError(rtn) } + +// Invoke a constructor function to create an object instance. +func (fn *Function) NewInstance(args ...Valuer) (*Object, error) { + var argptr *C.ValuePtr + if len(args) > 0 { + var cArgs = make([]C.ValuePtr, len(args)) + for i, arg := range args { + cArgs[i] = arg.value().ptr + } + argptr = (*C.ValuePtr)(unsafe.Pointer(&cArgs[0])) + } + fn.ctx.register() + rtn := C.FunctionNewInstance(fn.ptr, C.int(len(args)), argptr) + fn.ctx.deregister() + return getObject(fn.ctx, rtn), getError(rtn) +} diff --git a/function_test.go b/function_test.go index 4a9ff1d3..00c72f06 100644 --- a/function_test.go +++ b/function_test.go @@ -86,6 +86,57 @@ func TestFunctionCallError(t *testing.T) { } } +func TestFunctionNewInstance(t *testing.T) { + t.Parallel() + + ctx, err := v8go.NewContext() + failIf(t, err) + iso, err := ctx.Isolate() + failIf(t, err) + + value, err := ctx.Global().Get("Error") + failIf(t, err) + fn, err := value.AsFunction() + failIf(t, err) + messageObj, err := v8go.NewValue(iso, "test message") + failIf(t, err) + errObj, err := fn.NewInstance(messageObj) + failIf(t, err) + + message, err := errObj.Get("message") + failIf(t, err) + if !message.IsString() { + t.Error("missing error message") + } + want := "test message" + got := message.String() + if got != want { + t.Errorf("want %+v, got: %+v", want, got) + } +} + +func TestFunctionNewInstanceError(t *testing.T) { + t.Parallel() + + ctx, err := v8go.NewContext() + failIf(t, err) + _, err = ctx.RunScript("function throws() { throw 'error'; }", "script.js") + failIf(t, err) + throwsValue, err := ctx.Global().Get("throws") + failIf(t, err) + fn, _ := throwsValue.AsFunction() + + _, err = fn.NewInstance() + if err == nil { + t.Errorf("expected an error, got none") + } + got := *(err.(*v8go.JSError)) + want := v8go.JSError{Message: "error", Location: "script.js:1:21"} + if got != want { + t.Errorf("want %+v, got: %+v", want, got) + } +} + func failIf(t *testing.T, err error) { t.Helper() if err != nil { diff --git a/v8go.cc b/v8go.cc index 464eedb9..acff4cd3 100644 --- a/v8go.cc +++ b/v8go.cc @@ -1160,15 +1160,20 @@ ValuePtr PromiseResult(ValuePtr ptr) { /********** Function **********/ +static void buildCallArguments(Isolate* iso, Local *argv, int argc, ValuePtr args[]) +{ + for (int i = 0; i < argc; i++) { + m_value* arg = static_cast(args[i]); + argv[i] = arg->ptr.Get(iso); + } +} + RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr args[]) { LOCAL_VALUE(ptr) RtnValue rtn = {nullptr, nullptr}; Local fn = Local::Cast(value); Local argv[argc]; - for (int i = 0; i < argc; i++) { - m_value* arg = static_cast(args[i]); - argv[i] = arg->ptr.Get(iso); - } + buildCallArguments(iso, argv, argc, args); Local recv = Undefined(iso); MaybeLocal result = fn->Call(local_ctx, recv, argc, argv); if (result.IsEmpty()) { @@ -1183,6 +1188,25 @@ RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr args[]) { return rtn; } +RtnValue FunctionNewInstance(ValuePtr ptr, int argc, ValuePtr args[]) { + LOCAL_VALUE(ptr) + RtnValue rtn = {nullptr, nullptr}; + Local fn = Local::Cast(value); + Local argv[argc]; + buildCallArguments(iso, argv, argc, args); + MaybeLocal result = fn->NewInstance(local_ctx, argc, argv); + if (result.IsEmpty()) { + rtn.error = ExceptionError(try_catch, iso, local_ctx); + return rtn; + } + m_value* rtnval = new m_value; + rtnval->iso = iso; + rtnval->ctx = ctx; + rtnval->ptr = Persistent>(iso, result.ToLocalChecked()); + rtn.value = tracked_value(ctx, rtnval); + return rtn; +} + /******** Exceptions *********/ ValuePtr ExceptionError(IsolatePtr iso_ptr, const char* message) { diff --git a/v8go.h b/v8go.h index 1dbf5aec..dc93feeb 100644 --- a/v8go.h +++ b/v8go.h @@ -179,6 +179,7 @@ ValuePtr PromiseCatch(ValuePtr ptr, int callback_ref); extern ValuePtr PromiseResult(ValuePtr ptr); extern RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr argv[]); +RtnValue FunctionNewInstance(ValuePtr ptr, int argc, ValuePtr args[]); extern ValuePtr ExceptionError(IsolatePtr iso_ptr, const char* message); extern ValuePtr ExceptionRangeError(IsolatePtr iso_ptr, const char* message);