From 83a58350e74fdba31cd78148b6baee8edf1006af Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Wed, 1 Sep 2021 15:55:18 -0700 Subject: [PATCH 1/9] Support calling a method on an object. --- object.go | 30 ++++++++++++++++++++++++++++++ object_test.go | 20 ++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/object.go b/object.go index 4c1dc5c5..c4392993 100644 --- a/object.go +++ b/object.go @@ -19,6 +19,36 @@ type Object struct { *Value } +func (o *Object) MethodCall(methodName string, args ...Valuer) (*Value, error) { + ckey := C.CString(methodName) + defer C.free(unsafe.Pointer(ckey)) + + getRtn := C.ObjectGet(o.ptr, ckey) + err := getError(getRtn) + if err != nil { + return nil, err + } + fnVal := getValue(o.ctx, getRtn) + + fn, err := fnVal.AsFunction() + if err != nil { + return nil, err + } + + 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.FunctionCall(fn.ptr, o.ptr, C.int(len(args)), argptr) + fn.ctx.deregister() + return getValue(fn.ctx, rtn), getError(rtn) +} + // Set will set a property on the Object to a given value. // Supports all value types, eg: Object, Array, Date, Set, Map etc // If the value passed is a Go supported primitive (string, int32, uint32, int64, uint64, float64, big.Int) diff --git a/object_test.go b/object_test.go index b4c2b8d5..b9173f47 100644 --- a/object_test.go +++ b/object_test.go @@ -11,6 +11,26 @@ import ( "rogchap.com/v8go" ) +func TestObjectMethodCall(t *testing.T) { + t.Parallel() + + ctx, err := v8go.NewContext() + failIf(t, err) + val, err := ctx.RunScript(`class Obj { constructor(input) { this.input = input } print() { return this.input.toString() } }; new Obj("some val")`, "") + failIf(t, err) + obj, err := val.AsObject() + failIf(t, err) + val, err = obj.MethodCall("print") + failIf(t, err) + if val.String() != "some val" { + t.Errorf("unexpected value: %q", val) + } + _, err = obj.MethodCall("nope") + if err == nil { + t.Errorf("expected an error, got none") + } +} + func TestObjectSet(t *testing.T) { t.Parallel() From e6766823056f16380b7e49b3223dc2e3eb1378a0 Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Wed, 1 Sep 2021 16:13:33 -0700 Subject: [PATCH 2/9] Fix tests, support backup FunctionCall for undefined case --- function.go | 2 +- v8go.cc | 16 +++++++++++++--- v8go.h | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/function.go b/function.go index dd13b7b3..a041f659 100644 --- a/function.go +++ b/function.go @@ -26,7 +26,7 @@ func (fn *Function) Call(args ...Valuer) (*Value, error) { argptr = (*C.ValuePtr)(unsafe.Pointer(&cArgs[0])) } fn.ctx.register() - rtn := C.FunctionCall(fn.ptr, C.int(len(args)), argptr) + rtn := C.FunctionCall(fn.ptr, nil, C.int(len(args)), argptr) fn.ctx.deregister() return getValue(fn.ctx, rtn), getError(rtn) } diff --git a/v8go.cc b/v8go.cc index 1cb88aff..a16d4196 100644 --- a/v8go.cc +++ b/v8go.cc @@ -1216,14 +1216,24 @@ static void buildCallArguments(Isolate* iso, } } -RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr args[]) { +RtnValue FunctionCall(ValuePtr ptr, ValuePtr recv, int argc, ValuePtr args[]) { LOCAL_VALUE(ptr) + RtnValue rtn = {nullptr, nullptr}; Local fn = Local::Cast(value); Local argv[argc]; buildCallArguments(iso, argv, argc, args); - Local recv = Undefined(iso); - MaybeLocal result = fn->Call(local_ctx, recv, argc, argv); + + MaybeLocal result; + + if (recv == NULL) { + result = fn->Call(local_ctx, Undefined(iso), argc, argv); + } else { + m_value* objVal = static_cast(recv); + Local val = objVal->ptr.Get(iso); + Local obj = val.As(); + result = fn->Call(local_ctx, obj, argc, argv); + } if (result.IsEmpty()) { rtn.error = ExceptionError(try_catch, iso, local_ctx); return rtn; diff --git a/v8go.h b/v8go.h index 51651edc..d5364e2e 100644 --- a/v8go.h +++ b/v8go.h @@ -182,7 +182,7 @@ ValuePtr PromiseThen2(ValuePtr ptr, int on_fulfilled_ref, int on_rejected_ref); ValuePtr PromiseCatch(ValuePtr ptr, int callback_ref); extern ValuePtr PromiseResult(ValuePtr ptr); -extern RtnValue FunctionCall(ValuePtr ptr, int argc, ValuePtr argv[]); +extern RtnValue FunctionCall(ValuePtr ptr, ValuePtr recv, int argc, ValuePtr argv[]); RtnValue FunctionNewInstance(ValuePtr ptr, int argc, ValuePtr args[]); ValuePtr FunctionSourceMapUrl(ValuePtr ptr); From 0842e5f44c7259faf3556eaf6330c782c6756f9c Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Wed, 1 Sep 2021 16:17:20 -0700 Subject: [PATCH 3/9] Add assertion for block where val is converted to function - Besides verifying that the obj.Get returns the err properly, we also want to test that fnVal.AsFunction returns the err properly when something is not a function --- object_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/object_test.go b/object_test.go index b9173f47..406875b1 100644 --- a/object_test.go +++ b/object_test.go @@ -16,7 +16,7 @@ func TestObjectMethodCall(t *testing.T) { ctx, err := v8go.NewContext() failIf(t, err) - val, err := ctx.RunScript(`class Obj { constructor(input) { this.input = input } print() { return this.input.toString() } }; new Obj("some val")`, "") + val, err := ctx.RunScript(`class Obj { constructor(input) { this.input = input, this.prop = "" } print() { return this.input.toString() } }; new Obj("some val")`, "") failIf(t, err) obj, err := val.AsObject() failIf(t, err) @@ -25,6 +25,10 @@ func TestObjectMethodCall(t *testing.T) { if val.String() != "some val" { t.Errorf("unexpected value: %q", val) } + _, err = obj.MethodCall("prop") + if err == nil { + t.Errorf("expected an error, got none") + } _, err = obj.MethodCall("nope") if err == nil { t.Errorf("expected an error, got none") From ac6ad530c3d765a490041be5e6a1ac99acf1c868 Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Thu, 2 Sep 2021 10:48:37 -0700 Subject: [PATCH 4/9] Test no-op method and methodcall with args. --- object.go | 4 +--- object_test.go | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/object.go b/object.go index c4392993..e563b3d9 100644 --- a/object.go +++ b/object.go @@ -28,9 +28,7 @@ func (o *Object) MethodCall(methodName string, args ...Valuer) (*Value, error) { if err != nil { return nil, err } - fnVal := getValue(o.ctx, getRtn) - - fn, err := fnVal.AsFunction() + fn, err := getValue(o.ctx, getRtn).AsFunction() if err != nil { return nil, err } diff --git a/object_test.go b/object_test.go index 406875b1..278368ee 100644 --- a/object_test.go +++ b/object_test.go @@ -14,22 +14,23 @@ import ( func TestObjectMethodCall(t *testing.T) { t.Parallel() - ctx, err := v8go.NewContext() - failIf(t, err) - val, err := ctx.RunScript(`class Obj { constructor(input) { this.input = input, this.prop = "" } print() { return this.input.toString() } }; new Obj("some val")`, "") - failIf(t, err) - obj, err := val.AsObject() - failIf(t, err) - val, err = obj.MethodCall("print") + ctx, _ := v8go.NewContext() + iso := ctx.Isolate() + val, _ := ctx.RunScript(`class Obj { constructor(input) { this.input = input, this.prop = "" } print() { return this.input.toString() } }; new Obj("some val")`, "") + obj, _ := val.AsObject() + val, err := obj.MethodCall("print") failIf(t, err) if val.String() != "some val" { t.Errorf("unexpected value: %q", val) } - _, err = obj.MethodCall("prop") - if err == nil { - t.Errorf("expected an error, got none") - } - _, err = obj.MethodCall("nope") + + val, err = ctx.RunScript(`class Obj2 { print(str) { return str.toString() } }; new Obj2()`, "") + failIf(t, err) + obj, _ = val.AsObject() + arg, _ := v8go.NewValue(iso, "arg") + _, err = obj.MethodCall("print", arg) + failIf(t, err) + _, err = obj.MethodCall("notamethod") if err == nil { t.Errorf("expected an error, got none") } From 85f8c25791bb925a1163fe15967dceb985df0aa3 Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Thu, 2 Sep 2021 11:24:14 -0700 Subject: [PATCH 5/9] Use getter to vet error handling in C.ObjectGet - Add back test for methodcall on not-a-function --- object_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/object_test.go b/object_test.go index 278368ee..ef12589c 100644 --- a/object_test.go +++ b/object_test.go @@ -23,14 +23,18 @@ func TestObjectMethodCall(t *testing.T) { if val.String() != "some val" { t.Errorf("unexpected value: %q", val) } + _, err = obj.MethodCall("prop") + if err == nil { + t.Errorf("expected an error, got none") + } - val, err = ctx.RunScript(`class Obj2 { print(str) { return str.toString() } }; new Obj2()`, "") + val, err = ctx.RunScript(`class Obj2 { print(str) { return str.toString() }; get fails() { throw "error" } }; new Obj2()`, "") failIf(t, err) obj, _ = val.AsObject() arg, _ := v8go.NewValue(iso, "arg") _, err = obj.MethodCall("print", arg) failIf(t, err) - _, err = obj.MethodCall("notamethod") + _, err = obj.MethodCall("fails") if err == nil { t.Errorf("expected an error, got none") } From e7467fd9f1ba50b14a273f7999957dfb72fa06f4 Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Fri, 3 Sep 2021 13:43:08 -0700 Subject: [PATCH 6/9] Skip recv null check (#163), skip local cast on recv --- v8go.cc | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/v8go.cc b/v8go.cc index a16d4196..c239d3e7 100644 --- a/v8go.cc +++ b/v8go.cc @@ -1224,16 +1224,10 @@ RtnValue FunctionCall(ValuePtr ptr, ValuePtr recv, int argc, ValuePtr args[]) { Local argv[argc]; buildCallArguments(iso, argv, argc, args); - MaybeLocal result; + m_value* recv_val = static_cast(recv); + Local local_recv = recv_val->ptr.Get(iso); - if (recv == NULL) { - result = fn->Call(local_ctx, Undefined(iso), argc, argv); - } else { - m_value* objVal = static_cast(recv); - Local val = objVal->ptr.Get(iso); - Local obj = val.As(); - result = fn->Call(local_ctx, obj, argc, argv); - } + MaybeLocal result = fn->Call(local_ctx, local_recv, argc, argv); if (result.IsEmpty()) { rtn.error = ExceptionError(try_catch, iso, local_ctx); return rtn; From 63edea4477b8b70be6114ad9fe49f05a31c62d7b Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Fri, 3 Sep 2021 13:54:19 -0700 Subject: [PATCH 7/9] Use #163 undefined --- function.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/function.go b/function.go index a041f659..bbe96b06 100644 --- a/function.go +++ b/function.go @@ -26,7 +26,7 @@ func (fn *Function) Call(args ...Valuer) (*Value, error) { argptr = (*C.ValuePtr)(unsafe.Pointer(&cArgs[0])) } fn.ctx.register() - rtn := C.FunctionCall(fn.ptr, nil, C.int(len(args)), argptr) + rtn := C.FunctionCall(fn.ptr, fn.ctx.iso.undefined.ptr, C.int(len(args)), argptr) fn.ctx.deregister() return getValue(fn.ctx, rtn), getError(rtn) } From 50663bd799e1dd8ab2ff43a303008aa03d988fa9 Mon Sep 17 00:00:00 2001 From: Genevieve L'Esperance Date: Mon, 6 Sep 2021 10:46:46 -0700 Subject: [PATCH 8/9] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fcc46ff..595c62ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Access "this" from function callback - value.SameValue(otherValue) function to compare values for sameness - Undefined, Null functions to get these constant values for the isolate +- Support for calling a method on an object. ### Changed - Removed error return value from Context.Isolate() which never fails From aa2a8f57d67d7849a7de40df8755bcf0a918e4a4 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Wed, 8 Sep 2021 10:34:22 -0400 Subject: [PATCH 9/9] Test the return value for method call with argument --- object_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/object_test.go b/object_test.go index ef12589c..607b0f73 100644 --- a/object_test.go +++ b/object_test.go @@ -32,8 +32,11 @@ func TestObjectMethodCall(t *testing.T) { failIf(t, err) obj, _ = val.AsObject() arg, _ := v8go.NewValue(iso, "arg") - _, err = obj.MethodCall("print", arg) + val, err = obj.MethodCall("print", arg) failIf(t, err) + if val.String() != "arg" { + t.Errorf("unexpected value: %q", val) + } _, err = obj.MethodCall("fails") if err == nil { t.Errorf("expected an error, got none")