summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libdeno/api.cc21
-rw-r--r--libdeno/binding.cc162
-rw-r--r--libdeno/deno.h31
-rw-r--r--libdeno/internal.h14
-rw-r--r--libdeno/libdeno_test.cc100
-rw-r--r--libdeno/snapshot_creator.cc2
6 files changed, 287 insertions, 43 deletions
diff --git a/libdeno/api.cc b/libdeno/api.cc
index e6bc670c7..2c779fb68 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<uint8_t*>(const_cast<char*>(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<deno::DenoIsolate*>(d_);
d->isolate_->TerminateExecution();
}
+
+void deno_resolve_ok(Deno* d_, const char* filename, const char* source) {
+ deno::DenoIsolate* d = reinterpret_cast<deno::DenoIsolate*>(d_);
+ d->ResolveOk(filename, source);
+}
}
diff --git a/libdeno/binding.cc b/libdeno/binding.cc
index 9e93bfca8..f05d20757 100644
--- a/libdeno/binding.cc
+++ b/libdeno/binding.cc
@@ -285,7 +285,7 @@ void Send(const v8::FunctionCallbackInfo<v8::Value>& 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<v8::Name> property,
info.GetReturnValue().Set(ab);
}
-bool ExecuteV8StringSource(v8::Local<v8::Context> context,
- const char* js_filename,
- v8::Local<v8::String> source) {
+v8::ScriptOrigin ModuleOrigin(v8::Local<v8::Value> resource_name,
+ v8::Isolate* isolate) {
+ return v8::ScriptOrigin(resource_name, v8::Local<v8::Integer>(),
+ v8::Local<v8::Integer>(), v8::Local<v8::Boolean>(),
+ v8::Local<v8::Integer>(), v8::Local<v8::Value>(),
+ v8::Local<v8::Boolean>(), v8::Local<v8::Boolean>(),
+ 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<v8::Module> 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<v8::Module> CompileModule(v8::Local<v8::Context> context,
+ const char* js_filename,
+ v8::Local<v8::String> 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<v8::Module> ResolveCallback(v8::Local<v8::Context> context,
+ v8::Local<v8::String> specifier,
+ v8::Local<v8::Module> 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<v8::Module>();
+ } 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<v8::Context> 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<v8::Context> 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<v8::Context> context) {
diff --git a/libdeno/deno.h b/libdeno/deno.h
index 324f09a95..a061f7452 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 91e06bead..d8316ce6b 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<v8::Module> 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<int, std::string> module_filename_map_;
+ // filename -> Module
+ std::map<std::string, v8::Persistent<v8::Module>> module_map_;
+ // Set by deno_resolve_ok
+ v8::Persistent<v8::Module> resolve_module_;
+
v8::Persistent<v8::Context> context_;
std::map<int32_t, v8::Persistent<v8::Value>> async_data_map_;
std::map<int, v8::Persistent<v8::Value>> pending_promise_map_;
@@ -121,6 +133,8 @@ void DeleteDataRef(DenoIsolate* d, int32_t req_id);
bool Execute(v8::Local<v8::Context> context, const char* js_filename,
const char* js_source);
+bool ExecuteMod(v8::Local<v8::Context> context, const char* js_filename,
+ const char* js_source);
} // namespace deno
diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc
index a992f5303..8b95c1831 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<Deno*>(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 ba926d3f3..ff18bed02 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());