From f557f175e2ea3aa8074505c0c62825a884640209 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 29 Nov 2018 15:40:04 -0500 Subject: [PATCH] Add ESM support to libdeno Introduces deno_execute_mod() for executing ES modules. --- libdeno/api.cc | 21 +++++ libdeno/binding.cc | 162 +++++++++++++++++++++++++++++++++--- libdeno/deno.h | 31 +++++-- libdeno/internal.h | 14 ++++ libdeno/libdeno_test.cc | 100 +++++++++++++++++----- libdeno/snapshot_creator.cc | 2 +- 6 files changed, 287 insertions(+), 43 deletions(-) diff --git a/libdeno/api.cc b/libdeno/api.cc index e6bc670c7127c3..2c779fb6863377 100644 --- a/libdeno/api.cc +++ b/libdeno/api.cc @@ -80,7 +80,10 @@ deno::DenoIsolate* unwrap(Deno* d_) { deno_buf deno_get_snapshot(Deno* d_) { auto* d = unwrap(d_); CHECK_NE(d->snapshot_creator_, nullptr); + CHECK(d->resolve_module_.IsEmpty()); + d->ClearModules(); d->context_.Reset(); + auto blob = d->snapshot_creator_->CreateBlob( v8::SnapshotCreator::FunctionCodeHandling::kClear); return {nullptr, 0, reinterpret_cast(const_cast(blob.data)), @@ -123,6 +126,19 @@ int deno_execute(Deno* d_, void* user_data, const char* js_filename, return deno::Execute(context, js_filename, js_source) ? 1 : 0; } +int deno_execute_mod(Deno* d_, void* user_data, const char* js_filename, + const char* js_source) { + auto* d = unwrap(d_); + deno::UserDataScope user_data_scope(d, user_data); + auto* isolate = d->isolate_; + v8::Locker locker(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + auto context = d->context_.Get(d->isolate_); + CHECK(!context.IsEmpty()); + return deno::ExecuteMod(context, js_filename, js_source) ? 1 : 0; +} + int deno_respond(Deno* d_, void* user_data, int32_t req_id, deno_buf buf) { auto* d = unwrap(d_); if (d->current_args_ != nullptr) { @@ -193,4 +209,9 @@ void deno_terminate_execution(Deno* d_) { deno::DenoIsolate* d = reinterpret_cast(d_); d->isolate_->TerminateExecution(); } + +void deno_resolve_ok(Deno* d_, const char* filename, const char* source) { + deno::DenoIsolate* d = reinterpret_cast(d_); + d->ResolveOk(filename, source); +} } diff --git a/libdeno/binding.cc b/libdeno/binding.cc index 9e93bfca8aec16..f05d20757c9178 100644 --- a/libdeno/binding.cc +++ b/libdeno/binding.cc @@ -285,7 +285,7 @@ void Send(const v8::FunctionCallbackInfo& args) { DCHECK_EQ(d->isolate_, isolate); v8::Locker locker(d->isolate_); - v8::EscapableHandleScope handle_scope(isolate); + v8::HandleScope handle_scope(isolate); CHECK_EQ(d->current_args_, nullptr); // libdeno.send re-entry forbidden. int32_t req_id = d->next_req_id_++; @@ -343,34 +343,146 @@ void Shared(v8::Local property, info.GetReturnValue().Set(ab); } -bool ExecuteV8StringSource(v8::Local context, - const char* js_filename, - v8::Local source) { +v8::ScriptOrigin ModuleOrigin(v8::Local resource_name, + v8::Isolate* isolate) { + return v8::ScriptOrigin(resource_name, v8::Local(), + v8::Local(), v8::Local(), + v8::Local(), v8::Local(), + v8::Local(), v8::Local(), + v8::True(isolate)); +} + +void DenoIsolate::ClearModules() { + for (auto it = module_map_.begin(); it != module_map_.end(); it++) { + it->second.Reset(); + } + module_map_.clear(); + module_filename_map_.clear(); +} + +void DenoIsolate::RegisterModule(const char* filename, + v8::Local module) { + int id = module->GetIdentityHash(); + + // v8.h says that identity hash is not necessarily unique. It seems it's quite + // unique enough for the purposes of O(1000) modules, so we use it as a + // hashmap key here. The following check is to detect collisions. + CHECK_EQ(0, module_filename_map_.count(id)); + + module_filename_map_[id] = filename; + module_map_.emplace(std::piecewise_construct, std::make_tuple(filename), + std::make_tuple(isolate_, module)); +} + +v8::MaybeLocal CompileModule(v8::Local context, + const char* js_filename, + v8::Local source_text) { auto* isolate = context->GetIsolate(); + v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handle_scope(isolate); + v8::EscapableHandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context); + auto origin = ModuleOrigin(v8_str(js_filename, true), isolate); + v8::ScriptCompiler::Source source(source_text, origin); + + auto maybe_module = v8::ScriptCompiler::CompileModule(isolate, &source); + + if (!maybe_module.IsEmpty()) { + auto module = maybe_module.ToLocalChecked(); + CHECK_EQ(v8::Module::kUninstantiated, module->GetStatus()); + DenoIsolate* d = FromIsolate(isolate); + d->RegisterModule(js_filename, module); + } + + return handle_scope.EscapeMaybe(maybe_module); +} + +v8::MaybeLocal ResolveCallback(v8::Local context, + v8::Local specifier, + v8::Local referrer) { + auto* isolate = context->GetIsolate(); + DenoIsolate* d = FromIsolate(isolate); + + v8::Isolate::Scope isolate_scope(isolate); + v8::EscapableHandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); - v8::TryCatch try_catch(isolate); + int ref_id = referrer->GetIdentityHash(); + std::string referrer_filename = d->module_filename_map_[ref_id]; - auto name = v8_str(js_filename, true); + v8::String::Utf8Value specifier_(isolate, specifier); + const char* specifier_c = ToCString(specifier_); - v8::ScriptOrigin origin(name); + CHECK_NE(d->resolve_cb_, nullptr); + d->resolve_cb_(d->user_data_, specifier_c, referrer_filename.c_str()); - auto script = v8::Script::Compile(context, source, &origin); + if (d->resolve_module_.IsEmpty()) { + // Resolution Error. + isolate->ThrowException(v8_str("module resolution error")); + return v8::MaybeLocal(); + } else { + auto module = d->resolve_module_.Get(isolate); + d->resolve_module_.Reset(); + return handle_scope.Escape(module); + } +} - if (script.IsEmpty()) { +void DenoIsolate::ResolveOk(const char* filename, const char* source) { + CHECK(resolve_module_.IsEmpty()); + auto count = module_map_.count(filename); + if (count == 1) { + auto module = module_map_[filename].Get(isolate_); + resolve_module_.Reset(isolate_, module); + } else { + CHECK_EQ(count, 0); + v8::HandleScope handle_scope(isolate_); + auto context = context_.Get(isolate_); + v8::TryCatch try_catch(isolate_); + auto maybe_module = CompileModule(context, filename, v8_str(source)); + if (maybe_module.IsEmpty()) { + DCHECK(try_catch.HasCaught()); + HandleException(context, try_catch.Exception()); + } else { + auto module = maybe_module.ToLocalChecked(); + resolve_module_.Reset(isolate_, module); + } + } +} + +bool ExecuteMod(v8::Local context, const char* js_filename, + const char* js_source) { + auto* isolate = context->GetIsolate(); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context); + + auto source = v8_str(js_source, true); + + v8::TryCatch try_catch(isolate); + + auto maybe_module = CompileModule(context, js_filename, source); + + if (maybe_module.IsEmpty()) { DCHECK(try_catch.HasCaught()); HandleException(context, try_catch.Exception()); return false; } + DCHECK(!try_catch.HasCaught()); - auto result = script.ToLocalChecked()->Run(context); + auto module = maybe_module.ToLocalChecked(); + auto maybe_ok = module->InstantiateModule(context, ResolveCallback); + if (maybe_ok.IsNothing()) { + return false; + } + + CHECK_EQ(v8::Module::kInstantiated, module->GetStatus()); + auto result = module->Evaluate(context); if (result.IsEmpty()) { DCHECK(try_catch.HasCaught()); - HandleException(context, try_catch.Exception()); + CHECK_EQ(v8::Module::kErrored, module->GetStatus()); + HandleException(context, module->GetException()); return false; } @@ -382,8 +494,32 @@ bool Execute(v8::Local context, const char* js_filename, auto* isolate = context->GetIsolate(); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context); + auto source = v8_str(js_source, true); - return ExecuteV8StringSource(context, js_filename, source); + auto name = v8_str(js_filename, true); + + v8::TryCatch try_catch(isolate); + + v8::ScriptOrigin origin(name); + + auto script = v8::Script::Compile(context, source, &origin); + + if (script.IsEmpty()) { + DCHECK(try_catch.HasCaught()); + HandleException(context, try_catch.Exception()); + return false; + } + + auto result = script.ToLocalChecked()->Run(context); + + if (result.IsEmpty()) { + DCHECK(try_catch.HasCaught()); + HandleException(context, try_catch.Exception()); + return false; + } + + return true; } void InitializeContext(v8::Isolate* isolate, v8::Local context) { diff --git a/libdeno/deno.h b/libdeno/deno.h index 324f09a956a122..a061f74520b6f9 100644 --- a/libdeno/deno.h +++ b/libdeno/deno.h @@ -25,15 +25,26 @@ typedef struct deno_s Deno; typedef void (*deno_recv_cb)(void* user_data, int32_t req_id, deno_buf control_buf, deno_buf data_buf); +// A callback to implement ES Module imports. User must call deno_resolve_ok() +// at most once during deno_resolve_cb. If deno_resolve_ok() is not called, the +// specifier is considered invalid and will issue an error in JS. The reason +// deno_resolve_cb does not return deno_module is to avoid unnecessary heap +// allocations. +typedef void (*deno_resolve_cb)(void* user_data, const char* specifier, + const char* referrer); + +void deno_resolve_ok(Deno* d, const char* filename, const char* source); + void deno_init(); const char* deno_v8_version(); void deno_set_v8_flags(int* argc, char** argv); typedef struct { - int will_snapshot; // Default 0. If calling deno_get_snapshot 1. - deno_buf load_snapshot; // Optionally: A deno_buf from deno_get_snapshot. - deno_buf shared; // Shared buffer to be mapped to libdeno.shared - deno_recv_cb recv_cb; // Maps to libdeno.send() calls. + int will_snapshot; // Default 0. If calling deno_get_snapshot 1. + deno_buf load_snapshot; // Optionally: A deno_buf from deno_get_snapshot. + deno_buf shared; // Shared buffer to be mapped to libdeno.shared + deno_recv_cb recv_cb; // Maps to libdeno.send() calls. + deno_resolve_cb resolve_cb; // Each import calls this. } deno_config; // Create a new deno isolate. @@ -47,9 +58,10 @@ deno_buf deno_get_snapshot(Deno* d); void deno_delete(Deno* d); -// Returns false on error. +// Compile and execute a traditional JavaScript script that does not use +// module import statements. +// Return value: 0 = fail, 1 = success // Get error text with deno_last_exception(). -// 0 = fail, 1 = success // // TODO change return value to be const char*. On success the return // value is nullptr, on failure it is the JSON exception text that @@ -59,6 +71,13 @@ void deno_delete(Deno* d); int deno_execute(Deno* d, void* user_data, const char* js_filename, const char* js_source); +// Compile and execute an ES module. Caller must have provided a deno_resolve_cb +// when instantiating the Deno object. +// Return value: 0 = fail, 1 = success +// Get error text with deno_last_exception(). +int deno_execute_mod(Deno* d, void* user_data, const char* js_filename, + const char* js_source); + // deno_respond sends up to one message back for every deno_recv_cb made. // // If this is called during deno_recv_cb, the issuing libdeno.send() in diff --git a/libdeno/internal.h b/libdeno/internal.h index 91e06beadba139..d8316ce6b488f4 100644 --- a/libdeno/internal.h +++ b/libdeno/internal.h @@ -20,6 +20,7 @@ class DenoIsolate { snapshot_creator_(nullptr), global_import_buf_ptr_(nullptr), recv_cb_(config.recv_cb), + resolve_cb_(config.resolve_cb), next_req_id_(0), user_data_(nullptr) { array_buffer_allocator_ = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); @@ -40,6 +41,9 @@ class DenoIsolate { } void AddIsolate(v8::Isolate* isolate); + void RegisterModule(const char* filename, v8::Local module); + void ResolveOk(const char* filename, const char* source); + void ClearModules(); v8::Isolate* isolate_; v8::ArrayBuffer::Allocator* array_buffer_allocator_; @@ -48,9 +52,17 @@ class DenoIsolate { v8::SnapshotCreator* snapshot_creator_; void* global_import_buf_ptr_; deno_recv_cb recv_cb_; + deno_resolve_cb resolve_cb_; int32_t next_req_id_; void* user_data_; + // identity hash -> filename + std::map module_filename_map_; + // filename -> Module + std::map> module_map_; + // Set by deno_resolve_ok + v8::Persistent resolve_module_; + v8::Persistent context_; std::map> async_data_map_; std::map> pending_promise_map_; @@ -121,6 +133,8 @@ void DeleteDataRef(DenoIsolate* d, int32_t req_id); bool Execute(v8::Local context, const char* js_filename, const char* js_source); +bool ExecuteMod(v8::Local context, const char* js_filename, + const char* js_source); } // namespace deno diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc index a992f5303a8736..8b95c1831439eb 100644 --- a/libdeno/libdeno_test.cc +++ b/libdeno/libdeno_test.cc @@ -3,24 +3,18 @@ TEST(LibDenoTest, InitializesCorrectly) { EXPECT_NE(snapshot.data_ptr, nullptr); - Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); - EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "1 + 2")); - deno_delete(d); -} - -TEST(LibDenoTest, InitializesCorrectlyWithoutSnapshot) { - Deno* d = deno_new(deno_config{0, empty, empty, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "1 + 2")); deno_delete(d); } TEST(LibDenoTest, Snapshotter) { - Deno* d1 = deno_new(deno_config{1, empty, empty, nullptr}); + Deno* d1 = deno_new(deno_config{1, empty, empty, nullptr, nullptr}); EXPECT_TRUE(deno_execute(d1, nullptr, "a.js", "a = 1 + 2")); deno_buf test_snapshot = deno_get_snapshot(d1); deno_delete(d1); - Deno* d2 = deno_new(deno_config{0, test_snapshot, empty, nullptr}); + Deno* d2 = deno_new(deno_config{0, test_snapshot, empty, nullptr, nullptr}); EXPECT_TRUE( deno_execute(d2, nullptr, "b.js", "if (a != 3) throw Error('x');")); deno_delete(d2); @@ -29,14 +23,14 @@ TEST(LibDenoTest, Snapshotter) { } TEST(LibDenoTest, CanCallFunction) { - Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "if (CanCallFunction() != 'foo') throw Error();")); deno_delete(d); } TEST(LibDenoTest, ErrorsCorrectly) { - Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr}); EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "throw Error()")); deno_delete(d); } @@ -81,7 +75,7 @@ TEST(LibDenoTest, RecvReturnEmpty) { EXPECT_EQ(buf.data_ptr[1], 'b'); EXPECT_EQ(buf.data_ptr[2], 'c'); }; - Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb}); + Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "RecvReturnEmpty()")); EXPECT_EQ(count, 2); deno_delete(d); @@ -99,14 +93,14 @@ TEST(LibDenoTest, RecvReturnBar) { EXPECT_EQ(buf.data_ptr[2], 'c'); deno_respond(d, user_data, req_id, strbuf("bar")); }; - Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb}); + Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr}); EXPECT_TRUE(deno_execute(d, d, "a.js", "RecvReturnBar()")); EXPECT_EQ(count, 1); deno_delete(d); } TEST(LibDenoTest, DoubleRecvFails) { - Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr}); EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "DoubleRecvFails()")); deno_delete(d); } @@ -140,7 +134,7 @@ TEST(LibDenoTest, SendRecvSlice) { // Send back. deno_respond(d, user_data, req_id, buf2); }; - Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb}); + Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr}); EXPECT_TRUE(deno_execute(d, d, "a.js", "SendRecvSlice()")); EXPECT_EQ(count, 5); deno_delete(d); @@ -157,26 +151,26 @@ TEST(LibDenoTest, JSSendArrayBufferViewTypes) { EXPECT_EQ(buf.alloc_len, 4321u); EXPECT_EQ(buf.data_ptr[0], count); }; - Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb}); + Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "JSSendArrayBufferViewTypes()")); EXPECT_EQ(count, 3); deno_delete(d); } TEST(LibDenoTest, TypedArraySnapshots) { - Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "TypedArraySnapshots()")); deno_delete(d); } TEST(LibDenoTest, SnapshotBug) { - Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "SnapshotBug()")); deno_delete(d); } TEST(LibDenoTest, GlobalErrorHandling) { - Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr}); EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()")); // We only check that it starts with this string, so we don't have to check // the second frame, which contains line numbers in libdeno_test.js and may @@ -204,7 +198,7 @@ TEST(LibDenoTest, DataBuf) { EXPECT_EQ(buf.data_ptr[0], 1); EXPECT_EQ(buf.data_ptr[1], 2); }; - Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb}); + Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "DataBuf()")); EXPECT_EQ(count, 1); // data_buf was subsequently changed in JS, let's check that our copy reflects @@ -217,7 +211,7 @@ TEST(LibDenoTest, DataBuf) { TEST(LibDenoTest, CheckPromiseErrors) { static int count = 0; auto recv_cb = [](auto _, int req_id, auto buf, auto data_buf) { count++; }; - Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb}); + Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb, nullptr}); EXPECT_EQ(deno_last_exception(d), nullptr); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "CheckPromiseErrors()")); EXPECT_EQ(deno_last_exception(d), nullptr); @@ -230,7 +224,7 @@ TEST(LibDenoTest, CheckPromiseErrors) { } TEST(LibDenoTest, LastException) { - Deno* d = deno_new(deno_config{0, empty, empty, nullptr}); + Deno* d = deno_new(deno_config{0, empty, empty, nullptr, nullptr}); EXPECT_EQ(deno_last_exception(d), nullptr); EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "\n\nthrow Error('boo');\n\n")); EXPECT_STREQ(deno_last_exception(d), @@ -245,10 +239,70 @@ TEST(LibDenoTest, LastException) { TEST(LibDenoTest, Shared) { uint8_t s[] = {0, 1, 2}; deno_buf shared = {nullptr, 0, s, 3}; - Deno* d = deno_new(deno_config{0, snapshot, shared, nullptr}); + Deno* d = deno_new(deno_config{0, snapshot, shared, nullptr, nullptr}); EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "Shared()")); EXPECT_EQ(s[0], 42); EXPECT_EQ(s[1], 43); EXPECT_EQ(s[2], 44); deno_delete(d); } + +static const char* mod_a = + "import { retb } from 'b.js'\n" + "if (retb() != 'b') throw Error();"; + +static const char* mod_b = "export function retb() { return 'b' }"; + +TEST(LibDenoTest, ModuleResolution) { + static int count = 0; + auto resolve_cb = [](void* user_data, const char* specifier, + const char* referrer) { + EXPECT_STREQ(specifier, "b.js"); + EXPECT_STREQ(referrer, "a.js"); + count++; + auto d = reinterpret_cast(user_data); + deno_resolve_ok(d, "b.js", mod_b); + }; + Deno* d = deno_new(deno_config{0, empty, empty, nullptr, resolve_cb}); + EXPECT_TRUE(deno_execute_mod(d, d, "a.js", mod_a)); + EXPECT_EQ(count, 1); + deno_delete(d); +} + +TEST(LibDenoTest, ModuleResolutionFail) { + static int count = 0; + auto resolve_cb = [](void* user_data, const char* specifier, + const char* referrer) { + EXPECT_STREQ(specifier, "b.js"); + EXPECT_STREQ(referrer, "a.js"); + count++; + // Do not call deno_resolve_ok(); + }; + Deno* d = deno_new(deno_config{0, empty, empty, nullptr, resolve_cb}); + EXPECT_FALSE(deno_execute_mod(d, d, "a.js", mod_a)); + EXPECT_EQ(count, 1); + deno_delete(d); +} + +TEST(LibDenoTest, ModuleSnapshot) { + Deno* d1 = deno_new(deno_config{1, empty, empty, nullptr, nullptr}); + EXPECT_TRUE(deno_execute_mod(d1, nullptr, "x.js", + "const globalEval = eval\n" + "const global = globalEval('this')\n" + "global.a = 1 + 2")); + deno_buf test_snapshot = deno_get_snapshot(d1); + deno_delete(d1); + + const char* y_src = "if (a != 3) throw Error('x');"; + + deno_config config{0, test_snapshot, empty, nullptr, nullptr}; + Deno* d2 = deno_new(config); + EXPECT_TRUE(deno_execute(d2, nullptr, "y.js", y_src)); + deno_delete(d2); + + Deno* d3 = deno_new(config); + EXPECT_TRUE(deno_execute_mod(d3, nullptr, "y.js", y_src)); + deno_delete(d3); + + delete[] test_snapshot.data_ptr; +} diff --git a/libdeno/snapshot_creator.cc b/libdeno/snapshot_creator.cc index ba926d3f3585d6..ff18bed02364d6 100644 --- a/libdeno/snapshot_creator.cc +++ b/libdeno/snapshot_creator.cc @@ -23,7 +23,7 @@ int main(int argc, char** argv) { CHECK(deno::ReadFileToString(js_fn, &js_source)); deno_init(); - deno_config config = {1, deno::empty_buf, deno::empty_buf, nullptr}; + deno_config config = {1, deno::empty_buf, deno::empty_buf, nullptr, nullptr}; Deno* d = deno_new(config); int r = deno_execute(d, nullptr, js_fn, js_source.c_str());