From 6b4e736dfcc1ccb5f789a64950492cb5376a450c Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Fri, 10 Dec 2021 10:40:13 -0800 Subject: [PATCH 01/12] Add SnapshotCreator API Co-authored-by: Genevieve --- context.go | 22 +++++ context_test.go | 16 ++++ isolate.go | 37 +++++++- snapshot_creator.go | 104 +++++++++++++++++++++ snapshot_creator_test.go | 191 +++++++++++++++++++++++++++++++++++++++ v8go.cc | 104 ++++++++++++++++++++- v8go.h | 33 ++++++- 7 files changed, 500 insertions(+), 7 deletions(-) create mode 100644 snapshot_creator.go create mode 100644 snapshot_creator_test.go diff --git a/context.go b/context.go index 718757068..2fddc24d4 100644 --- a/context.go +++ b/context.go @@ -8,6 +8,7 @@ package v8go // #include "v8go.h" import "C" import ( + "errors" "runtime" "sync" "unsafe" @@ -76,6 +77,27 @@ func NewContext(opt ...ContextOption) *Context { return ctx } +func NewContextFromSnapshot(iso *Isolate, snapshot_index int) (*Context, error) { + ctxMutex.Lock() + ctxSeq++ + ref := ctxSeq + ctxMutex.Unlock() + + createParams := iso.createParams + if createParams == nil || createParams.startupData == nil { + return nil, errors.New("Must create an isolate from a snapshot blob") + } + + ctx := &Context{ + ref: ref, + ptr: C.NewContextFromSnapshot(iso.ptr, C.size_t(snapshot_index), C.int(ref)), + iso: iso, + } + + ctx.register() + return ctx, nil +} + // Isolate gets the current context's parent isolate.An error is returned // if the isolate has been terninated. func (c *Context) Isolate() *Isolate { diff --git a/context_test.go b/context_test.go index 10dad5d8e..9eb9d046d 100644 --- a/context_test.go +++ b/context_test.go @@ -39,6 +39,22 @@ func TestContextExec(t *testing.T) { } } +func TestNewContextFromSnapshotErrorWhenIsolateHasNoStartupData(t *testing.T) { + t.Parallel() + + iso := v8.NewIsolate() + defer iso.Dispose() + + ctx, err := v8.NewContextFromSnapshot(iso, 1) + + if ctx != nil { + t.Errorf("error expected nil context got: %+v", ctx) + } + if err == nil { + t.Error("error expected but was ") + } +} + func TestJSExceptions(t *testing.T) { t.Parallel() diff --git a/isolate.go b/isolate.go index 661fbec05..fb8861bb6 100644 --- a/isolate.go +++ b/isolate.go @@ -25,8 +25,9 @@ type Isolate struct { cbSeq int cbs map[int]FunctionCallback - null *Value - undefined *Value + null *Value + undefined *Value + createParams *CreateParams } // HeapStatistics represents V8 isolate heap statistics @@ -44,6 +45,18 @@ type HeapStatistics struct { NumberOfDetachedContexts uint64 } +type createOptions func(*CreateParams) + +func WithStartupData(startupData *StartupData) createOptions { + return func(params *CreateParams) { + params.startupData = startupData + } +} + +type CreateParams struct { + startupData *StartupData +} + // NewIsolate creates a new V8 isolate. Only one thread may access // a given isolate at a time, but different threads may access // different isolates simultaneously. @@ -51,13 +64,26 @@ type HeapStatistics struct { // by calling iso.Dispose(). // An *Isolate can be used as a v8go.ContextOption to create a new // Context, rather than creating a new default Isolate. -func NewIsolate() *Isolate { +func NewIsolate(opts ...createOptions) *Isolate { v8once.Do(func() { C.Init() }) + params := &CreateParams{} + for _, opt := range opts { + opt(params) + } + + var cOptions C.IsolateOptions + + if params.startupData != nil { + cOptions.snapshot_blob_data = (*C.char)(unsafe.Pointer(¶ms.startupData.data[0])) + cOptions.snapshot_blob_raw_size = params.startupData.raw_size + } + iso := &Isolate{ - ptr: C.NewIsolate(), - cbs: make(map[int]FunctionCallback), + ptr: C.NewIsolate(cOptions), + cbs: make(map[int]FunctionCallback), + createParams: params, } iso.null = newValueNull(iso) iso.undefined = newValueUndefined(iso) @@ -146,6 +172,7 @@ func (i *Isolate) Dispose() { return } C.IsolateDispose(i.ptr) + i.createParams = nil i.ptr = nil } diff --git a/snapshot_creator.go b/snapshot_creator.go new file mode 100644 index 000000000..46ff59817 --- /dev/null +++ b/snapshot_creator.go @@ -0,0 +1,104 @@ +// Copyright 2021 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 ( + "errors" + "unsafe" +) + +type FunctionCodeHandling int + +const ( + FunctionCodeHandlingKlear FunctionCodeHandling = iota + FunctionCodeHandlingKeep +) + +type StartupData struct { + data []byte + raw_size C.int +} + +type SnapshotCreator struct { + ptr C.SnapshotCreatorPtr + iso *Isolate + defaultContextAdded bool +} + +func NewSnapshotCreator() *SnapshotCreator { + v8once.Do(func() { + C.Init() + }) + + rtn := C.NewSnapshotCreator() + + return &SnapshotCreator{ + ptr: rtn.creator, + iso: &Isolate{ptr: rtn.iso}, + defaultContextAdded: false, + } +} + +func (s *SnapshotCreator) GetIsolate() (*Isolate, error) { + if s.ptr == nil { + return nil, errors.New("v8go: Cannot get Isolate after creating the blob") + } + + return s.iso, nil +} + +func (s *SnapshotCreator) SetDeafultContext(ctx *Context) error { + if s.defaultContextAdded { + return errors.New("v8go: Cannot set multiple default context for snapshot creator") + } + + C.SetDefaultContext(s.ptr, ctx.ptr) + s.defaultContextAdded = true + ctx.ptr = nil + + return nil +} + +func (s *SnapshotCreator) AddContext(ctx *Context) (int, error) { + if s.ptr == nil { + return 0, errors.New("v8go: Cannot add context to snapshot creator after creating the blob") + } + + index := C.AddContext(s.ptr, ctx.ptr) + ctx.ptr = nil + + return int(index), nil +} + +func (s *SnapshotCreator) Create(functionCode FunctionCodeHandling) (*StartupData, error) { + if s.ptr == nil { + return nil, errors.New("v8go: Cannot use snapshot creator after creating the blob") + } + + if !s.defaultContextAdded { + return nil, errors.New("v8go: Cannot create a snapshot without a default context") + } + + rtn := C.CreateBlob(s.ptr, C.int(functionCode)) + + s.ptr = nil + s.iso.ptr = nil + + raw_size := rtn.raw_size + data := C.GoBytes(unsafe.Pointer(rtn.data), raw_size) + + C.SnapshotBlobDelete(rtn) + + return &StartupData{data: data, raw_size: raw_size}, nil +} + +func (s *SnapshotCreator) Dispose() { + if s.ptr != nil { + C.DeleteSnapshotCreator(s.ptr) + } +} diff --git a/snapshot_creator_test.go b/snapshot_creator_test.go new file mode 100644 index 000000000..fdf5cec16 --- /dev/null +++ b/snapshot_creator_test.go @@ -0,0 +1,191 @@ +// Copyright 2021 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_test + +import ( + "testing" + + v8 "rogchap.com/v8go" +) + +func TestCreateSnapshot(t *testing.T) { + snapshotCreator := v8.NewSnapshotCreator() + snapshotCreatorIso, err := snapshotCreator.GetIsolate() + fatalIf(t, err) + + snapshotCreatorCtx := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx.Close() + + snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") + snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") + err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + fatalIf(t, err) + + data, err := snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + fatalIf(t, err) + + iso := v8.NewIsolate(v8.WithStartupData(data)) + defer iso.Dispose() + + ctx := v8.NewContext(iso) + defer ctx.Close() + + runVal, err := ctx.Global().Get("run") + if err != nil { + panic(err) + } + + fn, err := runVal.AsFunction() + if err != nil { + panic(err) + } + val, err := fn.Call(v8.Undefined(iso)) + if err != nil { + panic(err) + } + if val.String() != "7" { + t.Fatal("invalid val") + } +} + +func TestCreateSnapshotAndAddExtraContext(t *testing.T) { + snapshotCreator := v8.NewSnapshotCreator() + snapshotCreatorIso, err := snapshotCreator.GetIsolate() + fatalIf(t, err) + + snapshotCreatorCtx := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx.Close() + + snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") + snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") + err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + fatalIf(t, err) + + snapshotCreatorCtx2 := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx2.Close() + + snapshotCreatorCtx2.RunScript(`const multiply = (a, b) => a * b`, "add.js") + snapshotCreatorCtx2.RunScript(`function run() { return multiply(3, 4); }`, "main.js") + index, err := snapshotCreator.AddContext(snapshotCreatorCtx2) + fatalIf(t, err) + + snapshotCreatorCtx3 := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx3.Close() + + snapshotCreatorCtx3.RunScript(`const div = (a, b) => a / b`, "add.js") + snapshotCreatorCtx3.RunScript(`function run() { return div(6, 2); }`, "main.js") + index2, err := snapshotCreator.AddContext(snapshotCreatorCtx3) + fatalIf(t, err) + + data, err := snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + fatalIf(t, err) + + iso := v8.NewIsolate(v8.WithStartupData(data)) + defer iso.Dispose() + + ctx, err := v8.NewContextFromSnapshot(iso, index) + fatalIf(t, err) + defer ctx.Close() + + runVal, err := ctx.Global().Get("run") + if err != nil { + panic(err) + } + + fn, err := runVal.AsFunction() + if err != nil { + panic(err) + } + val, err := fn.Call(v8.Undefined(iso)) + if err != nil { + panic(err) + } + if val.String() != "12" { + t.Fatal("invalid val") + } + + ctx, err = v8.NewContextFromSnapshot(iso, index2) + fatalIf(t, err) + defer ctx.Close() + + runVal, err = ctx.Global().Get("run") + if err != nil { + panic(err) + } + + fn, err = runVal.AsFunction() + if err != nil { + panic(err) + } + val, err = fn.Call(v8.Undefined(iso)) + if err != nil { + panic(err) + } + if val.String() != "3" { + t.Fatal("invalid val") + } +} + +func TestCreateSnapshotErrorAfterAddingMultipleDefaultContext(t *testing.T) { + snapshotCreator := v8.NewSnapshotCreator() + defer snapshotCreator.Dispose() + snapshotCreatorIso, err := snapshotCreator.GetIsolate() + fatalIf(t, err) + snapshotCreatorCtx := v8.NewContext(snapshotCreatorIso) + + snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") + snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") + err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + fatalIf(t, err) + + err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + defer snapshotCreatorCtx.Close() + + if err == nil { + t.Error("Adding an extra default cointext show have fail") + } +} + +func TestCreateSnapshotErrorAfterSuccessfullCreate(t *testing.T) { + snapshotCreator := v8.NewSnapshotCreator() + snapshotCreatorIso, err := snapshotCreator.GetIsolate() + fatalIf(t, err) + snapshotCreatorCtx := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx.Close() + + snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") + snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") + err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + fatalIf(t, err) + + _, err = snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + fatalIf(t, err) + + _, err = snapshotCreator.GetIsolate() + if err == nil { + t.Error("Getting Isolate should have fail") + } + + _, err = snapshotCreator.AddContext(snapshotCreatorCtx) + if err == nil { + t.Error("Adding context should have fail") + } + + _, err = snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + if err == nil { + t.Error("Creating snapshot should have fail") + } +} + +func TestCreateSnapshotErrorIfNodefaultContextIsAdded(t *testing.T) { + snapshotCreator := v8.NewSnapshotCreator() + defer snapshotCreator.Dispose() + + _, err := snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + + if err == nil { + t.Error("Creating a snapshop should have fail") + } +} diff --git a/v8go.cc b/v8go.cc index 97390883e..70c540d21 100644 --- a/v8go.cc +++ b/v8go.cc @@ -26,6 +26,7 @@ const int ScriptCompilerEagerCompile = ScriptCompiler::kEagerCompile; struct m_ctx { Isolate* iso; + StartupData* startup_data; std::vector vals; std::vector unboundScripts; Persistent ptr; @@ -152,9 +153,19 @@ void Init() { return; } -IsolatePtr NewIsolate() { +IsolatePtr NewIsolate(IsolateOptions options) { Isolate::CreateParams params; params.array_buffer_allocator = default_allocator; + + StartupData* startup_data; + if (options.snapshot_blob_data != nullptr) { + startup_data = new StartupData{options.snapshot_blob_data, + options.snapshot_blob_raw_size}; + params.snapshot_blob = startup_data; + } else { + startup_data = nullptr; + } + Isolate* iso = Isolate::New(params); Locker locker(iso); Isolate::Scope isolate_scope(iso); @@ -166,6 +177,7 @@ IsolatePtr NewIsolate() { m_ctx* ctx = new m_ctx; ctx->ptr.Reset(iso, Context::New(iso)); ctx->iso = iso; + ctx->startup_data = startup_data; iso->SetData(0, ctx); return iso; @@ -264,6 +276,67 @@ RtnUnboundScript IsolateCompileUnboundScript(IsolatePtr iso, return rtn; } +/********** SnapshotCreator **********/ + +RtnSnapshotCreator NewSnapshotCreator() { + RtnSnapshotCreator rtn = {}; + SnapshotCreator* creator = new SnapshotCreator; + Isolate* iso = creator->GetIsolate(); + rtn.creator = creator; + rtn.iso = iso; + + return rtn; +} + +void DeleteSnapshotCreator(SnapshotCreatorPtr snapshotCreator) { + delete snapshotCreator; +} + +void SetDefaultContext(SnapshotCreatorPtr snapshotCreator, ContextPtr ctx) { + Isolate* iso = ctx->iso; + Locker locker(iso); + Isolate::Scope isolate_scope(iso); + HandleScope handle_scope(iso); + Local local_ctx = ctx->ptr.Get(iso); + Context::Scope context_scope(local_ctx); + + ContextFree(ctx); + + snapshotCreator->SetDefaultContext(local_ctx); +} + +size_t AddContext(SnapshotCreatorPtr snapshotCreator, ContextPtr ctx) { + Isolate* iso = ctx->iso; + Locker locker(iso); + Isolate::Scope isolate_scope(iso); + HandleScope handle_scope(iso); + Local local_ctx = ctx->ptr.Get(iso); + Context::Scope context_scope(local_ctx); + + ContextFree(ctx); + + return snapshotCreator->AddContext(local_ctx); +} + +RtnSnapshotBlob* CreateBlob(SnapshotCreatorPtr snapshotCreator, + int function_code_handling) { + // kKeep - keeps any compiled functions + // kClear - does not keep any compiled functions + StartupData startup_data = snapshotCreator->CreateBlob( + SnapshotCreator::FunctionCodeHandling(function_code_handling)); + + RtnSnapshotBlob* rtn = new RtnSnapshotBlob; + rtn->data = startup_data.data; + rtn->raw_size = startup_data.raw_size; + delete snapshotCreator; + return rtn; +} + +void SnapshotBlobDelete(RtnSnapshotBlob* ptr) { + delete[] ptr->data; + delete ptr; +} + /********** Exceptions & Errors **********/ ValuePtr IsolateThrowException(IsolatePtr iso, ValuePtr value) { @@ -593,6 +666,31 @@ ContextPtr NewContext(IsolatePtr iso, m_ctx* ctx = new m_ctx; ctx->ptr.Reset(iso, local_ctx); ctx->iso = iso; + ctx->startup_data = nullptr; + return ctx; +} + +ContextPtr NewContextFromSnapshot(IsolatePtr iso, + size_t snapshot_blob_index, + int ref) { + Locker locker(iso); + Isolate::Scope isolate_scope(iso); + HandleScope handle_scope(iso); + + // For function callbacks we need a reference to the context, but because of + // the complexities of C -> Go function pointers, we store a reference to the + // context as a simple integer identifier; this can then be used on the Go + // side to lookup the context in the context registry. We use slot 1 as slot 0 + // has special meaning for the Chrome debugger. + + Local local_ctx = + Context::FromSnapshot(iso, snapshot_blob_index).ToLocalChecked(); + local_ctx->SetEmbedderData(1, Integer::New(iso, ref)); + + m_ctx* ctx = new m_ctx; + ctx->ptr.Reset(iso, local_ctx); + ctx->iso = iso; + ctx->startup_data = nullptr; return ctx; } @@ -612,6 +710,10 @@ void ContextFree(ContextPtr ctx) { delete us; } + if (ctx->startup_data) { + delete ctx->startup_data; + } + delete ctx; } diff --git a/v8go.h b/v8go.h index 7acaf0425..3c59afba1 100644 --- a/v8go.h +++ b/v8go.h @@ -15,6 +15,7 @@ typedef v8::CpuProfiler* CpuProfilerPtr; typedef v8::CpuProfile* CpuProfilePtr; typedef const v8::CpuProfileNode* CpuProfileNodePtr; typedef v8::ScriptCompiler::CachedData* ScriptCompilerCachedDataPtr; +typedef v8::SnapshotCreator* SnapshotCreatorPtr; extern "C" { #else @@ -22,6 +23,9 @@ extern "C" { typedef struct v8Isolate v8Isolate; typedef v8Isolate* IsolatePtr; +typedef struct v8SnapshotCreator v8SnapshotCreator; +typedef v8SnapshotCreator* SnapshotCreatorPtr; + typedef struct v8CpuProfiler v8CpuProfiler; typedef v8CpuProfiler* CpuProfilerPtr; @@ -72,11 +76,26 @@ typedef struct { int rejected; } ScriptCompilerCachedData; +typedef struct { + const char* data; + int raw_size; +} RtnSnapshotBlob; + +typedef struct { + SnapshotCreatorPtr creator; + IsolatePtr iso; +} RtnSnapshotCreator; + typedef struct { ScriptCompilerCachedData cachedData; int compileOption; } CompileOptions; +typedef struct { + char* snapshot_blob_data; + int snapshot_blob_raw_size; +} IsolateOptions; + typedef struct { CpuProfilerPtr ptr; IsolatePtr iso; @@ -132,13 +151,22 @@ typedef struct { } ValueBigInt; extern void Init(); -extern IsolatePtr NewIsolate(); +extern IsolatePtr NewIsolate(IsolateOptions opts); extern void IsolatePerformMicrotaskCheckpoint(IsolatePtr ptr); extern void IsolateDispose(IsolatePtr ptr); extern void IsolateTerminateExecution(IsolatePtr ptr); extern int IsolateIsExecutionTerminating(IsolatePtr ptr); extern IsolateHStatistics IsolationGetHeapStatistics(IsolatePtr ptr); +extern void SnapshotBlobDelete(RtnSnapshotBlob* ptr); +extern RtnSnapshotCreator NewSnapshotCreator(); +extern void DeleteSnapshotCreator(SnapshotCreatorPtr snapshotCreator); +extern void SetDefaultContext(SnapshotCreatorPtr snapshotCreator, + ContextPtr ctx); +extern size_t AddContext(SnapshotCreatorPtr snapshotCreator, ContextPtr ctx); +extern RtnSnapshotBlob* CreateBlob(SnapshotCreatorPtr snapshotCreator, + int function_code_handling); + extern ValuePtr IsolateThrowException(IsolatePtr iso, ValuePtr value); extern RtnUnboundScript IsolateCompileUnboundScript(IsolatePtr iso_ptr, @@ -162,6 +190,9 @@ extern void CPUProfileDelete(CPUProfile* ptr); extern ContextPtr NewContext(IsolatePtr iso_ptr, TemplatePtr global_template_ptr, int ref); +extern ContextPtr NewContextFromSnapshot(IsolatePtr iso, + size_t snapshot_blob_index, + int ref); extern void ContextFree(ContextPtr ptr); extern RtnValue RunScript(ContextPtr ctx_ptr, const char* source, From b6360e25526f5715c99aec22b1692868cacfaf32 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Wed, 26 Jan 2022 11:34:22 +0100 Subject: [PATCH 02/12] Improve error message for NewContextFromSnapshot --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 2fddc24d4..6da4da1b7 100644 --- a/context.go +++ b/context.go @@ -85,7 +85,7 @@ func NewContextFromSnapshot(iso *Isolate, snapshot_index int) (*Context, error) createParams := iso.createParams if createParams == nil || createParams.startupData == nil { - return nil, errors.New("Must create an isolate from a snapshot blob") + return nil, errors.New("v8go: The isolate must have startupData associated with it") } ctx := &Context{ From 44297f88c33e86a0ab2850b35be6cdea41246832 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Thu, 27 Jan 2022 09:59:55 +0100 Subject: [PATCH 03/12] add documentation for the new API --- context.go | 2 ++ isolate.go | 3 +++ snapshot_creator.go | 16 ++++++++++++++-- snapshot_creator_test.go | 14 +++++++------- v8go.cc | 2 -- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/context.go b/context.go index 6da4da1b7..1ca4cbd4c 100644 --- a/context.go +++ b/context.go @@ -77,6 +77,8 @@ func NewContext(opt ...ContextOption) *Context { return ctx } +// NewContextFromSnapshot creates a new JavaScript context from the Isolate startup data; +// If the Isolate has no startup data associated returns an error. func NewContextFromSnapshot(iso *Isolate, snapshot_index int) (*Context, error) { ctxMutex.Lock() ctxSeq++ diff --git a/isolate.go b/isolate.go index fb8861bb6..9a0f292a5 100644 --- a/isolate.go +++ b/isolate.go @@ -62,6 +62,9 @@ type CreateParams struct { // different isolates simultaneously. // When an isolate is no longer used its resources should be freed // by calling iso.Dispose(). +// If StartupData is passed as part of createOptions it will be use as part +// of the createParams. The StartupData will be use when creating new context +// from the Isolate. // An *Isolate can be used as a v8go.ContextOption to create a new // Context, rather than creating a new default Isolate. func NewIsolate(opts ...createOptions) *Isolate { diff --git a/snapshot_creator.go b/snapshot_creator.go index 46ff59817..872a84863 100644 --- a/snapshot_creator.go +++ b/snapshot_creator.go @@ -14,22 +14,27 @@ import ( type FunctionCodeHandling int +// Clear - does not keeps any compiled data prior to serialization/deserialization/verify pass +// Keep - keeps any compiled data prior to serialization/deserialization/verify pass const ( - FunctionCodeHandlingKlear FunctionCodeHandling = iota + FunctionCodeHandlingClear FunctionCodeHandling = iota FunctionCodeHandlingKeep ) +// StartupData stores the snapshot blob data type StartupData struct { data []byte raw_size C.int } +// SnapshotCreator allows creating snapshot. type SnapshotCreator struct { ptr C.SnapshotCreatorPtr iso *Isolate defaultContextAdded bool } +// NewSnapshotCreator creates a new snapshot creator. func NewSnapshotCreator() *SnapshotCreator { v8once.Do(func() { C.Init() @@ -44,6 +49,8 @@ func NewSnapshotCreator() *SnapshotCreator { } } +// GetIsolate returns the Isolate associated with the SnapshotCreator. +// This Isolate must be use to create the contexts that later will be use to create the snapshot blob. func (s *SnapshotCreator) GetIsolate() (*Isolate, error) { if s.ptr == nil { return nil, errors.New("v8go: Cannot get Isolate after creating the blob") @@ -52,7 +59,8 @@ func (s *SnapshotCreator) GetIsolate() (*Isolate, error) { return s.iso, nil } -func (s *SnapshotCreator) SetDeafultContext(ctx *Context) error { +// SetDefaultContext set the default context to be included in the snapshot blob. +func (s *SnapshotCreator) SetDefaultContext(ctx *Context) error { if s.defaultContextAdded { return errors.New("v8go: Cannot set multiple default context for snapshot creator") } @@ -64,6 +72,8 @@ func (s *SnapshotCreator) SetDeafultContext(ctx *Context) error { return nil } +// AddContext add additional context to be included in the snapshot blob. +// Returns the index of the context in the snapshot blob, that later can be use to call v8go.NewContextFromSnapshot. func (s *SnapshotCreator) AddContext(ctx *Context) (int, error) { if s.ptr == nil { return 0, errors.New("v8go: Cannot add context to snapshot creator after creating the blob") @@ -75,6 +85,7 @@ func (s *SnapshotCreator) AddContext(ctx *Context) (int, error) { return int(index), nil } +// Create creates a snapshot data blob. func (s *SnapshotCreator) Create(functionCode FunctionCodeHandling) (*StartupData, error) { if s.ptr == nil { return nil, errors.New("v8go: Cannot use snapshot creator after creating the blob") @@ -97,6 +108,7 @@ func (s *SnapshotCreator) Create(functionCode FunctionCodeHandling) (*StartupDat return &StartupData{data: data, raw_size: raw_size}, nil } +// Dispose deletes the reference to the SnapshotCreator. func (s *SnapshotCreator) Dispose() { if s.ptr != nil { C.DeleteSnapshotCreator(s.ptr) diff --git a/snapshot_creator_test.go b/snapshot_creator_test.go index fdf5cec16..48995aaa4 100644 --- a/snapshot_creator_test.go +++ b/snapshot_creator_test.go @@ -20,10 +20,10 @@ func TestCreateSnapshot(t *testing.T) { snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") - err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + err = snapshotCreator.SetDefaultContext(snapshotCreatorCtx) fatalIf(t, err) - data, err := snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + data, err := snapshotCreator.Create(v8.FunctionCodeHandlingClear) fatalIf(t, err) iso := v8.NewIsolate(v8.WithStartupData(data)) @@ -60,7 +60,7 @@ func TestCreateSnapshotAndAddExtraContext(t *testing.T) { snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") - err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + err = snapshotCreator.SetDefaultContext(snapshotCreatorCtx) fatalIf(t, err) snapshotCreatorCtx2 := v8.NewContext(snapshotCreatorIso) @@ -79,7 +79,7 @@ func TestCreateSnapshotAndAddExtraContext(t *testing.T) { index2, err := snapshotCreator.AddContext(snapshotCreatorCtx3) fatalIf(t, err) - data, err := snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + data, err := snapshotCreator.Create(v8.FunctionCodeHandlingClear) fatalIf(t, err) iso := v8.NewIsolate(v8.WithStartupData(data)) @@ -137,10 +137,10 @@ func TestCreateSnapshotErrorAfterAddingMultipleDefaultContext(t *testing.T) { snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") - err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + err = snapshotCreator.SetDefaultContext(snapshotCreatorCtx) fatalIf(t, err) - err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + err = snapshotCreator.SetDefaultContext(snapshotCreatorCtx) defer snapshotCreatorCtx.Close() if err == nil { @@ -183,7 +183,7 @@ func TestCreateSnapshotErrorIfNodefaultContextIsAdded(t *testing.T) { snapshotCreator := v8.NewSnapshotCreator() defer snapshotCreator.Dispose() - _, err := snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + _, err := snapshotCreator.Create(v8.FunctionCodeHandlingClear) if err == nil { t.Error("Creating a snapshop should have fail") diff --git a/v8go.cc b/v8go.cc index 70c540d21..55a948392 100644 --- a/v8go.cc +++ b/v8go.cc @@ -320,8 +320,6 @@ size_t AddContext(SnapshotCreatorPtr snapshotCreator, ContextPtr ctx) { RtnSnapshotBlob* CreateBlob(SnapshotCreatorPtr snapshotCreator, int function_code_handling) { - // kKeep - keeps any compiled functions - // kClear - does not keep any compiled functions StartupData startup_data = snapshotCreator->CreateBlob( SnapshotCreator::FunctionCodeHandling(function_code_handling)); From 88b05d0194e68d9f82ca338535702ee3cb7b6449 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Thu, 27 Jan 2022 10:00:27 +0100 Subject: [PATCH 04/12] cleanup tests --- context_test.go | 2 +- snapshot_creator_test.go | 59 ++++++++++++++++------------------------ 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/context_test.go b/context_test.go index 9eb9d046d..acaca9dbe 100644 --- a/context_test.go +++ b/context_test.go @@ -48,7 +48,7 @@ func TestNewContextFromSnapshotErrorWhenIsolateHasNoStartupData(t *testing.T) { ctx, err := v8.NewContextFromSnapshot(iso, 1) if ctx != nil { - t.Errorf("error expected nil context got: %+v", ctx) + t.Errorf("expected nil context got: %+v", ctx) } if err == nil { t.Error("error expected but was ") diff --git a/snapshot_creator_test.go b/snapshot_creator_test.go index 48995aaa4..9c3a6dc88 100644 --- a/snapshot_creator_test.go +++ b/snapshot_creator_test.go @@ -33,18 +33,14 @@ func TestCreateSnapshot(t *testing.T) { defer ctx.Close() runVal, err := ctx.Global().Get("run") - if err != nil { - panic(err) - } + fatalIf(t, err) fn, err := runVal.AsFunction() - if err != nil { - panic(err) - } + fatalIf(t, err) + val, err := fn.Call(v8.Undefined(iso)) - if err != nil { - panic(err) - } + fatalIf(t, err) + if val.String() != "7" { t.Fatal("invalid val") } @@ -90,39 +86,32 @@ func TestCreateSnapshotAndAddExtraContext(t *testing.T) { defer ctx.Close() runVal, err := ctx.Global().Get("run") - if err != nil { - panic(err) - } + fatalIf(t, err) fn, err := runVal.AsFunction() - if err != nil { - panic(err) - } + fatalIf(t, err) + val, err := fn.Call(v8.Undefined(iso)) - if err != nil { - panic(err) - } + fatalIf(t, err) + if val.String() != "12" { t.Fatal("invalid val") } ctx, err = v8.NewContextFromSnapshot(iso, index2) fatalIf(t, err) + defer ctx.Close() runVal, err = ctx.Global().Get("run") - if err != nil { - panic(err) - } + fatalIf(t, err) fn, err = runVal.AsFunction() - if err != nil { - panic(err) - } + fatalIf(t, err) + val, err = fn.Call(v8.Undefined(iso)) - if err != nil { - panic(err) - } + fatalIf(t, err) + if val.String() != "3" { t.Fatal("invalid val") } @@ -144,7 +133,7 @@ func TestCreateSnapshotErrorAfterAddingMultipleDefaultContext(t *testing.T) { defer snapshotCreatorCtx.Close() if err == nil { - t.Error("Adding an extra default cointext show have fail") + t.Error("setting another default context should have failed, got ") } } @@ -157,25 +146,25 @@ func TestCreateSnapshotErrorAfterSuccessfullCreate(t *testing.T) { snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") - err = snapshotCreator.SetDeafultContext(snapshotCreatorCtx) + err = snapshotCreator.SetDefaultContext(snapshotCreatorCtx) fatalIf(t, err) - _, err = snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + _, err = snapshotCreator.Create(v8.FunctionCodeHandlingClear) fatalIf(t, err) _, err = snapshotCreator.GetIsolate() if err == nil { - t.Error("Getting Isolate should have fail") + t.Error("getting Isolate should have fail") } _, err = snapshotCreator.AddContext(snapshotCreatorCtx) if err == nil { - t.Error("Adding context should have fail") + t.Error("adding context should have fail") } - _, err = snapshotCreator.Create(v8.FunctionCodeHandlingKlear) + _, err = snapshotCreator.Create(v8.FunctionCodeHandlingClear) if err == nil { - t.Error("Creating snapshot should have fail") + t.Error("creating snapshot should have fail") } } @@ -186,6 +175,6 @@ func TestCreateSnapshotErrorIfNodefaultContextIsAdded(t *testing.T) { _, err := snapshotCreator.Create(v8.FunctionCodeHandlingClear) if err == nil { - t.Error("Creating a snapshop should have fail") + t.Error("creating a snapshop should have fail") } } From b66880e300ffef0c42cfde7b052cdb71282fcfe6 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Thu, 27 Jan 2022 10:00:49 +0100 Subject: [PATCH 05/12] remove unnecessary check for nullptr --- v8go.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v8go.cc b/v8go.cc index 55a948392..d8408d6ab 100644 --- a/v8go.cc +++ b/v8go.cc @@ -158,7 +158,7 @@ IsolatePtr NewIsolate(IsolateOptions options) { params.array_buffer_allocator = default_allocator; StartupData* startup_data; - if (options.snapshot_blob_data != nullptr) { + if (options.snapshot_blob_data) { startup_data = new StartupData{options.snapshot_blob_data, options.snapshot_blob_raw_size}; params.snapshot_blob = startup_data; From fbf12810e272e6f7a9e0ba8bf5e9bbc0990a0598 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Thu, 27 Jan 2022 11:20:38 +0100 Subject: [PATCH 06/12] handle errors when calling NewContextFromSnapshot at V8 level --- context.go | 12 ++++++------ context_test.go | 39 +++++++++++++++++++++++++++++++++++++++ v8go.cc | 21 +++++++++++++++++---- v8go.h | 7 ++++++- 4 files changed, 68 insertions(+), 11 deletions(-) diff --git a/context.go b/context.go index 1ca4cbd4c..b9b5cc33e 100644 --- a/context.go +++ b/context.go @@ -8,7 +8,6 @@ package v8go // #include "v8go.h" import "C" import ( - "errors" "runtime" "sync" "unsafe" @@ -78,21 +77,22 @@ func NewContext(opt ...ContextOption) *Context { } // NewContextFromSnapshot creates a new JavaScript context from the Isolate startup data; -// If the Isolate has no startup data associated returns an error. +// error will be of type `JSError` if not nil. func NewContextFromSnapshot(iso *Isolate, snapshot_index int) (*Context, error) { ctxMutex.Lock() ctxSeq++ ref := ctxSeq ctxMutex.Unlock() - createParams := iso.createParams - if createParams == nil || createParams.startupData == nil { - return nil, errors.New("v8go: The isolate must have startupData associated with it") + rtn := C.NewContextFromSnapshot(iso.ptr, C.size_t(snapshot_index), C.int(ref)) + + if rtn.context == nil { + return nil, newJSError(rtn.error) } ctx := &Context{ ref: ref, - ptr: C.NewContextFromSnapshot(iso.ptr, C.size_t(snapshot_index), C.int(ref)), + ptr: rtn.context, iso: iso, } diff --git a/context_test.go b/context_test.go index acaca9dbe..989c0f22b 100644 --- a/context_test.go +++ b/context_test.go @@ -55,6 +55,45 @@ func TestNewContextFromSnapshotErrorWhenIsolateHasNoStartupData(t *testing.T) { } } +func TestNewContextFromSnapshotErrorWhenIndexOutOfRange(t *testing.T) { + t.Parallel() + + snapshotCreator := v8.NewSnapshotCreator() + snapshotCreatorIso, err := snapshotCreator.GetIsolate() + fatalIf(t, err) + + snapshotCreatorCtx := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx.Close() + + snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") + snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") + err = snapshotCreator.SetDefaultContext(snapshotCreatorCtx) + fatalIf(t, err) + + snapshotCreatorCtx2 := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx2.Close() + + snapshotCreatorCtx2.RunScript(`const multiply = (a, b) => a * b`, "add.js") + snapshotCreatorCtx2.RunScript(`function run() { return multiply(3, 4); }`, "main.js") + index, err := snapshotCreator.AddContext(snapshotCreatorCtx2) + fatalIf(t, err) + + data, err := snapshotCreator.Create(v8.FunctionCodeHandlingClear) + fatalIf(t, err) + + iso := v8.NewIsolate(v8.WithStartupData(data)) + defer iso.Dispose() + + ctx, err := v8.NewContextFromSnapshot(iso, index+1) + + if ctx != nil { + t.Errorf("expected nil context got: %+v", ctx) + } + if err == nil { + t.Error("error expected but was ") + } +} + func TestJSExceptions(t *testing.T) { t.Parallel() diff --git a/v8go.cc b/v8go.cc index d8408d6ab..0da59508a 100644 --- a/v8go.cc +++ b/v8go.cc @@ -668,28 +668,41 @@ ContextPtr NewContext(IsolatePtr iso, return ctx; } -ContextPtr NewContextFromSnapshot(IsolatePtr iso, +RtnContext NewContextFromSnapshot(IsolatePtr iso, size_t snapshot_blob_index, int ref) { Locker locker(iso); Isolate::Scope isolate_scope(iso); HandleScope handle_scope(iso); + RtnContext rtn = {}; + // For function callbacks we need a reference to the context, but because of // the complexities of C -> Go function pointers, we store a reference to the // context as a simple integer identifier; this can then be used on the Go // side to lookup the context in the context registry. We use slot 1 as slot 0 // has special meaning for the Chrome debugger. - Local local_ctx = - Context::FromSnapshot(iso, snapshot_blob_index).ToLocalChecked(); + Local local_ctx; + MaybeLocal maybe_local_ctx = + Context::FromSnapshot(iso, snapshot_blob_index); + + if (!maybe_local_ctx.ToLocal(&local_ctx)) { + RtnError error = {nullptr, nullptr, nullptr}; + error.msg = CopyString("Failed to create context from snapshot index: " + + std::to_string(snapshot_blob_index)); + rtn.error = error; + return rtn; + } + local_ctx->SetEmbedderData(1, Integer::New(iso, ref)); m_ctx* ctx = new m_ctx; ctx->ptr.Reset(iso, local_ctx); ctx->iso = iso; ctx->startup_data = nullptr; - return ctx; + rtn.context = ctx; + return rtn; } void ContextFree(ContextPtr ctx) { diff --git a/v8go.h b/v8go.h index 3c59afba1..efe95eed2 100644 --- a/v8go.h +++ b/v8go.h @@ -124,6 +124,11 @@ typedef struct { RtnError error; } RtnValue; +typedef struct { + ContextPtr context; + RtnError error; +} RtnContext; + typedef struct { const char* data; int length; @@ -190,7 +195,7 @@ extern void CPUProfileDelete(CPUProfile* ptr); extern ContextPtr NewContext(IsolatePtr iso_ptr, TemplatePtr global_template_ptr, int ref); -extern ContextPtr NewContextFromSnapshot(IsolatePtr iso, +extern RtnContext NewContextFromSnapshot(IsolatePtr iso, size_t snapshot_blob_index, int ref); extern void ContextFree(ContextPtr ptr); From ebdfa43ceb462988cec848c0a725e879859c1357 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Tue, 15 Mar 2022 12:10:55 +0100 Subject: [PATCH 07/12] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1b80bd4..f46fc2374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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). ## [Unreleased] +- NewIsolate accepts option arguments. Thanks to [@GustavoCaso](https://github.com/GustavoCaso) +- Add support for Snapshot creation. Thanks to [@GustavoCaso](https://github.com/GustavoCaso) ### Fixed - Use string length to ensure null character-containing strings in Go/JS are not terminated early. From acaa5fb629f191bfa72aef0b993bdff7c2e38fb0 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Tue, 15 Mar 2022 12:13:12 +0100 Subject: [PATCH 08/12] fix typos --- snapshot_creator.go | 4 ++-- snapshot_creator_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/snapshot_creator.go b/snapshot_creator.go index 872a84863..10ca6414d 100644 --- a/snapshot_creator.go +++ b/snapshot_creator.go @@ -14,7 +14,7 @@ import ( type FunctionCodeHandling int -// Clear - does not keeps any compiled data prior to serialization/deserialization/verify pass +// Clear - does not keep any compiled data prior to serialization/deserialization/verify pass // Keep - keeps any compiled data prior to serialization/deserialization/verify pass const ( FunctionCodeHandlingClear FunctionCodeHandling = iota @@ -50,7 +50,7 @@ func NewSnapshotCreator() *SnapshotCreator { } // GetIsolate returns the Isolate associated with the SnapshotCreator. -// This Isolate must be use to create the contexts that later will be use to create the snapshot blob. +// This Isolate must be used to create the contexts that later will be used to create the snapshot blob. func (s *SnapshotCreator) GetIsolate() (*Isolate, error) { if s.ptr == nil { return nil, errors.New("v8go: Cannot get Isolate after creating the blob") diff --git a/snapshot_creator_test.go b/snapshot_creator_test.go index 9c3a6dc88..efa28a3a5 100644 --- a/snapshot_creator_test.go +++ b/snapshot_creator_test.go @@ -168,7 +168,7 @@ func TestCreateSnapshotErrorAfterSuccessfullCreate(t *testing.T) { } } -func TestCreateSnapshotErrorIfNodefaultContextIsAdded(t *testing.T) { +func TestCreateSnapshotErrorIfNoDefaultContextIsAdded(t *testing.T) { snapshotCreator := v8.NewSnapshotCreator() defer snapshotCreator.Dispose() From 40300294e9006ec7d792deec9729bfce26dab5ed Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Tue, 15 Mar 2022 12:16:28 +0100 Subject: [PATCH 09/12] make sure pointer to snapshot creator is nil after disposing it --- snapshot_creator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/snapshot_creator.go b/snapshot_creator.go index 10ca6414d..6ea83a6b2 100644 --- a/snapshot_creator.go +++ b/snapshot_creator.go @@ -112,5 +112,6 @@ func (s *SnapshotCreator) Create(functionCode FunctionCodeHandling) (*StartupDat func (s *SnapshotCreator) Dispose() { if s.ptr != nil { C.DeleteSnapshotCreator(s.ptr) + s.ptr = nil } } From ef9e9106bb9bec1c7605144d2836d221bb5442a8 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Tue, 15 Mar 2022 12:19:38 +0100 Subject: [PATCH 10/12] move defer call --- snapshot_creator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapshot_creator_test.go b/snapshot_creator_test.go index efa28a3a5..900588981 100644 --- a/snapshot_creator_test.go +++ b/snapshot_creator_test.go @@ -123,6 +123,7 @@ func TestCreateSnapshotErrorAfterAddingMultipleDefaultContext(t *testing.T) { snapshotCreatorIso, err := snapshotCreator.GetIsolate() fatalIf(t, err) snapshotCreatorCtx := v8.NewContext(snapshotCreatorIso) + defer snapshotCreatorCtx.Close() snapshotCreatorCtx.RunScript(`const add = (a, b) => a + b`, "add.js") snapshotCreatorCtx.RunScript(`function run() { return add(3, 4); }`, "main.js") @@ -130,7 +131,6 @@ func TestCreateSnapshotErrorAfterAddingMultipleDefaultContext(t *testing.T) { fatalIf(t, err) err = snapshotCreator.SetDefaultContext(snapshotCreatorCtx) - defer snapshotCreatorCtx.Close() if err == nil { t.Error("setting another default context should have failed, got ") From eec9dcd2cac4aee3e8aa8b25288c694405f73856 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Tue, 15 Mar 2022 12:22:29 +0100 Subject: [PATCH 11/12] clean some C++ code --- v8go.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/v8go.cc b/v8go.cc index 0da59508a..27b220ee8 100644 --- a/v8go.cc +++ b/v8go.cc @@ -684,10 +684,8 @@ RtnContext NewContextFromSnapshot(IsolatePtr iso, // has special meaning for the Chrome debugger. Local local_ctx; - MaybeLocal maybe_local_ctx = - Context::FromSnapshot(iso, snapshot_blob_index); - if (!maybe_local_ctx.ToLocal(&local_ctx)) { + if (!Context::FromSnapshot(iso, snapshot_blob_index).ToLocal(&local_ctx)) { RtnError error = {nullptr, nullptr, nullptr}; error.msg = CopyString("Failed to create context from snapshot index: " + std::to_string(snapshot_blob_index)); From ebb5fa86eef60eb92c366285415e2c941311c101 Mon Sep 17 00:00:00 2001 From: Gustavo Caso Date: Tue, 15 Mar 2022 12:29:32 +0100 Subject: [PATCH 12/12] improve comments on SnapshotCreator.Create --- snapshot_creator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/snapshot_creator.go b/snapshot_creator.go index 6ea83a6b2..837d7b09e 100644 --- a/snapshot_creator.go +++ b/snapshot_creator.go @@ -86,6 +86,7 @@ func (s *SnapshotCreator) AddContext(ctx *Context) (int, error) { } // Create creates a snapshot data blob. +// The snapshot creator instance is unsable after creating the snapshot data blob. func (s *SnapshotCreator) Create(functionCode FunctionCodeHandling) (*StartupData, error) { if s.ptr == nil { return nil, errors.New("v8go: Cannot use snapshot creator after creating the blob")