From 7b267832e180af24600a269edec60a188dca3b4d Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Thu, 13 Jan 2022 15:32:33 -0500 Subject: [PATCH 1/2] Add String type, constructor functions for it and Value.AsString to cast to it --- CHANGELOG.md | 3 +++ string.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ value.go | 13 +++++++++---- 3 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 string.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1b80bd4..0c1d1015b 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 +- Add String type, constructor functions for it and Value.AsString() to cast to it + ### Fixed - Use string length to ensure null character-containing strings in Go/JS are not terminated early. - Object.Set with an empty key string is now supported diff --git a/string.go b/string.go new file mode 100644 index 000000000..dd8bc3b71 --- /dev/null +++ b/string.go @@ -0,0 +1,45 @@ +// Copyright 2022 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 + +// #include +// #include "v8go.h" +import "C" +import ( + "unsafe" +) + +// String is a JavaScript string object (ECMA-262, 4.3.17) +type String struct { + *Value +} + +func jsStringResult(ctx *Context, rtn C.RtnValue) (String, error) { + if rtn.value == nil { + return String{nil}, newJSError(rtn.error) + } + return String{&Value{rtn.value, ctx}}, nil +} + +// NewString returns a JS string from the Go string or an error if the string is longer than the max V8 string length. +func NewString(iso *Isolate, val string) (String, error) { + cstr := C.CString(val) + defer C.free(unsafe.Pointer(cstr)) + rtn := C.NewValueString(iso.ptr, cstr, C.int(len(val))) + return jsStringResult(nil, rtn) +} + +// MustNewString wraps NewString with a panic on error check. +// +// Use for strings known to be within than the max V8 string length. +// V8's max string length is (1 << 28) - 16 on 32-bit systems +// and (1 << 29) - 24 on other systems, at the time of writing. +func MustNewString(iso *Isolate, val string) String { + jsStr, err := NewString(iso, val) + if err != nil { + panic(err) + } + return jsStr +} diff --git a/value.go b/value.go index c4ba9acf0..cfb8af9da 100644 --- a/value.go +++ b/value.go @@ -70,10 +70,8 @@ func NewValue(iso *Isolate, val interface{}) (*Value, error) { switch v := val.(type) { case string: - cstr := C.CString(v) - defer C.free(unsafe.Pointer(cstr)) - rtn := C.NewValueString(iso.ptr, cstr, C.int(len(v))) - return valueResult(nil, rtn) + str, err := NewString(iso, v) + return str.Value, err case int32: rtnVal = &Value{ ptr: C.NewValueInteger(iso.ptr, C.int(v)), @@ -567,6 +565,13 @@ func (v *Value) AsFunction() (*Function, error) { return &Function{v}, nil } +func (v *Value) AsString() (*String, error) { + if !v.IsFunction() { + return nil, errors.New("v8go: value is not a String") + } + return &String{v}, nil +} + // MarshalJSON implements the json.Marshaler interface. func (v *Value) MarshalJSON() ([]byte, error) { jsonStr, err := JSONStringify(nil, v) From c6b856e3150db2349c78e34f579a60e0a5d85334 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Thu, 13 Jan 2022 15:33:05 -0500 Subject: [PATCH 2/2] Use MustNewString in tests to avoid missing or verbose error checking --- context_test.go | 4 +--- function_template_fetch_test.go | 2 +- function_template_test.go | 4 ++-- function_test.go | 3 +-- isolate_test.go | 2 +- object_template_test.go | 2 +- object_test.go | 2 +- promise_test.go | 6 +++--- 8 files changed, 11 insertions(+), 14 deletions(-) diff --git a/context_test.go b/context_test.go index 10dad5d8e..fe7807c81 100644 --- a/context_test.go +++ b/context_test.go @@ -123,9 +123,7 @@ func TestRegistryFromJSON(t *testing.T) { global := v8.NewObjectTemplate(iso) err := global.Set("location", v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { - v, err := v8.NewValue(iso, "world") - fatalIf(t, err) - return v + return v8.MustNewString(iso, "world").Value })) fatalIf(t, err) diff --git a/function_template_fetch_test.go b/function_template_fetch_test.go index 601da4d71..a88e5f3da 100644 --- a/function_template_fetch_test.go +++ b/function_template_fetch_test.go @@ -33,7 +33,7 @@ func ExampleFunctionTemplate_fetch() { go func() { res, _ := http.Get(url) body, _ := ioutil.ReadAll(res.Body) - val, _ := v8.NewValue(iso, string(body)) + val := v8.MustNewString(iso, string(body)) resolver.Resolve(val) }() return resolver.GetPromise().Value diff --git a/function_template_test.go b/function_template_test.go index de2dd382d..c2ab32924 100644 --- a/function_template_test.go +++ b/function_template_test.go @@ -60,8 +60,8 @@ func TestFunctionTemplateGetFunction(t *testing.T) { var args *v8.FunctionCallbackInfo tmpl := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { args = info - reply, _ := v8.NewValue(iso, "hello") - return reply + reply := v8.MustNewString(iso, "hello") + return reply.Value }) fn := tmpl.GetFunction(ctx) ten, err := v8.NewValue(iso, int32(10)) diff --git a/function_test.go b/function_test.go index 8b8e65f6f..6abb2c24c 100644 --- a/function_test.go +++ b/function_test.go @@ -162,8 +162,7 @@ func TestFunctionNewInstance(t *testing.T) { fatalIf(t, err) fn, err := value.AsFunction() fatalIf(t, err) - messageObj, err := v8.NewValue(iso, "test message") - fatalIf(t, err) + messageObj := v8.MustNewString(iso, "test message") errObj, err := fn.NewInstance(messageObj) fatalIf(t, err) diff --git a/isolate_test.go b/isolate_test.go index a6cca8e53..927839ee9 100644 --- a/isolate_test.go +++ b/isolate_test.go @@ -203,7 +203,7 @@ func TestIsolateThrowException(t *testing.T) { t.Parallel() iso := v8.NewIsolate() - strErr, _ := v8.NewValue(iso, "some type error") + strErr := v8.MustNewString(iso, "some type error").Value throwError := func(val *v8.Value) { v := iso.ThrowException(val) diff --git a/object_template_test.go b/object_template_test.go index 76ca2f01b..1ffb8a55b 100644 --- a/object_template_test.go +++ b/object_template_test.go @@ -24,7 +24,7 @@ func TestObjectTemplate(t *testing.T) { } } - val, _ := v8.NewValue(iso, "bar") + val := v8.MustNewString(iso, "bar").Value objVal := v8.NewObjectTemplate(iso) bigbigint, _ := new(big.Int).SetString("36893488147419099136", 10) // larger than a single word size (64bit) bigbignegint, _ := new(big.Int).SetString("-36893488147419099136", 10) diff --git a/object_test.go b/object_test.go index b40d1b0db..0c22eca2b 100644 --- a/object_test.go +++ b/object_test.go @@ -33,7 +33,7 @@ func TestObjectMethodCall(t *testing.T) { val, err = ctx.RunScript(`class Obj2 { print(str) { return str.toString() }; get fails() { throw "error" } }; new Obj2()`, "") fatalIf(t, err) obj, _ = val.AsObject() - arg, _ := v8.NewValue(iso, "arg") + arg := v8.MustNewString(iso, "arg") val, err = obj.MethodCall("print", arg) fatalIf(t, err) if val.String() != "arg" { diff --git a/promise_test.go b/promise_test.go index 0ecf467d2..622a4a621 100644 --- a/promise_test.go +++ b/promise_test.go @@ -41,7 +41,7 @@ func TestPromiseFulfilled(t *testing.T) { t.Error("unexpected call of Then prior to resolving the promise") } - val1, _ := v8.NewValue(iso, "foo") + val1 := v8.MustNewString(iso, "foo") res1.Resolve(val1) if s := prom1.State(); s != v8.Fulfilled { @@ -69,8 +69,8 @@ func TestPromiseRejected(t *testing.T) { defer ctx.Close() res2, _ := v8.NewPromiseResolver(ctx) - val2, _ := v8.NewValue(iso, "Bad Foo") - res2.Reject(val2) + val2 := v8.MustNewString(iso, "Bad Foo") + res2.Reject(val2.Value) prom2 := res2.GetPromise() if s := prom2.State(); s != v8.Rejected {