summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libdeno/BUILD.gn2
-rw-r--r--libdeno/api.cc20
-rw-r--r--libdeno/binding.cc288
-rw-r--r--libdeno/deno.h49
-rw-r--r--libdeno/internal.h46
-rw-r--r--libdeno/libdeno_test.cc143
-rw-r--r--libdeno/modules.cc206
-rw-r--r--libdeno/modules_test.cc268
-rw-r--r--libdeno/snapshot_creator.cc2
-rw-r--r--src/deno_dir.rs5
-rw-r--r--src/errors.rs18
-rw-r--r--src/isolate.rs235
-rw-r--r--src/libdeno.rs43
-rw-r--r--src/main.rs6
-rw-r--r--tests/circular1.js2
-rw-r--r--tests/circular1.js.out2
-rw-r--r--tests/circular1.test2
-rw-r--r--tests/circular2.js2
-rw-r--r--tests/error_009_missing_js_module.js.out2
-rw-r--r--tests/imports_meta.js3
-rw-r--r--tests/imports_meta.js.out2
-rw-r--r--tests/imports_meta.test2
-rw-r--r--tests/imports_meta2.js1
23 files changed, 883 insertions, 466 deletions
diff --git a/libdeno/BUILD.gn b/libdeno/BUILD.gn
index 4620dba38..6cea30b2a 100644
--- a/libdeno/BUILD.gn
+++ b/libdeno/BUILD.gn
@@ -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 = [
diff --git a/libdeno/api.cc b/libdeno/api.cc
index 1c62cdd5a..af3c4b215 100644
--- a/libdeno/api.cc
+++ b/libdeno/api.cc
@@ -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);
-}
}
diff --git a/libdeno/binding.cc b/libdeno/binding.cc
index 39007405f..6c636774a 100644
--- a/libdeno/binding.cc
+++ b/libdeno/binding.cc
@@ -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();
- }
- 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;
+ for (auto it = mods_.begin(); it != mods_.end(); it++) {
+ it->second.handle.Reset();
}
-
- 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
diff --git a/libdeno/deno.h b/libdeno/deno.h
index 7568002ae..42df80825 100644
--- a/libdeno/deno.h
+++ b/libdeno/deno.h
@@ -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
diff --git a/libdeno/internal.h b/libdeno/internal.h
index 58dae013c..022fd8f1e 100644
--- a/libdeno/internal.h
+++ b/libdeno/internal.h
@@ -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);
- void ClearModules();
+ deno_mod RegisterModule(const char* name, const char* source);
v8::Local<v8::Object> GetBuiltinModules();
+ void ClearModules();
+
+ 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_;
diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc
index 603674663..5628be00e 100644
--- a/libdeno/libdeno_test.cc
+++ b/libdeno/libdeno_test.cc
@@ -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);
-}
diff --git a/libdeno/modules.cc b/libdeno/modules.cc
new file mode 100644
index 000000000..961686aca
--- /dev/null
+++ b/libdeno/modules.cc
@@ -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"
diff --git a/libdeno/modules_test.cc b/libdeno/modules_test.cc
new file mode 100644
index 000000000..f637ae070
--- /dev/null
+++ b/libdeno/modules_test.cc
@@ -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);
+}
diff --git a/libdeno/snapshot_creator.cc b/libdeno/snapshot_creator.cc
index 6a83f963a..d38b6c075 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, 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());
diff --git a/src/deno_dir.rs b/src/deno_dir.rs
index 5c7e2f9e8..6fbe90f68 100644
--- a/src/deno_dir.rs
+++ b/src/deno_dir.rs
@@ -355,9 +355,8 @@ impl DenoDir {
}
}
- // Prototype: https://github.com/denoland/deno/blob/golang/os.go#L70-L98
- // Returns (module name, local filename)
- fn resolve_module(
+ /// Returns (module name, local filename)
+ pub fn resolve_module(
self: &Self,
specifier: &str,
referrer: &str,
diff --git a/src/errors.rs b/src/errors.rs
index 0855e0079..74502acc5 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+use crate::js_errors::JSError;
pub use crate::msg::ErrorKind;
use crate::resolve_addr::ResolveAddrError;
@@ -178,3 +179,20 @@ pub fn permission_denied() -> DenoError {
String::from("permission denied"),
)
}
+
+pub enum RustOrJsError {
+ Rust(DenoError),
+ Js(JSError),
+}
+
+impl From<DenoError> for RustOrJsError {
+ fn from(e: DenoError) -> Self {
+ RustOrJsError::Rust(e)
+ }
+}
+
+impl From<JSError> for RustOrJsError {
+ fn from(e: JSError) -> Self {
+ RustOrJsError::Js(e)
+ }
+}
diff --git a/src/isolate.rs b/src/isolate.rs
index 7d1fdf71d..39136daff 100644
--- a/src/isolate.rs
+++ b/src/isolate.rs
@@ -3,11 +3,14 @@
// TODO Currently this module uses Tokio, but it would be nice if they were
// decoupled.
+#![allow(dead_code)]
+
use crate::compiler::compile_sync;
use crate::compiler::CodeFetchOutput;
use crate::deno_dir;
use crate::errors::DenoError;
use crate::errors::DenoResult;
+use crate::errors::RustOrJsError;
use crate::flags;
use crate::js_errors::JSError;
use crate::libdeno;
@@ -21,6 +24,7 @@ use libc::c_char;
use libc::c_void;
use std;
use std::cell::Cell;
+use std::collections::HashMap;
use std::env;
use std::ffi::CStr;
use std::ffi::CString;
@@ -48,6 +52,10 @@ pub type Dispatch =
fn(isolate: &Isolate, buf: libdeno::deno_buf, data_buf: libdeno::deno_buf)
-> (bool, Box<Op>);
+pub struct ModuleInfo {
+ name: String,
+}
+
pub struct Isolate {
libdeno_isolate: *const libdeno::isolate,
dispatch: Dispatch,
@@ -55,6 +63,8 @@ pub struct Isolate {
tx: mpsc::Sender<(i32, Buf)>,
ntasks: Cell<i32>,
timeout_due: Cell<Option<Instant>>,
+ pub modules: HashMap<libdeno::deno_mod, ModuleInfo>,
+ pub modules_by_name: HashMap<String, libdeno::deno_mod>,
pub state: Arc<IsolateState>,
}
@@ -155,6 +165,7 @@ pub struct Metrics {
pub bytes_sent_control: AtomicUsize,
pub bytes_sent_data: AtomicUsize,
pub bytes_received: AtomicUsize,
+ pub resolve_count: AtomicUsize,
}
static DENO_INIT: Once = ONCE_INIT;
@@ -173,7 +184,6 @@ impl Isolate {
load_snapshot: snapshot,
shared: libdeno::deno_buf::empty(), // TODO Use for message passing.
recv_cb: pre_dispatch,
- resolve_cb,
};
let libdeno_isolate = unsafe { libdeno::deno_new(config) };
// This channel handles sending async messages back to the runtime.
@@ -186,6 +196,8 @@ impl Isolate {
tx,
ntasks: Cell::new(0),
timeout_due: Cell::new(None),
+ modules: HashMap::new(),
+ modules_by_name: HashMap::new(),
state,
}
}
@@ -254,34 +266,156 @@ impl Isolate {
Ok(())
}
+ pub fn mod_new(
+ &mut self,
+ name: String,
+ source: String,
+ ) -> Result<libdeno::deno_mod, JSError> {
+ let name_ = CString::new(name.clone()).unwrap();
+ let name_ptr = name_.as_ptr() as *const i8;
+
+ let source_ = CString::new(source.clone()).unwrap();
+ let source_ptr = source_.as_ptr() as *const i8;
+
+ let id = unsafe {
+ libdeno::deno_mod_new(self.libdeno_isolate, name_ptr, source_ptr)
+ };
+ if let Some(js_error) = self.last_exception() {
+ assert_eq!(id, 0);
+ return Err(js_error);
+ }
+
+ let name2 = name.clone();
+ self.modules.insert(id, ModuleInfo { name });
+
+ debug!("modules_by_name insert {}", name2);
+ self.modules_by_name.insert(name2, id);
+
+ Ok(id)
+ }
+
+ // TODO(ry) This should be private...
+ pub fn resolve_cb(
+ &self,
+ specifier: &str,
+ referrer: libdeno::deno_mod,
+ ) -> libdeno::deno_mod {
+ self
+ .state
+ .metrics
+ .resolve_count
+ .fetch_add(1, Ordering::Relaxed);
+
+ debug!("resolve_cb {}", specifier);
+
+ let r = self.modules.get(&referrer);
+ if r.is_none() {
+ debug!("cant find referrer {}", referrer);
+ return 0;
+ }
+ let referrer_name = &r.unwrap().name;
+ let r = self.state.dir.resolve_module(specifier, referrer_name);
+ if let Err(err) = r {
+ debug!("potentially swallowed err: {}", err);
+ return 0;
+ }
+ let (name, _local_filename) = r.unwrap();
+
+ if let Some(id) = self.modules_by_name.get(&name) {
+ return *id;
+ } else {
+ return 0;
+ }
+ }
+
+ // TODO(ry) make this return a future.
+ pub fn mod_load_deps(
+ &mut self,
+ id: libdeno::deno_mod,
+ ) -> Result<(), RustOrJsError> {
+ // basically iterate over the imports, start loading them.
+
+ let referrer = self.modules.get(&id).unwrap().clone();
+ let referrer_name = referrer.name.clone();
+ let len =
+ unsafe { libdeno::deno_mod_imports_len(self.libdeno_isolate, id) };
+
+ for i in 0..len {
+ let specifier_ptr =
+ unsafe { libdeno::deno_mod_imports_get(self.libdeno_isolate, id, i) };
+ let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) };
+ let specifier: &str = specifier_c.to_str().unwrap();
+
+ // TODO(ry) This shouldn't be necessary here. builtin modules should be
+ // taken care of at the libdeno level.
+ if specifier == "deno" {
+ continue;
+ }
+
+ let (name, _local_filename) = self
+ .state
+ .dir
+ .resolve_module(specifier, &referrer_name)
+ .map_err(DenoError::from)
+ .map_err(RustOrJsError::from)?;
+
+ debug!("mod_load_deps {} {}", i, name);
+
+ if None == self.modules_by_name.get(&name) {
+ let out =
+ code_fetch_and_maybe_compile(&self.state, specifier, &referrer_name)?;
+ let child_id =
+ self.mod_new(out.module_name.clone(), out.js_source())?;
+
+ self.mod_load_deps(child_id)?;
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn mod_instantiate(&self, id: libdeno::deno_mod) -> Result<(), JSError> {
+ unsafe {
+ libdeno::deno_mod_instantiate(
+ self.libdeno_isolate,
+ self.as_raw_ptr(),
+ id,
+ resolve_cb,
+ )
+ };
+ if let Some(js_error) = self.last_exception() {
+ return Err(js_error);
+ }
+
+ Ok(())
+ }
+
+ pub fn mod_evaluate(&self, id: libdeno::deno_mod) -> Result<(), JSError> {
+ unsafe {
+ libdeno::deno_mod_evaluate(self.libdeno_isolate, self.as_raw_ptr(), id)
+ };
+ if let Some(js_error) = self.last_exception() {
+ return Err(js_error);
+ }
+ Ok(())
+ }
+
/// Executes the provided JavaScript module.
pub fn execute_mod(
- &self,
+ &mut self,
js_filename: &str,
is_prefetch: bool,
) -> Result<(), JSError> {
let out =
code_fetch_and_maybe_compile(&self.state, js_filename, ".").unwrap();
- let filename = CString::new(out.filename.clone()).unwrap();
- let filename_ptr = filename.as_ptr() as *const i8;
+ let id = self.mod_new(out.filename.clone(), out.js_source())?;
- let js_source = CString::new(out.js_source().clone()).unwrap();
- let js_source = CString::new(js_source).unwrap();
- let js_source_ptr = js_source.as_ptr() as *const i8;
+ self.mod_load_deps(id).ok();
- let r = unsafe {
- libdeno::deno_execute_mod(
- self.libdeno_isolate,
- self.as_raw_ptr(),
- filename_ptr,
- js_source_ptr,
- if is_prefetch { 1 } else { 0 },
- )
- };
- if r == 0 {
- let js_error = self.last_exception().unwrap();
- return Err(js_error);
+ self.mod_instantiate(id)?;
+ if !is_prefetch {
+ self.mod_evaluate(id)?;
}
Ok(())
}
@@ -393,40 +527,12 @@ fn code_fetch_and_maybe_compile(
extern "C" fn resolve_cb(
user_data: *mut c_void,
specifier_ptr: *const c_char,
- referrer_ptr: *const c_char,
-) {
+ referrer: libdeno::deno_mod,
+) -> libdeno::deno_mod {
+ let isolate = unsafe { Isolate::from_raw_ptr(user_data) };
let specifier_c: &CStr = unsafe { CStr::from_ptr(specifier_ptr) };
let specifier: &str = specifier_c.to_str().unwrap();
-
- let referrer_c: &CStr = unsafe { CStr::from_ptr(referrer_ptr) };
- let referrer: &str = referrer_c.to_str().unwrap();
-
- debug!("module_resolve callback {} {}", specifier, referrer);
- let isolate = unsafe { Isolate::from_raw_ptr(user_data) };
-
- let maybe_out =
- code_fetch_and_maybe_compile(&isolate.state, specifier, referrer);
-
- if maybe_out.is_err() {
- // Resolution failure
- return;
- }
-
- let out = maybe_out.unwrap();
-
- let filename = CString::new(out.filename.clone()).unwrap();
- let filename_ptr = filename.as_ptr() as *const i8;
-
- let js_source = CString::new(out.js_source().clone()).unwrap();
- let js_source_ptr = js_source.as_ptr() as *const i8;
-
- unsafe {
- libdeno::deno_resolve_ok(
- isolate.libdeno_isolate,
- filename_ptr,
- js_source_ptr,
- )
- };
+ return isolate.resolve_cb(specifier, referrer);
}
// Dereferences the C pointer into the Rust Isolate object.
@@ -673,12 +779,37 @@ mod tests {
let state = Arc::new(IsolateState::new(flags, rest_argv, None));
let snapshot = libdeno::deno_buf::empty();
- let isolate = Isolate::new(snapshot, state, dispatch_sync);
+ let mut isolate = Isolate::new(snapshot, state, dispatch_sync);
tokio_util::init(|| {
isolate
.execute_mod(filename, false)
.expect("execute_mod error");
isolate.event_loop().ok();
});
+
+ let metrics = &isolate.state.metrics;
+ assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 1);
+ }
+
+ #[test]
+ fn execute_mod_circular() {
+ let filename = std::env::current_dir().unwrap().join("tests/circular1.js");
+ let filename = filename.to_str().unwrap();
+
+ let argv = vec![String::from("./deno"), String::from(filename)];
+ let (flags, rest_argv, _) = flags::set_flags(argv).unwrap();
+
+ let state = Arc::new(IsolateState::new(flags, rest_argv, None));
+ let snapshot = libdeno::deno_buf::empty();
+ let mut isolate = Isolate::new(snapshot, state, dispatch_sync);
+ tokio_util::init(|| {
+ isolate
+ .execute_mod(filename, false)
+ .expect("execute_mod error");
+ isolate.event_loop().ok();
+ });
+
+ let metrics = &isolate.state.metrics;
+ assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 2);
}
}
diff --git a/src/libdeno.rs b/src/libdeno.rs
index f8f8a61e0..ac1655bb3 100644
--- a/src/libdeno.rs
+++ b/src/libdeno.rs
@@ -2,6 +2,7 @@
use libc::c_char;
use libc::c_int;
use libc::c_void;
+use libc::size_t;
use std::ops::{Deref, DerefMut};
use std::ptr::null;
@@ -111,11 +112,14 @@ type deno_recv_cb = unsafe extern "C" fn(
);
#[allow(non_camel_case_types)]
+pub type deno_mod = i32;
+
+#[allow(non_camel_case_types)]
type deno_resolve_cb = unsafe extern "C" fn(
user_data: *mut c_void,
specifier: *const c_char,
- referrer: *const c_char,
-);
+ referrer: deno_mod,
+) -> deno_mod;
#[repr(C)]
pub struct deno_config {
@@ -123,7 +127,6 @@ pub struct deno_config {
pub load_snapshot: deno_buf,
pub shared: deno_buf,
pub recv_cb: deno_recv_cb,
- pub resolve_cb: deno_resolve_cb,
}
extern "C" {
@@ -146,16 +149,34 @@ extern "C" {
js_filename: *const c_char,
js_source: *const c_char,
) -> c_int;
- pub fn deno_execute_mod(
+
+ // Modules
+
+ pub fn deno_mod_new(
+ i: *const isolate,
+ name: *const c_char,
+ source: *const c_char,
+ ) -> deno_mod;
+
+ pub fn deno_mod_imports_len(i: *const isolate, id: deno_mod) -> size_t;
+
+ pub fn deno_mod_imports_get(
+ i: *const isolate,
+ id: deno_mod,
+ index: size_t,
+ ) -> *const c_char;
+
+ pub fn deno_mod_instantiate(
i: *const isolate,
user_data: *const c_void,
- js_filename: *const c_char,
- js_source: *const c_char,
- resolve_only: i32,
- ) -> c_int;
- pub fn deno_resolve_ok(
+ id: deno_mod,
+ resolve_cb: deno_resolve_cb,
+ );
+
+ pub fn deno_mod_evaluate(
i: *const isolate,
- js_filename: *const c_char,
- js_source: *const c_char,
+ user_data: *const c_void,
+ id: deno_mod,
);
+
}
diff --git a/src/main.rs b/src/main.rs
index 89c9563e1..1b93ea86d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -84,7 +84,7 @@ fn main() {
let state = Arc::new(isolate::IsolateState::new(flags, rest_argv, None));
let snapshot = snapshot::deno_snapshot();
- let isolate = isolate::Isolate::new(snapshot, state, ops::dispatch);
+ let mut isolate = isolate::Isolate::new(snapshot, state, ops::dispatch);
tokio_util::init(|| {
// Setup runtime.
@@ -94,9 +94,9 @@ fn main() {
// Execute input file.
if isolate.state.argv.len() > 1 {
- let input_filename = &isolate.state.argv[1];
+ let input_filename = isolate.state.argv[1].clone();
isolate
- .execute_mod(input_filename, should_prefetch)
+ .execute_mod(&input_filename, should_prefetch)
.unwrap_or_else(print_err_and_exit);
}
diff --git a/tests/circular1.js b/tests/circular1.js
new file mode 100644
index 000000000..b166f7e5d
--- /dev/null
+++ b/tests/circular1.js
@@ -0,0 +1,2 @@
+import "circular2.js";
+console.log("circular1");
diff --git a/tests/circular1.js.out b/tests/circular1.js.out
new file mode 100644
index 000000000..21f7fd585
--- /dev/null
+++ b/tests/circular1.js.out
@@ -0,0 +1,2 @@
+circular2
+circular1
diff --git a/tests/circular1.test b/tests/circular1.test
new file mode 100644
index 000000000..d86a00c31
--- /dev/null
+++ b/tests/circular1.test
@@ -0,0 +1,2 @@
+args: tests/circular1.js --reload
+output: tests/circular1.js.out
diff --git a/tests/circular2.js b/tests/circular2.js
new file mode 100644
index 000000000..3d3136a0d
--- /dev/null
+++ b/tests/circular2.js
@@ -0,0 +1,2 @@
+import "circular1.js";
+console.log("circular2");
diff --git a/tests/error_009_missing_js_module.js.out b/tests/error_009_missing_js_module.js.out
index e0d8cce2e..ca41435fa 100644
--- a/tests/error_009_missing_js_module.js.out
+++ b/tests/error_009_missing_js_module.js.out
@@ -1 +1 @@
-Uncaught NotFound: Cannot resolve module "./bad-module.js" from "[WILDCARD]/tests/error_009_missing_js_module.js"
+Uncaught Cannot resolve module "./bad-module.js" from "[WILDCARD]error_009_missing_js_module.js"
diff --git a/tests/imports_meta.js b/tests/imports_meta.js
new file mode 100644
index 000000000..3361d1237
--- /dev/null
+++ b/tests/imports_meta.js
@@ -0,0 +1,3 @@
+console.log("imports_meta", import.meta.url);
+
+import "imports_meta2.js";
diff --git a/tests/imports_meta.js.out b/tests/imports_meta.js.out
new file mode 100644
index 000000000..ec6e7eaec
--- /dev/null
+++ b/tests/imports_meta.js.out
@@ -0,0 +1,2 @@
+imports_meta2 [WILDCARD]imports_meta2.js
+imports_meta [WILDCARD]imports_meta.js
diff --git a/tests/imports_meta.test b/tests/imports_meta.test
new file mode 100644
index 000000000..17591ea33
--- /dev/null
+++ b/tests/imports_meta.test
@@ -0,0 +1,2 @@
+args: tests/imports_meta.js --reload
+output: tests/imports_meta.js.out
diff --git a/tests/imports_meta2.js b/tests/imports_meta2.js
new file mode 100644
index 000000000..583861e12
--- /dev/null
+++ b/tests/imports_meta2.js
@@ -0,0 +1 @@
+console.log("imports_meta2", import.meta.url);