diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1b80bd4..2bc26fcb5 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 +- NewStringFromBytes to support the creation of [binary strings](https://developer.mozilla.org/en-US/docs/Web/API/DOMString/Binary) from Go byte array. + ### 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/go.mod b/go.mod index 0bacfa294..4f34d027e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module rogchap.com/v8go -go 1.16 +go 1.16 \ No newline at end of file diff --git a/v8go.cc b/v8go.cc index 97390883e..4b24f0a64 100644 --- a/v8go.cc +++ b/v8go.cc @@ -808,6 +808,23 @@ ValuePtr NewValueIntegerFromUnsigned(IsolatePtr iso, uint32_t v) { return tracked_value(ctx, val); } +RtnValue NewStringFromBytes(IsolatePtr iso, const uint8_t* v, int len){ + ISOLATE_SCOPE_INTERNAL_CONTEXT(iso); + TryCatch try_catch(iso); + RtnValue rtn = {}; + Local str; + if (!String::NewFromOneByte(iso, v, NewStringType::kNormal, len).ToLocal(&str)) { + rtn.error = ExceptionError(try_catch, iso, ctx->ptr.Get(iso)); + return rtn; + } + m_value* val = new m_value; + val->iso = iso; + val->ctx = ctx; + val->ptr = Persistent>(iso, str); + rtn.value = tracked_value(ctx, val); + return rtn; +} + RtnValue NewValueString(IsolatePtr iso, const char* v, int v_length) { ISOLATE_SCOPE_INTERNAL_CONTEXT(iso); TryCatch try_catch(iso); @@ -962,6 +979,7 @@ RtnString ValueToString(ValuePtr ptr) { // TODO: Consider propagating the JS error. A fallback value could be returned // in Value.String() String::Utf8Value src(iso, value); + printf("___ valuetostring %d \n", src.length()); char* data = static_cast(malloc(src.length())); memcpy(data, *src, src.length()); rtn.data = data; diff --git a/v8go.h b/v8go.h index 7acaf0425..fb500f7fb 100644 --- a/v8go.h +++ b/v8go.h @@ -195,6 +195,7 @@ extern ValuePtr NewValueUndefined(IsolatePtr iso_ptr); extern ValuePtr NewValueInteger(IsolatePtr iso_ptr, int32_t v); extern ValuePtr NewValueIntegerFromUnsigned(IsolatePtr iso_ptr, uint32_t v); extern RtnValue NewValueString(IsolatePtr iso_ptr, const char* v, int v_length); +extern RtnValue NewStringFromBytes(IsolatePtr iso, const uint8_t* v, int len); extern ValuePtr NewValueBoolean(IsolatePtr iso_ptr, int v); extern ValuePtr NewValueNumber(IsolatePtr iso_ptr, double v); extern ValuePtr NewValueBigInt(IsolatePtr iso_ptr, int64_t v); diff --git a/value.go b/value.go index c4ba9acf0..bcd07595b 100644 --- a/value.go +++ b/value.go @@ -53,6 +53,16 @@ func Null(iso *Isolate) *Value { return iso.null } +// NewStringFromBytes returns V8::string from byte array. Creates binary string from bytes. +func NewStringFromBytes(iso *Isolate, bytes []byte) (*Value, error) { + if iso == nil { + panic(errors.New("v8go: failed to create new Value: Isolate cannot be ")) + } + cUint := (*C.uchar)(unsafe.Pointer(&bytes[0])) + rtnVal := C.NewStringFromBytes(iso.ptr, cUint, C.int(len(bytes))) + return valueResult(nil, rtnVal) +} + // NewValue will create a primitive value. Supported values types to create are: // string -> V8::String // int32 -> V8::Integer diff --git a/value_test.go b/value_test.go index d386a5893..9ed222aab 100644 --- a/value_test.go +++ b/value_test.go @@ -519,6 +519,63 @@ func TestValueFunction(t *testing.T) { } +func TestNewStringFromBytes(t *testing.T) { + t.Parallel() + iso := v8.NewIsolate() + defer iso.Dispose() + global := v8.NewObjectTemplate(iso) + + if recoverPanic(func() { v8.NewStringFromBytes(nil, []byte{}) }) == nil { + t.Error("expected panic") + } + + testFn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value { + args := info.Args() + input := args[0].String() + if len(input) == 0 { + //String() terminates input at null character. hence hardcoding for 3rd input + input = "a\x00\xffe" + } + val, _ := v8.NewStringFromBytes(iso, []byte(input)) + return val + }) + global.Set("foo", testFn, v8.ReadOnly) + + ctx := v8.NewContext(iso, global) + defer ctx.Close() + tests := [...]struct { + name string + script string + }{ + {"normal string", + ` let res = foo("str"); + if(res.length != 3){ + throw Error("expected length 3") + } + `}, + {"multi-byte sequence", + ` res = foo("Ò"); + if(res.length != 2 ){ + throw Error("expected length 2") + } + `}, + {"null terminated sequence", + ` res = foo(""); + if(res.length != 4){ + throw Error("expected length 4") + } + `}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if _, err := ctx.RunScript(tt.script, ""); err != nil { + t.Errorf("expected error, but got error: %v", err) + } + }) + } + +} + func TestValueSameValue(t *testing.T) { t.Parallel() iso := v8.NewIsolate()