0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

Add ESM support to libdeno

Introduces deno_execute_mod() for executing ES modules.
This commit is contained in:
Ryan Dahl 2018-11-29 15:40:04 -05:00
parent cbb18a596a
commit bba0ed3185
6 changed files with 288 additions and 44 deletions

View file

@ -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);
}
}

View file

@ -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,19 +343,164 @@ 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::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);
int ref_id = referrer->GetIdentityHash();
std::string referrer_filename = d->module_filename_map_[ref_id];
v8::String::Utf8Value specifier_(isolate, specifier);
const char* specifier_c = ToCString(specifier_);
CHECK_NE(d->resolve_cb_, nullptr);
d->resolve_cb_(d->user_data_, specifier_c, referrer_filename.c_str());
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);
}
}
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 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());
CHECK_EQ(v8::Module::kErrored, module->GetStatus());
HandleException(context, module->GetException());
return false;
}
return true;
}
bool Execute(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);
auto name = v8_str(js_filename, true);
v8::TryCatch try_catch(isolate);
v8::ScriptOrigin origin(name);
auto script = v8::Script::Compile(context, source, &origin);
@ -377,15 +522,6 @@ bool ExecuteV8StringSource(v8::Local<v8::Context> context,
return true;
}
bool Execute(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);
auto source = v8_str(js_source, true);
return ExecuteV8StringSource(context, js_filename, source);
}
void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context) {
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context);

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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());