#include #include // PATH_MAX #include // 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::HandleScope; using v8::Integer; using v8::IntegrityLevel; using v8::Isolate; using v8::JSON; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Module; using v8::Object; using v8::Promise; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::String; using v8::Value; static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"}; ModuleWrap::ModuleWrap(Environment* env, Local object, Local module, Local url) : BaseObject(env, object) { module_.Reset(env->isolate(), module); url_.Reset(env->isolate(), url); } ModuleWrap::~ModuleWrap() { HandleScope scope(env()->isolate()); Local module = module_.Get(env()->isolate()); auto range = env()->module_map.equal_range(module->GetIdentityHash()); for (auto it = range.first; it != range.second; ++it) { if (it->second == this) { env()->module_map.erase(it); break; } } module_.Reset(); } void ModuleWrap::New(const FunctionCallbackInfo& 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(); if (!args[1]->IsString()) { env->ThrowError("second argument is not a string"); return; } Local url = args[1].As(); Local 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); env->module_map.emplace(mod->GetIdentityHash(), obj); Wrap(that, obj); that->SetIntegrityLevel(ctx, IntegrityLevel::kFrozen); args.GetReturnValue().Set(that); } void ModuleWrap::Link(const FunctionCallbackInfo& 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 resolver_arg = args[0].As(); auto that = args.This(); ModuleWrap* obj = Unwrap(that); auto mod_context = that->CreationContext(); if (obj->linked_) return; obj->linked_ = true; Local mod(obj->module_.Get(iso)); // call the dependency resolve callbacks for (int i = 0; i < mod->GetModuleRequestsLength(); i++) { Local specifier = mod->GetModuleRequest(i); Utf8Value specifier_utf(env->isolate(), specifier); std::string specifier_std(*specifier_utf, specifier_utf.length()); Local argv[] = { specifier }; MaybeLocal maybe_resolve_return_value = resolver_arg->Call(mod_context, that, 1, argv); if (maybe_resolve_return_value.IsEmpty()) { return; } Local resolve_return_value = maybe_resolve_return_value.ToLocalChecked(); if (!resolve_return_value->IsPromise()) { env->ThrowError("linking error, expected resolver to return a promise"); } Local resolve_promise = resolve_return_value.As(); obj->resolve_cache_[specifier_std].Reset(env->isolate(), resolve_promise); } args.GetReturnValue().Set(handle_scope.Escape(that)); } void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { auto iso = args.GetIsolate(); auto that = args.This(); auto ctx = that->CreationContext(); ModuleWrap* obj = Unwrap(that); Local mod = obj->module_.Get(iso); Maybe ok = mod->InstantiateModule(ctx, ModuleWrap::ResolveCallback); // clear resolve cache on instantiate for (auto& entry : obj->resolve_cache_) entry.second.Reset(); obj->resolve_cache_.clear(); if (!ok.FromMaybe(false)) { return; } } void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { auto iso = args.GetIsolate(); auto that = args.This(); auto ctx = that->CreationContext(); ModuleWrap* obj = Unwrap(that); auto result = obj->module_.Get(iso)->Evaluate(ctx); if (result.IsEmpty()) { return; } auto ret = result.ToLocalChecked(); args.GetReturnValue().Set(ret); } MaybeLocal ModuleWrap::ResolveCallback(Local context, Local specifier, Local referrer) { Environment* env = Environment::GetCurrent(context); Isolate* iso = Isolate::GetCurrent(); if (env->module_map.count(referrer->GetIdentityHash()) == 0) { env->ThrowError("linking error, unknown module"); return MaybeLocal(); } ModuleWrap* dependent = nullptr; auto range = env->module_map.equal_range(referrer->GetIdentityHash()); for (auto it = range.first; it != range.second; ++it) { if (it->second->module_ == referrer) { dependent = it->second; break; } } if (dependent == nullptr) { env->ThrowError("linking error, null dep"); return MaybeLocal(); } 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(); } Local 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(); } auto module_object = resolve_promise->Result().As(); if (module_object.IsEmpty() || !module_object->IsObject()) { env->ThrowError("linking error, expected a valid module object from " "resolver"); return MaybeLocal(); } ModuleWrap* mod; ASSIGN_OR_RETURN_UNWRAP(&mod, module_object, MaybeLocal()); 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(base), 4096); uv_fs_read(uv_default_loop(), &req, file, &buf, 1, 0, nullptr); while (req.result > 0) { src += std::string(static_cast(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 = -1; }; 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 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(); 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(); 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); } UNREACHABLE(); } void ModuleWrap::Resolve(const FunctionCallbackInfo& 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 target, Local unused, Local context) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); Local 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)