diff options
author | Bradley Farias <bradley.meck@gmail.com> | 2017-06-05 19:44:56 -0500 |
---|---|---|
committer | Bradley Farias <bradley.meck@gmail.com> | 2017-09-07 15:18:32 -0500 |
commit | c8a389e19f172edbada83f59944cad7cc802d9d5 (patch) | |
tree | 15a8653683a97ff0d6b2e7f08ef8081405700ea3 /src/module_wrap.cc | |
parent | 46133b5beba2c780fb3b9a9d6be610d09f752182 (diff) | |
download | android-node-v8-c8a389e19f172edbada83f59944cad7cc802d9d5.tar.gz android-node-v8-c8a389e19f172edbada83f59944cad7cc802d9d5.tar.bz2 android-node-v8-c8a389e19f172edbada83f59944cad7cc802d9d5.zip |
module: Allow runMain to be ESM
This follows the EPS an allows the node CLI to have ESM as an entry point.
`node ./example.mjs`. A newer V8 is needed for `import()` so that is not
included. `import.meta` is still in specification stage so that also is not
included.
PR-URL: https://github.com/nodejs/node/pull/14369
Author: Bradley Farias <bradley.meck@gmail.com>
Author: Guy Bedford <guybedford@gmail.com>
Author: Jan Krems <jan.krems@groupon.com>
Author: Timothy Gu <timothygu99@gmail.com>
Author: Michaƫl Zasso <targos@protonmail.com>
Author: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Diffstat (limited to 'src/module_wrap.cc')
-rw-r--r-- | src/module_wrap.cc | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/src/module_wrap.cc b/src/module_wrap.cc new file mode 100644 index 0000000000..05bbe04ef2 --- /dev/null +++ b/src/module_wrap.cc @@ -0,0 +1,531 @@ +#include <algorithm> +#include <limits.h> // PATH_MAX +#include <sys/stat.h> // S_IFDIR +#include "module_wrap.h" + +#include "env.h" +#include "node_url.h" +#include "util.h" +#include "util-inl.h" + +namespace node { +namespace loader { + +using node::url::URL; +using node::url::URL_FLAGS_FAILED; +using v8::Context; +using v8::EscapableHandleScope; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Integer; +using v8::IntegrityLevel; +using v8::Isolate; +using v8::JSON; +using v8::Local; +using v8::MaybeLocal; +using v8::Module; +using v8::Object; +using v8::Persistent; +using v8::Promise; +using v8::ScriptCompiler; +using v8::ScriptOrigin; +using v8::String; +using v8::Value; + +static const char* EXTENSIONS[] = {".mjs", ".js", ".json", ".node"}; +std::map<int, std::vector<ModuleWrap*>*> ModuleWrap::module_map_; + +ModuleWrap::ModuleWrap(Environment* env, + Local<Object> object, + Local<Module> module, + Local<String> url) : BaseObject(env, object) { + Isolate* iso = Isolate::GetCurrent(); + module_.Reset(iso, module); + url_.Reset(iso, url); +} + +ModuleWrap::~ModuleWrap() { + Local<Module> module = module_.Get(Isolate::GetCurrent()); + std::vector<ModuleWrap*>* same_hash = module_map_[module->GetIdentityHash()]; + auto it = std::find(same_hash->begin(), same_hash->end(), this); + + if (it != same_hash->end()) { + same_hash->erase(it); + } + + module_.Reset(); +} + +void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + Isolate* iso = args.GetIsolate(); + + if (!args.IsConstructCall()) { + env->ThrowError("constructor must be called using new"); + return; + } + + if (args.Length() != 2) { + env->ThrowError("constructor must have exactly 2 arguments " + "(string, string)"); + return; + } + + if (!args[0]->IsString()) { + env->ThrowError("first argument is not a string"); + return; + } + + auto source_text = args[0].As<String>(); + + if (!args[1]->IsString()) { + env->ThrowError("second argument is not a string"); + return; + } + + Local<String> url = args[1].As<String>(); + + Local<Module> mod; + + // compile + { + ScriptOrigin origin(url, + Integer::New(iso, 0), + Integer::New(iso, 0), + False(iso), + Integer::New(iso, 0), + FIXED_ONE_BYTE_STRING(iso, ""), + False(iso), + False(iso), + True(iso)); + ScriptCompiler::Source source(source_text, origin); + auto maybe_mod = ScriptCompiler::CompileModule(iso, &source); + if (maybe_mod.IsEmpty()) { + return; + } + mod = maybe_mod.ToLocalChecked(); + } + + auto that = args.This(); + auto ctx = that->CreationContext(); + auto url_str = FIXED_ONE_BYTE_STRING(iso, "url"); + + if (!that->Set(ctx, url_str, url).FromMaybe(false)) { + return; + } + + ModuleWrap* obj = + new ModuleWrap(Environment::GetCurrent(ctx), that, mod, url); + + if (ModuleWrap::module_map_.count(mod->GetIdentityHash()) == 0) { + ModuleWrap::module_map_[mod->GetIdentityHash()] = + new std::vector<ModuleWrap*>(); + } + + ModuleWrap::module_map_[mod->GetIdentityHash()]->push_back(obj); + Wrap(that, obj); + + that->SetIntegrityLevel(ctx, IntegrityLevel::kFrozen); + args.GetReturnValue().Set(that); +} + +void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* iso = args.GetIsolate(); + EscapableHandleScope handle_scope(iso); + if (!args[0]->IsFunction()) { + env->ThrowError("first argument is not a function"); + return; + } + + Local<Function> resolver_arg = args[0].As<Function>(); + + auto that = args.This(); + ModuleWrap* obj = Unwrap<ModuleWrap>(that); + auto mod_context = that->CreationContext(); + if (obj->linked_) return; + obj->linked_ = true; + Local<Module> mod(obj->module_.Get(iso)); + + // call the dependency resolve callbacks + for (int i = 0; i < mod->GetModuleRequestsLength(); i++) { + Local<String> specifier = mod->GetModuleRequest(i); + Utf8Value specifier_utf(env->isolate(), specifier); + std::string specifier_std(*specifier_utf, specifier_utf.length()); + + Local<Value> argv[] = { + specifier + }; + + MaybeLocal<Value> maybe_resolve_return_value = + resolver_arg->Call(mod_context, that, 1, argv); + if (maybe_resolve_return_value.IsEmpty()) { + return; + } + Local<Value> resolve_return_value = + maybe_resolve_return_value.ToLocalChecked(); + if (!resolve_return_value->IsPromise()) { + env->ThrowError("linking error, expected resolver to return a promise"); + } + Local<Promise> resolve_promise = resolve_return_value.As<Promise>(); + obj->resolve_cache_[specifier_std] = new Persistent<Promise>(); + obj->resolve_cache_[specifier_std]->Reset(iso, resolve_promise); + } + + args.GetReturnValue().Set(handle_scope.Escape(that)); +} + +void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) { + auto iso = args.GetIsolate(); + auto that = args.This(); + auto ctx = that->CreationContext(); + + ModuleWrap* obj = Unwrap<ModuleWrap>(that); + Local<Module> mod = obj->module_.Get(iso); + bool ok = mod->Instantiate(ctx, ModuleWrap::ResolveCallback); + + // clear resolve cache on instantiate + obj->resolve_cache_.clear(); + + if (!ok) { + return; + } +} + +void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) { + auto iso = args.GetIsolate(); + auto that = args.This(); + auto ctx = that->CreationContext(); + ModuleWrap* obj = Unwrap<ModuleWrap>(that); + auto result = obj->module_.Get(iso)->Evaluate(ctx); + + if (result.IsEmpty()) { + return; + } + + auto ret = result.ToLocalChecked(); + args.GetReturnValue().Set(ret); +} + +MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context, + Local<String> specifier, + Local<Module> referrer) { + Environment* env = Environment::GetCurrent(context); + Isolate* iso = Isolate::GetCurrent(); + if (ModuleWrap::module_map_.count(referrer->GetIdentityHash()) == 0) { + env->ThrowError("linking error, unknown module"); + return MaybeLocal<Module>(); + } + + std::vector<ModuleWrap*>* possible_deps = + ModuleWrap::module_map_[referrer->GetIdentityHash()]; + ModuleWrap* dependent = nullptr; + + for (auto possible_dep : *possible_deps) { + if (possible_dep->module_ == referrer) { + dependent = possible_dep; + } + } + + if (dependent == nullptr) { + env->ThrowError("linking error, null dep"); + return MaybeLocal<Module>(); + } + + Utf8Value specifier_utf(env->isolate(), specifier); + std::string specifier_std(*specifier_utf, specifier_utf.length()); + + if (dependent->resolve_cache_.count(specifier_std) != 1) { + env->ThrowError("linking error, not in local cache"); + return MaybeLocal<Module>(); + } + + Local<Promise> resolve_promise = + dependent->resolve_cache_[specifier_std]->Get(iso); + + if (resolve_promise->State() != Promise::kFulfilled) { + env->ThrowError("linking error, dependency promises must be resolved on " + "instantiate"); + return MaybeLocal<Module>(); + } + + auto module_object = resolve_promise->Result().As<Object>(); + if (module_object.IsEmpty() || !module_object->IsObject()) { + env->ThrowError("linking error, expected a valid module object from " + "resolver"); + return MaybeLocal<Module>(); + } + + ModuleWrap* mod; + ASSIGN_OR_RETURN_UNWRAP(&mod, module_object, MaybeLocal<Module>()); + return mod->module_.Get(env->isolate()); +} + +namespace { + +URL __init_cwd() { + std::string specifier = "file://"; +#ifdef _WIN32 + // MAX_PATH is in characters, not bytes. Make sure we have enough headroom. + char buf[MAX_PATH * 4]; +#else + char buf[PATH_MAX]; +#endif + + size_t cwd_len = sizeof(buf); + int err = uv_cwd(buf, &cwd_len); + if (err) { + return URL(""); + } + specifier += buf; + specifier += "/"; + return URL(specifier); +} +static URL INITIAL_CWD(__init_cwd()); +inline bool is_relative_or_absolute_path(std::string specifier) { + auto len = specifier.length(); + if (len <= 0) { + return false; + } else if (specifier[0] == '/') { + return true; + } else if (specifier[0] == '.') { + if (len == 1 || specifier[1] == '/') { + return true; + } else if (specifier[1] == '.') { + if (len == 2 || specifier[2] == '/') { + return true; + } + } + } + return false; +} +struct read_result { + bool had_error = false; + std::string source; +} read_result; +inline const struct read_result read_file(uv_file file) { + struct read_result ret; + std::string src; + uv_fs_t req; + void* base = malloc(4096); + if (base == nullptr) { + ret.had_error = true; + return ret; + } + uv_buf_t buf = uv_buf_init(static_cast<char*>(base), 4096); + uv_fs_read(uv_default_loop(), &req, file, &buf, 1, 0, nullptr); + while (req.result > 0) { + src += std::string(static_cast<const char*>(buf.base), req.result); + uv_fs_read(uv_default_loop(), &req, file, &buf, 1, src.length(), nullptr); + } + ret.source = src; + return ret; +} +struct file_check { + bool failed = true; + uv_file file; +} file_check; +inline const struct file_check check_file(URL search, + bool close = false, + bool allow_dir = false) { + struct file_check ret; + uv_fs_t fs_req; + std::string path = search.ToFilePath(); + if (path.empty()) { + return ret; + } + uv_fs_open(nullptr, &fs_req, path.c_str(), O_RDONLY, 0, nullptr); + auto fd = fs_req.result; + if (fd < 0) { + return ret; + } + if (!allow_dir) { + uv_fs_fstat(nullptr, &fs_req, fd, nullptr); + if (fs_req.statbuf.st_mode & S_IFDIR) { + uv_fs_close(nullptr, &fs_req, fd, nullptr); + return ret; + } + } + ret.failed = false; + ret.file = fd; + if (close) uv_fs_close(nullptr, &fs_req, fd, nullptr); + return ret; +} +URL resolve_extensions(URL search, bool check_exact = true) { + if (check_exact) { + auto check = check_file(search, true); + if (!check.failed) { + return search; + } + } + for (auto extension : EXTENSIONS) { + URL guess(search.path() + extension, &search); + auto check = check_file(guess, true); + if (!check.failed) { + return guess; + } + } + return URL(""); +} +inline URL resolve_index(URL search) { + return resolve_extensions(URL("index", &search), false); +} +URL resolve_main(URL search) { + URL pkg("package.json", &search); + auto check = check_file(pkg); + if (!check.failed) { + auto iso = Isolate::GetCurrent(); + auto ctx = iso->GetCurrentContext(); + auto read = read_file(check.file); + uv_fs_t fs_req; + // if we fail to close :-/ + uv_fs_close(nullptr, &fs_req, check.file, nullptr); + if (read.had_error) return URL(""); + std::string pkg_src = read.source; + Local<String> src = + String::NewFromUtf8(iso, pkg_src.c_str(), + String::kNormalString, pkg_src.length()); + if (src.IsEmpty()) return URL(""); + auto maybe_pkg_json = JSON::Parse(ctx, src); + if (maybe_pkg_json.IsEmpty()) return URL(""); + auto pkg_json_obj = maybe_pkg_json.ToLocalChecked().As<Object>(); + if (!pkg_json_obj->IsObject()) return URL(""); + auto maybe_pkg_main = pkg_json_obj->Get( + ctx, FIXED_ONE_BYTE_STRING(iso, "main")); + if (maybe_pkg_main.IsEmpty()) return URL(""); + auto pkg_main_str = maybe_pkg_main.ToLocalChecked().As<String>(); + if (!pkg_main_str->IsString()) return URL(""); + Utf8Value main_utf8(iso, pkg_main_str); + std::string main_std(*main_utf8, main_utf8.length()); + if (!is_relative_or_absolute_path(main_std)) { + main_std.insert(0, "./"); + } + return Resolve(main_std, &search); + } + return URL(""); +} +URL resolve_module(std::string specifier, URL* base) { + URL parent(".", base); + URL dir(""); + do { + dir = parent; + auto check = Resolve("./node_modules/" + specifier, &dir, true); + if (!(check.flags() & URL_FLAGS_FAILED)) { + const auto limit = specifier.find('/'); + const auto spec_len = limit == std::string::npos ? + specifier.length() : + limit + 1; + std::string chroot = + dir.path() + "node_modules/" + specifier.substr(0, spec_len); + if (check.path().substr(0, chroot.length()) != chroot) { + return URL(""); + } + return check; + } else { + // TODO(bmeck) PREVENT FALLTHROUGH + } + parent = URL("..", &dir); + } while (parent.path() != dir.path()); + return URL(""); +} + +URL resolve_directory(URL search, bool read_pkg_json) { + if (read_pkg_json) { + auto main = resolve_main(search); + if (!(main.flags() & URL_FLAGS_FAILED)) return main; + } + return resolve_index(search); +} + +} // anonymous namespace + + +URL Resolve(std::string specifier, URL* base, bool read_pkg_json) { + URL pure_url(specifier); + if (!(pure_url.flags() & URL_FLAGS_FAILED)) { + return pure_url; + } + if (specifier.length() == 0) { + return URL(""); + } + if (is_relative_or_absolute_path(specifier)) { + URL resolved(specifier, base); + auto file = resolve_extensions(resolved); + if (!(file.flags() & URL_FLAGS_FAILED)) return file; + if (specifier.back() != '/') { + resolved = URL(specifier + "/", base); + } + return resolve_directory(resolved, read_pkg_json); + } else { + return resolve_module(specifier, base); + } + return URL(""); +} + +void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.IsConstructCall()) { + env->ThrowError("resolve() must not be called as a constructor"); + return; + } + if (args.Length() != 2) { + env->ThrowError("resolve must have exactly 2 arguments (string, string)"); + return; + } + + if (!args[0]->IsString()) { + env->ThrowError("first argument is not a string"); + return; + } + Utf8Value specifier_utf(env->isolate(), args[0]); + + if (!args[1]->IsString()) { + env->ThrowError("second argument is not a string"); + return; + } + Utf8Value url_utf(env->isolate(), args[1]); + URL url(*url_utf, url_utf.length()); + + if (url.flags() & URL_FLAGS_FAILED) { + env->ThrowError("second argument is not a URL string"); + return; + } + + URL result = node::loader::Resolve(*specifier_utf, &url, true); + if (result.flags() & URL_FLAGS_FAILED) { + std::string msg = "module "; + msg += *specifier_utf; + msg += " not found"; + env->ThrowError(msg.c_str()); + return; + } + + args.GetReturnValue().Set(result.ToObject(env)); +} + +void ModuleWrap::Initialize(Local<Object> target, + Local<Value> unused, + Local<Context> context) { + Environment* env = Environment::GetCurrent(context); + Isolate* isolate = env->isolate(); + + Local<FunctionTemplate> tpl = env->NewFunctionTemplate(New); + tpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap")); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + env->SetProtoMethod(tpl, "link", Link); + env->SetProtoMethod(tpl, "instantiate", Instantiate); + env->SetProtoMethod(tpl, "evaluate", Evaluate); + + target->Set(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), tpl->GetFunction()); + env->SetMethod(target, "resolve", node::loader::ModuleWrap::Resolve); +} + +} // namespace loader +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(module_wrap, + node::loader::ModuleWrap::Initialize) |