mirror of
https://github.com/denoland/deno.git
synced 2025-10-03 15:44:36 +00:00
Refactor libdeno ES module interface. (#1624)
Allows for future asynchronous module loading. Add support for import.meta.url Fixes #1496
This commit is contained in:
parent
7d278a0383
commit
00597ffde1
23 changed files with 884 additions and 467 deletions
|
@ -52,6 +52,7 @@ v8_source_set("libdeno") {
|
|||
"file_util.cc",
|
||||
"file_util.h",
|
||||
"internal.h",
|
||||
"modules.cc",
|
||||
]
|
||||
deps = [
|
||||
":v8",
|
||||
|
@ -84,6 +85,7 @@ v8_executable("test_cc") {
|
|||
sources = [
|
||||
"file_util_test.cc",
|
||||
"libdeno_test.cc",
|
||||
"modules_test.cc",
|
||||
"test.cc",
|
||||
]
|
||||
deps = [
|
||||
|
|
|
@ -80,7 +80,6 @@ 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();
|
||||
|
||||
|
@ -126,20 +125,6 @@ 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, int resolve_only) {
|
||||
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, resolve_only) ? 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) {
|
||||
|
@ -210,9 +195,4 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,6 +246,57 @@ v8::Local<v8::Object> DenoIsolate::GetBuiltinModules() {
|
|||
return handle_scope.Escape(builtin_modules_.Get(isolate_));
|
||||
}
|
||||
|
||||
v8::ScriptOrigin ModuleOrigin(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> resource_name) {
|
||||
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));
|
||||
}
|
||||
|
||||
deno_mod DenoIsolate::RegisterModule(const char* name, const char* source) {
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::Locker locker(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
auto context = context_.Get(isolate_);
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
v8::Local<v8::String> name_str = v8_str(name, true);
|
||||
v8::Local<v8::String> source_str = v8_str(source, true);
|
||||
|
||||
auto origin = ModuleOrigin(isolate_, name_str);
|
||||
v8::ScriptCompiler::Source source_(source_str, origin);
|
||||
|
||||
v8::TryCatch try_catch(isolate_);
|
||||
|
||||
auto maybe_module = v8::ScriptCompiler::CompileModule(isolate_, &source_);
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
CHECK(maybe_module.IsEmpty());
|
||||
HandleException(context, try_catch.Exception());
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto module = maybe_module.ToLocalChecked();
|
||||
|
||||
int id = module->GetIdentityHash();
|
||||
|
||||
std::vector<std::string> import_specifiers;
|
||||
|
||||
for (int i = 0; i < module->GetModuleRequestsLength(); ++i) {
|
||||
v8::Local<v8::String> specifier = module->GetModuleRequest(i);
|
||||
v8::String::Utf8Value specifier_utf8(isolate_, specifier);
|
||||
import_specifiers.push_back(*specifier_utf8);
|
||||
}
|
||||
|
||||
mods_.emplace(std::piecewise_construct, std::make_tuple(id),
|
||||
std::make_tuple(isolate_, module, name, import_specifiers));
|
||||
mods_by_name_[name] = id;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void BuiltinModules(v8::Local<v8::Name> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||
v8::Isolate* isolate = info.GetIsolate();
|
||||
|
@ -275,214 +326,12 @@ void Shared(v8::Local<v8::Name> property,
|
|||
info.GetReturnValue().Set(ab);
|
||||
}
|
||||
|
||||
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();
|
||||
for (auto it = mods_.begin(); it != mods_.end(); it++) {
|
||||
it->second.handle.Reset();
|
||||
}
|
||||
module_map_.clear();
|
||||
for (auto it = module_info_map_.begin(); it != module_info_map_.end(); it++) {
|
||||
it->second.second.Reset();
|
||||
}
|
||||
module_info_map_.clear();
|
||||
}
|
||||
|
||||
void DenoIsolate::RegisterModule(const char* filename,
|
||||
v8::Local<v8::Module> module) {
|
||||
int id = module->GetIdentityHash();
|
||||
|
||||
module_map_.emplace(std::piecewise_construct, std::make_tuple(filename),
|
||||
std::make_tuple(isolate_, module));
|
||||
|
||||
// Identity hash is not necessarily unique
|
||||
// Therefore, we store a persistent handle along with filenames
|
||||
// such that we can compare the identites and select the correct module
|
||||
module_info_map_.emplace(
|
||||
std::piecewise_construct, std::make_tuple(id),
|
||||
std::make_tuple(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 = DenoIsolate::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 = DenoIsolate::FromIsolate(isolate);
|
||||
|
||||
v8::Isolate::Scope isolate_scope(isolate);
|
||||
v8::EscapableHandleScope handle_scope(isolate);
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
v8::String::Utf8Value specifier_utf8val(isolate, specifier);
|
||||
const char* specifier_cstr = ToCString(specifier_utf8val);
|
||||
|
||||
auto builtin_modules = d->GetBuiltinModules();
|
||||
bool has_builtin = builtin_modules->Has(context, specifier).ToChecked();
|
||||
if (has_builtin) {
|
||||
auto val = builtin_modules->Get(context, specifier).ToLocalChecked();
|
||||
CHECK(val->IsObject());
|
||||
auto obj = val->ToObject(isolate);
|
||||
|
||||
// In order to export obj as a module, we must iterate over its properties
|
||||
// and export them each individually.
|
||||
std::string src = "let globalEval = eval\nlet g = globalEval('this');\n";
|
||||
auto names = obj->GetOwnPropertyNames(context).ToLocalChecked();
|
||||
for (uint32_t i = 0; i < names->Length(); i++) {
|
||||
auto name = names->Get(context, i).ToLocalChecked();
|
||||
v8::String::Utf8Value name_utf8val(isolate, name);
|
||||
const char* name_cstr = ToCString(name_utf8val);
|
||||
src.append("export const ");
|
||||
src.append(name_cstr);
|
||||
src.append(" = g.libdeno.builtinModules.");
|
||||
src.append(specifier_cstr);
|
||||
src.append(".");
|
||||
src.append(name_cstr);
|
||||
src.append(";\n");
|
||||
}
|
||||
auto export_str = v8_str(src.c_str(), true);
|
||||
|
||||
auto module =
|
||||
CompileModule(context, specifier_cstr, export_str).ToLocalChecked();
|
||||
auto maybe_ok = module->InstantiateModule(context, ResolveCallback);
|
||||
CHECK(!maybe_ok.IsNothing());
|
||||
|
||||
return handle_scope.Escape(module);
|
||||
}
|
||||
|
||||
int ref_id = referrer->GetIdentityHash();
|
||||
auto range = d->module_info_map_.equal_range(ref_id);
|
||||
std::string referrer_filename;
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
// it->second: <string, v8::Persistent<v8::Module>>
|
||||
// operator== compares value identities stored in the handles
|
||||
// https://denolib.github.io/v8-docs/include_2v8_8h_source.html#l00487
|
||||
// Due to possibilities of identity hash collision, this is necessary
|
||||
if (it->second.second == referrer) {
|
||||
referrer_filename = it->second.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK_NE(referrer_filename.size(), 0);
|
||||
|
||||
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.
|
||||
std::stringstream err_ss;
|
||||
err_ss << "NotFound: Cannot resolve module \"" << specifier_c
|
||||
<< "\" from \"" << referrer_filename << "\"";
|
||||
auto resolve_error = v8_str(err_ss.str().c_str());
|
||||
isolate->ThrowException(resolve_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, true));
|
||||
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, bool resolve_only) {
|
||||
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()) {
|
||||
DCHECK(try_catch.HasCaught());
|
||||
HandleException(context, try_catch.Exception());
|
||||
return false;
|
||||
}
|
||||
|
||||
CHECK_EQ(v8::Module::kInstantiated, module->GetStatus());
|
||||
|
||||
if (resolve_only) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
mods_.clear();
|
||||
mods_by_name_.clear();
|
||||
}
|
||||
|
||||
bool Execute(v8::Local<v8::Context> context, const char* js_filename,
|
||||
|
@ -558,18 +407,35 @@ void MessageCallback(v8::Local<v8::Message> message,
|
|||
HandleExceptionMessage(context, message);
|
||||
}
|
||||
|
||||
void HostInitializeImportMetaObjectCallback(v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Module> module,
|
||||
v8::Local<v8::Object> meta) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
|
||||
v8::Isolate::Scope isolate_scope(isolate);
|
||||
|
||||
CHECK(!module.IsEmpty());
|
||||
|
||||
deno_mod id = module->GetIdentityHash();
|
||||
CHECK_NE(id, 0);
|
||||
|
||||
auto* info = d->GetModuleInfo(id);
|
||||
|
||||
const char* url = info->name.c_str();
|
||||
|
||||
meta->CreateDataProperty(context, v8_str("url"), v8_str(url, true))
|
||||
.ToChecked();
|
||||
}
|
||||
|
||||
void DenoIsolate::AddIsolate(v8::Isolate* isolate) {
|
||||
isolate_ = isolate;
|
||||
// Leaving this code here because it will probably be useful later on, but
|
||||
// disabling it now as I haven't got tests for the desired behavior.
|
||||
// d->isolate->SetAbortOnUncaughtExceptionCallback(AbortOnUncaughtExceptionCallback);
|
||||
// d->isolate->AddMessageListener(MessageCallback2);
|
||||
// d->isolate->SetFatalErrorHandler(FatalErrorCallback2);
|
||||
isolate_->SetCaptureStackTraceForUncaughtExceptions(
|
||||
true, 10, v8::StackTrace::kDetailed);
|
||||
isolate_->SetPromiseRejectCallback(deno::PromiseRejectCallback);
|
||||
isolate_->SetData(0, this);
|
||||
isolate_->AddMessageListener(MessageCallback);
|
||||
isolate->SetHostInitializeImportMetaObjectCallback(
|
||||
HostInitializeImportMetaObjectCallback);
|
||||
}
|
||||
|
||||
} // namespace deno
|
||||
|
|
|
@ -25,26 +25,15 @@ 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.
|
||||
deno_resolve_cb resolve_cb; // Each import calls this.
|
||||
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_config;
|
||||
|
||||
// Create a new deno isolate.
|
||||
|
@ -65,16 +54,6 @@ 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().
|
||||
// If resolve_only is 0, compile and evaluate the module.
|
||||
// If resolve_only is 1, compile and collect dependencies of the module
|
||||
// without running the code.
|
||||
int deno_execute_mod(Deno* d, void* user_data, const char* js_filename,
|
||||
const char* js_source, int resolve_only);
|
||||
|
||||
// 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
|
||||
|
@ -101,6 +80,26 @@ const char* deno_last_exception(Deno* d);
|
|||
|
||||
void deno_terminate_execution(Deno* d);
|
||||
|
||||
// Module API
|
||||
|
||||
typedef int deno_mod;
|
||||
|
||||
// Returns zero on error - check deno_last_exception().
|
||||
deno_mod deno_mod_new(Deno* d, const char* name, const char* source);
|
||||
|
||||
size_t deno_mod_imports_len(Deno* d, deno_mod id);
|
||||
|
||||
// Returned pointer is valid for the lifetime of the Deno isolate "d".
|
||||
const char* deno_mod_imports_get(Deno* d, deno_mod id, size_t index);
|
||||
|
||||
typedef deno_mod (*deno_resolve_cb)(void* user_data, const char* specifier,
|
||||
deno_mod referrer);
|
||||
|
||||
void deno_mod_instantiate(Deno* d, void* user_data, deno_mod id,
|
||||
deno_resolve_cb cb);
|
||||
|
||||
void deno_mod_evaluate(Deno* d, void* user_data, deno_mod id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
@ -5,12 +5,25 @@
|
|||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "deno.h"
|
||||
#include "third_party/v8/include/v8.h"
|
||||
#include "third_party/v8/src/base/logging.h"
|
||||
|
||||
namespace deno {
|
||||
|
||||
struct ModuleInfo {
|
||||
std::string name;
|
||||
v8::Persistent<v8::Module> handle;
|
||||
std::vector<std::string> import_specifiers;
|
||||
|
||||
ModuleInfo(v8::Isolate* isolate, v8::Local<v8::Module> module,
|
||||
const char* name_, std::vector<std::string> import_specifiers_)
|
||||
: name(name_), import_specifiers(import_specifiers_) {
|
||||
handle.Reset(isolate, module);
|
||||
}
|
||||
};
|
||||
|
||||
// deno_s = Wrapped Isolate.
|
||||
class DenoIsolate {
|
||||
public:
|
||||
|
@ -21,9 +34,9 @@ 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) {
|
||||
user_data_(nullptr),
|
||||
resolve_cb_(nullptr) {
|
||||
array_buffer_allocator_ = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
if (config.load_snapshot.data_ptr) {
|
||||
snapshot_.data =
|
||||
|
@ -46,11 +59,22 @@ 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);
|
||||
|
||||
deno_mod RegisterModule(const char* name, const char* source);
|
||||
v8::Local<v8::Object> GetBuiltinModules();
|
||||
void ClearModules();
|
||||
|
||||
v8::Local<v8::Object> GetBuiltinModules();
|
||||
ModuleInfo* GetModuleInfo(deno_mod id) {
|
||||
if (id == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
auto it = mods_.find(id);
|
||||
if (it != mods_.end()) {
|
||||
return &it->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
v8::ArrayBuffer::Allocator* array_buffer_allocator_;
|
||||
|
@ -59,19 +83,13 @@ 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, module (avoid hash collision)
|
||||
std::multimap<int, std::pair<std::string, v8::Persistent<v8::Module>>>
|
||||
module_info_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::Object> builtin_modules_;
|
||||
std::map<deno_mod, ModuleInfo> mods_;
|
||||
std::map<std::string, deno_mod> mods_by_name_;
|
||||
deno_resolve_cb resolve_cb_;
|
||||
|
||||
v8::Persistent<v8::Context> context_;
|
||||
std::map<int32_t, v8::Persistent<v8::Value>> async_data_map_;
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
|
||||
TEST(LibDenoTest, InitializesCorrectly) {
|
||||
EXPECT_NE(snapshot.data_ptr, nullptr);
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, 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, Snapshotter) {
|
||||
Deno* d1 = deno_new(deno_config{1, empty, empty, nullptr, nullptr});
|
||||
Deno* d1 = deno_new(deno_config{1, empty, empty, 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, nullptr});
|
||||
Deno* d2 = deno_new(deno_config{0, test_snapshot, empty, nullptr});
|
||||
EXPECT_TRUE(
|
||||
deno_execute(d2, nullptr, "b.js", "if (a != 3) throw Error('x');"));
|
||||
deno_delete(d2);
|
||||
|
@ -23,14 +23,14 @@ TEST(LibDenoTest, Snapshotter) {
|
|||
}
|
||||
|
||||
TEST(LibDenoTest, CanCallFunction) {
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, 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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr});
|
||||
EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "throw Error()"));
|
||||
deno_delete(d);
|
||||
}
|
||||
|
@ -75,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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
|
||||
EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "RecvReturnEmpty()"));
|
||||
EXPECT_EQ(count, 2);
|
||||
deno_delete(d);
|
||||
|
@ -93,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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
|
||||
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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr});
|
||||
EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "DoubleRecvFails()"));
|
||||
deno_delete(d);
|
||||
}
|
||||
|
@ -134,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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
|
||||
EXPECT_TRUE(deno_execute(d, d, "a.js", "SendRecvSlice()"));
|
||||
EXPECT_EQ(count, 5);
|
||||
deno_delete(d);
|
||||
|
@ -151,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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
|
||||
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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, 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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, 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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, nullptr});
|
||||
EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()"));
|
||||
std::string expected =
|
||||
"{\"message\":\"Uncaught ReferenceError: notdefined is not defined\","
|
||||
|
@ -199,7 +199,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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
|
||||
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
|
||||
|
@ -212,7 +212,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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, empty, recv_cb});
|
||||
EXPECT_EQ(deno_last_exception(d), nullptr);
|
||||
EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "CheckPromiseErrors()"));
|
||||
EXPECT_EQ(deno_last_exception(d), nullptr);
|
||||
|
@ -225,7 +225,7 @@ TEST(LibDenoTest, CheckPromiseErrors) {
|
|||
}
|
||||
|
||||
TEST(LibDenoTest, LastException) {
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, nullptr, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, 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),
|
||||
|
@ -240,7 +240,7 @@ TEST(LibDenoTest, LastException) {
|
|||
}
|
||||
|
||||
TEST(LibDenoTest, EncodeErrorBug) {
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, nullptr, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, nullptr});
|
||||
EXPECT_EQ(deno_last_exception(d), nullptr);
|
||||
EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "eval('a')"));
|
||||
EXPECT_STREQ(
|
||||
|
@ -259,117 +259,10 @@ TEST(LibDenoTest, EncodeErrorBug) {
|
|||
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, nullptr});
|
||||
Deno* d = deno_new(deno_config{0, snapshot, shared, 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, false));
|
||||
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, false));
|
||||
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",
|
||||
0));
|
||||
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, false));
|
||||
deno_delete(d3);
|
||||
|
||||
delete[] test_snapshot.data_ptr;
|
||||
}
|
||||
|
||||
TEST(LibDenoTest, ModuleResolveOnly) {
|
||||
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});
|
||||
// Code should not execute. If executed, the error would be thrown
|
||||
EXPECT_TRUE(deno_execute_mod(d, d, "a.js",
|
||||
"import { retb } from 'b.js'\n"
|
||||
"throw Error('unreachable');",
|
||||
true));
|
||||
EXPECT_EQ(count, 1);
|
||||
deno_delete(d);
|
||||
}
|
||||
|
||||
TEST(LibDenoTest, BuiltinModules) {
|
||||
static int count = 0;
|
||||
auto resolve_cb = [](void* user_data, const char* specifier,
|
||||
const char* referrer) {
|
||||
EXPECT_STREQ(specifier, "b.js");
|
||||
EXPECT_STREQ(referrer, "c.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(
|
||||
d, d, "setup.js", "libdeno.builtinModules['deno'] = { foo: 'bar' }; \n"));
|
||||
EXPECT_EQ(count, 0);
|
||||
EXPECT_TRUE(
|
||||
deno_execute_mod(d, d, "c.js",
|
||||
"import { retb } from 'b.js'\n"
|
||||
"import * as deno from 'deno'\n"
|
||||
"if (retb() != 'b') throw Error('retb');\n"
|
||||
// " libdeno.print('deno ' + JSON.stringify(deno));\n"
|
||||
"if (deno.foo != 'bar') throw Error('foo');\n",
|
||||
false));
|
||||
EXPECT_EQ(count, 1);
|
||||
deno_delete(d);
|
||||
}
|
||||
|
|
206
libdeno/modules.cc
Normal file
206
libdeno/modules.cc
Normal file
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
|
||||
#include "exceptions.h"
|
||||
#include "internal.h"
|
||||
|
||||
using deno::DenoIsolate;
|
||||
using deno::HandleException;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::HandleScope;
|
||||
using v8::Integer;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::Locker;
|
||||
using v8::Module;
|
||||
using v8::Object;
|
||||
using v8::ScriptCompiler;
|
||||
using v8::ScriptOrigin;
|
||||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
std::string BuiltinModuleSrc(Local<Context> context, Local<String> specifier) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
|
||||
v8::Isolate::Scope isolate_scope(isolate);
|
||||
v8::EscapableHandleScope handle_scope(isolate);
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
v8::String::Utf8Value specifier_utf8val(isolate, specifier);
|
||||
const char* specifier_cstr = *specifier_utf8val;
|
||||
|
||||
auto builtin_modules = d->GetBuiltinModules();
|
||||
auto val = builtin_modules->Get(context, specifier).ToLocalChecked();
|
||||
CHECK(val->IsObject());
|
||||
auto obj = val->ToObject(isolate);
|
||||
|
||||
// In order to export obj as a module, we must iterate over its properties
|
||||
// and export them each individually.
|
||||
// TODO(ry) Find a better way to do this.
|
||||
std::string src = "let globalEval = eval\nlet g = globalEval('this');\n";
|
||||
auto names = obj->GetOwnPropertyNames(context).ToLocalChecked();
|
||||
for (uint32_t i = 0; i < names->Length(); i++) {
|
||||
auto name = names->Get(context, i).ToLocalChecked();
|
||||
v8::String::Utf8Value name_utf8val(isolate, name);
|
||||
const char* name_cstr = *name_utf8val;
|
||||
// TODO(ry) use format string.
|
||||
src.append("export const ");
|
||||
src.append(name_cstr);
|
||||
src.append(" = g.libdeno.builtinModules.");
|
||||
src.append(specifier_cstr);
|
||||
src.append(".");
|
||||
src.append(name_cstr);
|
||||
src.append(";\n");
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Module> ResolveCallback(Local<Context> context,
|
||||
Local<String> specifier,
|
||||
Local<Module> referrer) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
v8::Isolate::Scope isolate_scope(isolate);
|
||||
v8::Locker locker(isolate);
|
||||
|
||||
DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
|
||||
|
||||
v8::EscapableHandleScope handle_scope(isolate);
|
||||
|
||||
auto builtin_modules = d->GetBuiltinModules();
|
||||
|
||||
deno_mod referrer_id = referrer->GetIdentityHash();
|
||||
auto* referrer_info = d->GetModuleInfo(referrer_id);
|
||||
CHECK_NE(referrer_info, nullptr);
|
||||
|
||||
for (int i = 0; i < referrer->GetModuleRequestsLength(); i++) {
|
||||
Local<String> req = referrer->GetModuleRequest(i);
|
||||
|
||||
if (req->Equals(context, specifier).ToChecked()) {
|
||||
v8::String::Utf8Value req_utf8(isolate, req);
|
||||
std::string req_str(*req_utf8);
|
||||
|
||||
deno_mod id = 0;
|
||||
{
|
||||
bool has_builtin = builtin_modules->Has(context, specifier).ToChecked();
|
||||
if (has_builtin) {
|
||||
auto it = d->mods_by_name_.find(req_str.c_str());
|
||||
if (it != d->mods_by_name_.end()) {
|
||||
id = it->second;
|
||||
} else {
|
||||
std::string src = BuiltinModuleSrc(context, specifier);
|
||||
id = d->RegisterModule(req_str.c_str(), src.c_str());
|
||||
}
|
||||
} else {
|
||||
id = d->resolve_cb_(d->user_data_, req_str.c_str(), referrer_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: id might be zero, in which case GetModuleInfo will return
|
||||
// nullptr.
|
||||
auto* info = d->GetModuleInfo(id);
|
||||
if (info == nullptr) {
|
||||
char buf[64 * 1024];
|
||||
snprintf(buf, sizeof(buf), "Cannot resolve module \"%s\" from \"%s\"",
|
||||
req_str.c_str(), referrer_info->name.c_str());
|
||||
isolate->ThrowException(deno::v8_str(buf, true));
|
||||
break;
|
||||
} else {
|
||||
Local<Module> child_mod = info->handle.Get(isolate);
|
||||
return handle_scope.Escape(child_mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v8::MaybeLocal<v8::Module>(); // Error
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
deno_mod deno_mod_new(Deno* d_, const char* name_cstr,
|
||||
const char* source_cstr) {
|
||||
auto* d = unwrap(d_);
|
||||
return d->RegisterModule(name_cstr, source_cstr);
|
||||
}
|
||||
|
||||
const char* deno_mod_name(Deno* d_, deno_mod id) {
|
||||
auto* d = unwrap(d_);
|
||||
auto* info = d->GetModuleInfo(id);
|
||||
return info->name.c_str();
|
||||
}
|
||||
|
||||
size_t deno_mod_imports_len(Deno* d_, deno_mod id) {
|
||||
auto* d = unwrap(d_);
|
||||
auto* info = d->GetModuleInfo(id);
|
||||
return info->import_specifiers.size();
|
||||
}
|
||||
|
||||
const char* deno_mod_imports_get(Deno* d_, deno_mod id, size_t index) {
|
||||
auto* d = unwrap(d_);
|
||||
auto* info = d->GetModuleInfo(id);
|
||||
if (info == nullptr || index >= info->import_specifiers.size()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return info->import_specifiers[index].c_str();
|
||||
}
|
||||
}
|
||||
|
||||
void deno_mod_instantiate(Deno* d_, void* user_data, deno_mod id,
|
||||
deno_resolve_cb cb) {
|
||||
auto* d = unwrap(d_);
|
||||
deno::UserDataScope user_data_scope(d, user_data);
|
||||
|
||||
auto* isolate = d->isolate_;
|
||||
v8::Isolate::Scope isolate_scope(isolate);
|
||||
v8::Locker locker(isolate);
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
auto context = d->context_.Get(d->isolate_);
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
v8::TryCatch try_catch(isolate);
|
||||
{
|
||||
CHECK_EQ(nullptr, d->resolve_cb_);
|
||||
d->resolve_cb_ = cb;
|
||||
{
|
||||
auto* info = d->GetModuleInfo(id);
|
||||
if (info == nullptr) {
|
||||
return;
|
||||
}
|
||||
Local<Module> module = info->handle.Get(isolate);
|
||||
if (module->GetStatus() == Module::kErrored) {
|
||||
return;
|
||||
}
|
||||
auto maybe_ok = module->InstantiateModule(context, ResolveCallback);
|
||||
CHECK(maybe_ok.IsJust() || try_catch.HasCaught());
|
||||
}
|
||||
d->resolve_cb_ = nullptr;
|
||||
}
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
HandleException(context, try_catch.Exception());
|
||||
}
|
||||
}
|
||||
|
||||
void deno_mod_evaluate(Deno* d_, void* user_data, deno_mod id) {
|
||||
auto* d = unwrap(d_);
|
||||
deno::UserDataScope user_data_scope(d, user_data);
|
||||
|
||||
auto* isolate = d->isolate_;
|
||||
v8::Isolate::Scope isolate_scope(isolate);
|
||||
v8::Locker locker(isolate);
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
auto context = d->context_.Get(d->isolate_);
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
auto* info = d->GetModuleInfo(id);
|
||||
Local<Module> module = info->handle.Get(isolate);
|
||||
|
||||
CHECK_EQ(Module::kInstantiated, module->GetStatus());
|
||||
|
||||
auto maybe_result = module->Evaluate(context);
|
||||
if (maybe_result.IsEmpty()) {
|
||||
CHECK_EQ(Module::kErrored, module->GetStatus());
|
||||
HandleException(context, module->GetException());
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
268
libdeno/modules_test.cc
Normal file
268
libdeno/modules_test.cc
Normal file
|
@ -0,0 +1,268 @@
|
|||
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
|
||||
#include "test.h"
|
||||
|
||||
static int exec_count = 0;
|
||||
void recv_cb(void* user_data, int req_id, deno_buf buf, deno_buf data_buf) {
|
||||
// We use this to check that scripts have executed.
|
||||
EXPECT_EQ(1u, buf.data_len);
|
||||
EXPECT_EQ(buf.data_ptr[0], 4);
|
||||
exec_count++;
|
||||
}
|
||||
|
||||
TEST(ModulesTest, Resolution) {
|
||||
exec_count = 0; // Reset
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, recv_cb});
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
static deno_mod a = deno_mod_new(d, "a.js",
|
||||
"import { b } from 'b.js'\n"
|
||||
"if (b() != 'b') throw Error();\n"
|
||||
"libdeno.send(new Uint8Array([4]));");
|
||||
EXPECT_NE(a, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
const char* b_src = "export function b() { return 'b' }";
|
||||
static deno_mod b = deno_mod_new(d, "b.js", b_src);
|
||||
EXPECT_NE(b, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
EXPECT_EQ(1u, deno_mod_imports_len(d, a));
|
||||
EXPECT_EQ(0u, deno_mod_imports_len(d, b));
|
||||
|
||||
EXPECT_STREQ("b.js", deno_mod_imports_get(d, a, 0));
|
||||
EXPECT_EQ(nullptr, deno_mod_imports_get(d, a, 1));
|
||||
EXPECT_EQ(nullptr, deno_mod_imports_get(d, b, 0));
|
||||
|
||||
static int resolve_count = 0;
|
||||
auto resolve_cb = [](void* user_data, const char* specifier,
|
||||
deno_mod referrer) {
|
||||
EXPECT_EQ(referrer, a);
|
||||
EXPECT_STREQ(specifier, "b.js");
|
||||
resolve_count++;
|
||||
return b;
|
||||
};
|
||||
|
||||
deno_mod_instantiate(d, d, b, resolve_cb);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(0, resolve_count);
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_instantiate(d, d, a, resolve_cb);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, resolve_count);
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_evaluate(d, d, a);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, resolve_count);
|
||||
EXPECT_EQ(1, exec_count);
|
||||
|
||||
deno_delete(d);
|
||||
}
|
||||
|
||||
TEST(ModulesTest, BuiltinModules) {
|
||||
exec_count = 0; // Reset
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, recv_cb});
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_execute(d, d, "setup.js",
|
||||
"libdeno.builtinModules['deno'] = { foo: 'bar' };");
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
static deno_mod a =
|
||||
deno_mod_new(d, "a.js",
|
||||
"import { b } from 'b.js'\n"
|
||||
"import * as deno from 'deno'\n"
|
||||
"if (b() != 'b') throw Error('b');\n"
|
||||
"if (deno.foo != 'bar') throw Error('foo');\n"
|
||||
"libdeno.send(new Uint8Array([4]));");
|
||||
EXPECT_NE(a, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
const char* b_src = "export function b() { return 'b' }";
|
||||
static deno_mod b = deno_mod_new(d, "b.js", b_src);
|
||||
EXPECT_NE(b, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
EXPECT_EQ(2u, deno_mod_imports_len(d, a));
|
||||
EXPECT_EQ(0u, deno_mod_imports_len(d, b));
|
||||
|
||||
EXPECT_STREQ("b.js", deno_mod_imports_get(d, a, 0));
|
||||
EXPECT_STREQ("deno", deno_mod_imports_get(d, a, 1));
|
||||
EXPECT_EQ(nullptr, deno_mod_imports_get(d, a, 2));
|
||||
EXPECT_EQ(nullptr, deno_mod_imports_get(d, b, 0));
|
||||
|
||||
static int resolve_count = 0;
|
||||
auto resolve_cb = [](void* user_data, const char* specifier,
|
||||
deno_mod referrer) {
|
||||
EXPECT_EQ(referrer, a);
|
||||
EXPECT_STREQ(specifier, "b.js");
|
||||
resolve_count++;
|
||||
return b;
|
||||
};
|
||||
|
||||
deno_mod_instantiate(d, d, b, resolve_cb);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(0, resolve_count);
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_instantiate(d, d, a, resolve_cb);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, resolve_count);
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_evaluate(d, d, a);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, resolve_count);
|
||||
EXPECT_EQ(1, exec_count);
|
||||
|
||||
deno_delete(d);
|
||||
}
|
||||
|
||||
TEST(ModulesTest, BuiltinModules2) {
|
||||
exec_count = 0; // Reset
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, recv_cb});
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_execute(d, d, "setup.js",
|
||||
"libdeno.builtinModules['builtin1'] = { foo: 'bar' }; \n"
|
||||
"libdeno.builtinModules['builtin2'] = { hello: 'world' }; \n");
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
static deno_mod a =
|
||||
deno_mod_new(d, "a.js",
|
||||
"import * as b1 from 'builtin1'\n"
|
||||
"import * as b2 from 'builtin2'\n"
|
||||
"if (b1.foo != 'bar') throw Error('bad1');\n"
|
||||
"if (b2.hello != 'world') throw Error('bad2');\n"
|
||||
"libdeno.send(new Uint8Array([4]));");
|
||||
EXPECT_NE(a, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
EXPECT_EQ(2u, deno_mod_imports_len(d, a));
|
||||
EXPECT_STREQ("builtin1", deno_mod_imports_get(d, a, 0));
|
||||
EXPECT_STREQ("builtin2", deno_mod_imports_get(d, a, 1));
|
||||
|
||||
deno_mod_instantiate(d, d, a, nullptr);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_evaluate(d, d, a);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, exec_count);
|
||||
|
||||
deno_delete(d);
|
||||
}
|
||||
|
||||
TEST(ModulesTest, BuiltinModules3) {
|
||||
exec_count = 0; // Reset
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, recv_cb});
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_execute(d, d, "setup.js",
|
||||
"libdeno.builtinModules['builtin'] = { foo: 'bar' };");
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
static deno_mod a =
|
||||
deno_mod_new(d, "a.js",
|
||||
"import * as b1 from 'builtin'\n"
|
||||
"import * as b2 from 'b.js'\n"
|
||||
"if (b1.foo != 'bar') throw Error('bad1');\n"
|
||||
"if (b2.bar() != 'bar') throw Error('bad2');\n"
|
||||
"libdeno.send(new Uint8Array([4]));");
|
||||
EXPECT_NE(a, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
EXPECT_EQ(2u, deno_mod_imports_len(d, a));
|
||||
EXPECT_STREQ("builtin", deno_mod_imports_get(d, a, 0));
|
||||
EXPECT_STREQ("b.js", deno_mod_imports_get(d, a, 1));
|
||||
|
||||
static deno_mod b = deno_mod_new(d, "b.js",
|
||||
"import { foo } from 'builtin';\n"
|
||||
"export function bar() { return foo }\n");
|
||||
EXPECT_NE(b, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
static int resolve_count = 0;
|
||||
auto resolve_cb = [](void* user_data, const char* specifier,
|
||||
deno_mod referrer) {
|
||||
EXPECT_EQ(referrer, a);
|
||||
EXPECT_STREQ(specifier, "b.js");
|
||||
resolve_count++;
|
||||
return b;
|
||||
};
|
||||
|
||||
deno_mod_instantiate(d, d, a, resolve_cb);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, resolve_count);
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_instantiate(d, d, b, resolve_cb);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, resolve_count);
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_evaluate(d, d, a);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, exec_count);
|
||||
|
||||
deno_delete(d);
|
||||
}
|
||||
|
||||
TEST(ModulesTest, ResolutionError) {
|
||||
exec_count = 0; // Reset
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, recv_cb});
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
static deno_mod a = deno_mod_new(d, "a.js",
|
||||
"import 'bad'\n"
|
||||
"libdeno.send(new Uint8Array([4]));");
|
||||
EXPECT_NE(a, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
EXPECT_EQ(1u, deno_mod_imports_len(d, a));
|
||||
EXPECT_STREQ("bad", deno_mod_imports_get(d, a, 0));
|
||||
|
||||
static int resolve_count = 0;
|
||||
auto resolve_cb = [](void* user_data, const char* specifier,
|
||||
deno_mod referrer) {
|
||||
EXPECT_EQ(referrer, a);
|
||||
EXPECT_STREQ(specifier, "bad");
|
||||
resolve_count++;
|
||||
return 0;
|
||||
};
|
||||
|
||||
deno_mod_instantiate(d, d, a, resolve_cb);
|
||||
EXPECT_NE(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, resolve_count);
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_delete(d);
|
||||
}
|
||||
|
||||
TEST(ModulesTest, ImportMetaUrl) {
|
||||
exec_count = 0; // Reset
|
||||
Deno* d = deno_new(deno_config{0, empty, empty, recv_cb});
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
static deno_mod a =
|
||||
deno_mod_new(d, "a.js",
|
||||
"if ('a.js' != import.meta.url) throw 'hmm'\n"
|
||||
"libdeno.send(new Uint8Array([4]));");
|
||||
EXPECT_NE(a, 0);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
|
||||
deno_mod_instantiate(d, d, a, nullptr);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(0, exec_count);
|
||||
|
||||
deno_mod_evaluate(d, d, a);
|
||||
EXPECT_EQ(nullptr, deno_last_exception(d));
|
||||
EXPECT_EQ(1, exec_count);
|
||||
}
|
|
@ -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, nullptr};
|
||||
deno_config config = {1, deno::empty_buf, deno::empty_buf, nullptr};
|
||||
Deno* d = deno_new(config);
|
||||
|
||||
int r = deno_execute(d, nullptr, js_fn, js_source.c_str());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue