diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ba1668..1a366da1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.10.0] - 2023-04-10 + +### Changed +- Required Go version changed to 1.17 (needed for SharedArrayBuffer support) + +### Added +- Support for getting the underlying data (as a `[]byte`) from a SharedArrayBuffer + +### Fixed +- Upgrade to V8 11.1.277.13 + + ## [v0.9.0] - 2023-03-30 ### Fixed diff --git a/function_template.go b/function_template.go index 66f1b8ce..42ff0058 100644 --- a/function_template.go +++ b/function_template.go @@ -83,6 +83,7 @@ func (tmpl *FunctionTemplate) GetFunction(ctx *Context) *Function { // Note that ideally `thisAndArgs` would be split into two separate arguments, but they were combined // to workaround an ERROR_COMMITMENT_LIMIT error on windows that was detected in CI. +// //export goFunctionCallback func goFunctionCallback(ctxref int, cbref int, thisAndArgs *C.ValuePtr, argsCount int) C.ValuePtr { ctx := getContext(ctxref) diff --git a/go.mod b/go.mod index 0bacfa29..4a8709c8 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module rogchap.com/v8go -go 1.16 +go 1.17 diff --git a/v8go.cc b/v8go.cc index 86cdc874..836bf204 100644 --- a/v8go.cc +++ b/v8go.cc @@ -1670,4 +1670,43 @@ const char* Version() { void SetFlags(const char* flags) { V8::SetFlagsFromString(flags); } + +/********** SharedArrayBuffer & BackingStore ***********/ + +struct v8BackingStore { + v8BackingStore(std::shared_ptr&& ptr) + : backing_store{ptr} {} + std::shared_ptr backing_store; +}; + +BackingStorePtr SharedArrayBufferGetBackingStore(ValuePtr ptr) { + LOCAL_VALUE(ptr); + auto buffer = Local::Cast(value); + auto backing_store = buffer->GetBackingStore(); + auto proxy = new v8BackingStore(std::move(backing_store)); + return proxy; +} + +void BackingStoreRelease(BackingStorePtr ptr) { + if (ptr == nullptr) { + return; + } + ptr->backing_store.reset(); + delete ptr; +} + +void* BackingStoreData(BackingStorePtr ptr) { + if (ptr == nullptr) { + return nullptr; + } + + return ptr->backing_store->Data(); +} + +size_t BackingStoreByteLength(BackingStorePtr ptr) { + if (ptr == nullptr) { + return 0; + } + return ptr->backing_store->ByteLength(); +} } diff --git a/v8go.h b/v8go.h index b20daca4..57e20cfa 100644 --- a/v8go.h +++ b/v8go.h @@ -35,6 +35,10 @@ typedef struct v8ScriptCompilerCachedData v8ScriptCompilerCachedData; typedef const v8ScriptCompilerCachedData* ScriptCompilerCachedDataPtr; #endif +// Opaque to both C and C++ +typedef struct v8BackingStore v8BackingStore; +typedef v8BackingStore* BackingStorePtr; + #include #include @@ -307,6 +311,11 @@ ValuePtr FunctionSourceMapUrl(ValuePtr ptr); const char* Version(); extern void SetFlags(const char* flags); +extern BackingStorePtr SharedArrayBufferGetBackingStore(ValuePtr ptr); +extern void BackingStoreRelease(BackingStorePtr ptr); +extern void* BackingStoreData(BackingStorePtr ptr); +extern size_t BackingStoreByteLength(BackingStorePtr ptr); + #ifdef __cplusplus } // extern "C" #endif diff --git a/value.go b/value.go index 61d4deec..7ded7f20 100644 --- a/value.go +++ b/value.go @@ -54,13 +54,14 @@ func Null(iso *Isolate) *Value { } // NewValue will create a primitive value. Supported values types to create are: -// string -> V8::String -// int32 -> V8::Integer -// uint32 -> V8::Integer -// int64 -> V8::BigInt -// uint64 -> V8::BigInt -// bool -> V8::Boolean -// *big.Int -> V8::BigInt +// +// string -> V8::String +// int32 -> V8::Integer +// uint32 -> V8::Integer +// int64 -> V8::BigInt +// uint64 -> V8::BigInt +// bool -> V8::Boolean +// *big.Int -> V8::BigInt func NewValue(iso *Isolate, val interface{}) (*Value, error) { if iso == nil { return nil, errors.New("v8go: failed to create new Value: Isolate cannot be ") @@ -580,3 +581,20 @@ func (v *Value) MarshalJSON() ([]byte, error) { } return []byte(jsonStr), nil } + +func (v *Value) SharedArrayBufferGetContents() ([]byte, func(), error) { + if !v.IsSharedArrayBuffer() { + return nil, nil, errors.New("v8go: value is not a SharedArrayBuffer") + } + + backingStore := C.SharedArrayBufferGetBackingStore(v.ptr) + release := func() { + C.BackingStoreRelease(backingStore) + } + + byte_ptr := (*byte)(unsafe.Pointer(C.BackingStoreData(backingStore))) + byte_size := C.BackingStoreByteLength(backingStore) + byte_slice := unsafe.Slice(byte_ptr, byte_size) + + return byte_slice, release, nil +} diff --git a/value_test.go b/value_test.go index d386a589..5bd439d7 100644 --- a/value_test.go +++ b/value_test.go @@ -711,3 +711,62 @@ func TestValueMarshalJSON(t *testing.T) { }) } } + +func TestValueArrayBufferContents(t *testing.T) { + t.Parallel() + iso := v8.NewIsolate() + defer iso.Dispose() + + ctx := v8.NewContext(iso) + defer ctx.Close() + + val, err := ctx.RunScript(` + (()=>{ + let buf = new SharedArrayBuffer(1024); + let arr = new Int8Array(buf); + arr[0] = 42; + arr[1] = 52; + return buf; + })(); + `, "test.js") + + if err != nil { + t.Fatalf("failed to run script: %v", err) + } + + if !val.IsSharedArrayBuffer() { + t.Fatalf("expected SharedArrayBuffer value") + } + + buf, cleanup, err := val.SharedArrayBufferGetContents() + if err != nil { + t.Fatalf("error getting array buffer contents: %#v", err) + } + defer cleanup() + + if len(buf) != 1024 { + t.Fatalf("expected len(buf) to be 1024") + } + + if buf[0] != 42 { + t.Fatalf("expected buf[0] to be 42") + } + + if buf[1] != 52 { + t.Fatalf("expected buf[1] to be 52") + } + + if buf[3] != 0 { + t.Fatalf("expected buf[1] to be 0") + } + + // ensure there's an error if we call the method on something that isn't a SharedArrayBuffer + val, err = ctx.RunScript("7", "test2.js") + if err != nil { + t.Fatalf("error running trivial script") + } + _, _, err = val.SharedArrayBufferGetContents() + if err == nil { + t.Fatalf("Expected an error trying call SharedArrayBufferGetContents on value of incorrect type") + } +}