diff options
Diffstat (limited to 'src/module_wrap.cc')
-rw-r--r-- | src/module_wrap.cc | 490 |
1 files changed, 354 insertions, 136 deletions
diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 56149d0cc7..622deed45f 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -29,7 +29,6 @@ using v8::HandleScope; using v8::Integer; using v8::IntegrityLevel; using v8::Isolate; -using v8::JSON; using v8::Just; using v8::Local; using v8::Maybe; @@ -46,7 +45,13 @@ using v8::String; using v8::Undefined; using v8::Value; -static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"}; +static const char* const EXTENSIONS[] = { + ".mjs", + ".cjs", + ".js", + ".json", + ".node" +}; ModuleWrap::ModuleWrap(Environment* env, Local<Object> object, @@ -471,100 +476,233 @@ std::string ReadFile(uv_file file) { return contents; } -enum CheckFileOptions { - LEAVE_OPEN_AFTER_CHECK, - CLOSE_AFTER_CHECK +enum DescriptorType { + FILE, + DIRECTORY, + NONE }; -Maybe<uv_file> CheckFile(const std::string& path, - CheckFileOptions opt = CLOSE_AFTER_CHECK) { +// When DescriptorType cache is added, this can also return +// Nothing for the "null" cache entries. +inline Maybe<uv_file> OpenDescriptor(const std::string& path) { uv_fs_t fs_req; - if (path.empty()) { - return Nothing<uv_file>(); - } - uv_file fd = uv_fs_open(nullptr, &fs_req, path.c_str(), O_RDONLY, 0, nullptr); uv_fs_req_cleanup(&fs_req); + if (fd < 0) return Nothing<uv_file>(); + return Just(fd); +} - if (fd < 0) { - return Nothing<uv_file>(); - } - - uv_fs_fstat(nullptr, &fs_req, fd, nullptr); - uint64_t is_directory = fs_req.statbuf.st_mode & S_IFDIR; +inline void CloseDescriptor(uv_file fd) { + uv_fs_t fs_req; + uv_fs_close(nullptr, &fs_req, fd, nullptr); uv_fs_req_cleanup(&fs_req); +} - if (is_directory) { - CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, fd, nullptr)); +inline DescriptorType CheckDescriptorAtFile(uv_file fd) { + uv_fs_t fs_req; + int rc = uv_fs_fstat(nullptr, &fs_req, fd, nullptr); + if (rc == 0) { + uint64_t is_directory = fs_req.statbuf.st_mode & S_IFDIR; uv_fs_req_cleanup(&fs_req); - return Nothing<uv_file>(); + return is_directory ? DIRECTORY : FILE; } + uv_fs_req_cleanup(&fs_req); + return NONE; +} - if (opt == CLOSE_AFTER_CHECK) { - CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, fd, nullptr)); - uv_fs_req_cleanup(&fs_req); - } +// TODO(@guybedford): Add a DescriptorType cache layer here. +// Should be directory based -> if path/to/dir doesn't exist +// then the cache should early-fail any path/to/dir/file check. +DescriptorType CheckDescriptorAtPath(const std::string& path) { + Maybe<uv_file> fd = OpenDescriptor(path); + if (fd.IsNothing()) return NONE; + DescriptorType type = CheckDescriptorAtFile(fd.FromJust()); + CloseDescriptor(fd.FromJust()); + return type; +} - return Just(fd); +Maybe<std::string> ReadIfFile(const std::string& path) { + Maybe<uv_file> fd = OpenDescriptor(path); + if (fd.IsNothing()) return Nothing<std::string>(); + DescriptorType type = CheckDescriptorAtFile(fd.FromJust()); + if (type != FILE) return Nothing<std::string>(); + std::string source = ReadFile(fd.FromJust()); + CloseDescriptor(fd.FromJust()); + return Just(source); } using Exists = PackageConfig::Exists; using IsValid = PackageConfig::IsValid; using HasMain = PackageConfig::HasMain; +using PackageType = PackageConfig::PackageType; -const PackageConfig& GetPackageConfig(Environment* env, - const std::string& path) { +Maybe<const PackageConfig*> GetPackageConfig(Environment* env, + const std::string& path, + const URL& base) { auto existing = env->package_json_cache.find(path); if (existing != env->package_json_cache.end()) { - return existing->second; + const PackageConfig* pcfg = &existing->second; + if (pcfg->is_valid == IsValid::No) { + std::string msg = "Invalid JSON in '" + path + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); + return Nothing<const PackageConfig*>(); + } + return Just(pcfg); } - Maybe<uv_file> check = CheckFile(path, LEAVE_OPEN_AFTER_CHECK); - if (check.IsNothing()) { + + Maybe<std::string> source = ReadIfFile(path); + + if (source.IsNothing()) { auto entry = env->package_json_cache.emplace(path, - PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "" }); - return entry.first->second; + PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", + PackageType::None }); + return Just(&entry.first->second); } + std::string pkg_src = source.FromJust(); + Isolate* isolate = env->isolate(); v8::HandleScope handle_scope(isolate); - std::string pkg_src = ReadFile(check.FromJust()); - uv_fs_t fs_req; - CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, check.FromJust(), nullptr)); - uv_fs_req_cleanup(&fs_req); - - Local<String> src; - if (!String::NewFromUtf8(isolate, - pkg_src.c_str(), - v8::NewStringType::kNormal, - pkg_src.length()).ToLocal(&src)) { - auto entry = env->package_json_cache.emplace(path, - PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "" }); - return entry.first->second; - } - - Local<Value> pkg_json_v; Local<Object> pkg_json; - - if (!JSON::Parse(env->context(), src).ToLocal(&pkg_json_v) || - !pkg_json_v->ToObject(env->context()).ToLocal(&pkg_json)) { - auto entry = env->package_json_cache.emplace(path, - PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "" }); - return entry.first->second; + { + Local<Value> src; + Local<Value> pkg_json_v; + Local<Context> context = env->context(); + + if (!ToV8Value(context, pkg_src).ToLocal(&src) || + !v8::JSON::Parse(context, src.As<String>()).ToLocal(&pkg_json_v) || + !pkg_json_v->ToObject(context).ToLocal(&pkg_json)) { + env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "", + PackageType::None }); + std::string msg = "Invalid JSON in '" + path + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); + return Nothing<const PackageConfig*>(); + } } Local<Value> pkg_main; HasMain has_main = HasMain::No; std::string main_std; if (pkg_json->Get(env->context(), env->main_string()).ToLocal(&pkg_main)) { - has_main = HasMain::Yes; + if (pkg_main->IsString()) { + has_main = HasMain::Yes; + } Utf8Value main_utf8(isolate, pkg_main); main_std.assign(std::string(*main_utf8, main_utf8.length())); } + PackageType pkg_type = PackageType::None; + Local<Value> type_v; + if (pkg_json->Get(env->context(), env->type_string()).ToLocal(&type_v)) { + if (type_v->StrictEquals(env->module_string())) { + pkg_type = PackageType::Module; + } else if (type_v->StrictEquals(env->commonjs_string())) { + pkg_type = PackageType::CommonJS; + } + // ignore unknown types for forwards compatibility + } + + Local<Value> exports_v; + if (pkg_json->Get(env->context(), + env->exports_string()).ToLocal(&exports_v) && + (exports_v->IsObject() || exports_v->IsString() || + exports_v->IsBoolean())) { + Persistent<Value> exports; + exports.Reset(env->isolate(), exports_v); + + auto entry = env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + pkg_type }); + return Just(&entry.first->second); + } + auto entry = env->package_json_cache.emplace(path, - PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std }); - return entry.first->second; + PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + pkg_type }); + return Just(&entry.first->second); +} + +Maybe<const PackageConfig*> GetPackageScopeConfig(Environment* env, + const URL& resolved, + const URL& base) { + URL pjson_url("./package.json", &resolved); + while (true) { + Maybe<const PackageConfig*> pkg_cfg = + GetPackageConfig(env, pjson_url.ToFilePath(), base); + if (pkg_cfg.IsNothing()) return pkg_cfg; + if (pkg_cfg.FromJust()->exists == Exists::Yes) return pkg_cfg; + + URL last_pjson_url = pjson_url; + pjson_url = URL("../package.json", pjson_url); + + // Terminates at root where ../package.json equals ../../package.json + // (can't just check "/package.json" for Windows support). + if (pjson_url.path() == last_pjson_url.path()) { + auto entry = env->package_json_cache.emplace(pjson_url.ToFilePath(), + PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", + PackageType::None }); + const PackageConfig* pcfg = &entry.first->second; + return Just(pcfg); + } + } +} + +/* + * Legacy CommonJS main resolution: + * 1. let M = pkg_url + (json main field) + * 2. TRY(M, M.js, M.json, M.node) + * 3. TRY(M/index.js, M/index.json, M/index.node) + * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) + * 5. NOT_FOUND + */ +inline bool FileExists(const URL& url) { + return CheckDescriptorAtPath(url.ToFilePath()) == FILE; +} +Maybe<URL> LegacyMainResolve(const URL& pjson_url, + const PackageConfig& pcfg) { + URL guess; + if (pcfg.has_main == HasMain::Yes) { + // Note: fs check redundances will be handled by Descriptor cache here. + if (FileExists(guess = URL("./" + pcfg.main, pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + ".js", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + ".json", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + ".node", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + "/index.js", pjson_url))) { + return Just(guess); + } + // Such stat. + if (FileExists(guess = URL("./" + pcfg.main + "/index.json", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./" + pcfg.main + "/index.node", pjson_url))) { + return Just(guess); + } + // Fallthrough. + } + if (FileExists(guess = URL("./index.js", pjson_url))) { + return Just(guess); + } + // So fs. + if (FileExists(guess = URL("./index.json", pjson_url))) { + return Just(guess); + } + if (FileExists(guess = URL("./index.node", pjson_url))) { + return Just(guess); + } + // Not found. + return Nothing<URL>(); } enum ResolveExtensionsOptions { @@ -575,17 +713,14 @@ enum ResolveExtensionsOptions { template <ResolveExtensionsOptions options> Maybe<URL> ResolveExtensions(const URL& search) { if (options == TRY_EXACT_NAME) { - std::string filePath = search.ToFilePath(); - Maybe<uv_file> check = CheckFile(filePath); - if (!check.IsNothing()) { + if (FileExists(search)) { return Just(search); } } for (const char* extension : EXTENSIONS) { URL guess(search.path() + extension, &search); - Maybe<uv_file> check = CheckFile(guess.ToFilePath()); - if (!check.IsNothing()) { + if (FileExists(guess)) { return Just(guess); } } @@ -597,93 +732,150 @@ inline Maybe<URL> ResolveIndex(const URL& search) { return ResolveExtensions<ONLY_VIA_EXTENSIONS>(URL("index", search)); } -Maybe<URL> ResolveMain(Environment* env, const URL& search) { - URL pkg("package.json", &search); - - const PackageConfig& pjson = - GetPackageConfig(env, pkg.ToFilePath()); - // Note invalid package.json should throw in resolver - // currently we silently ignore which is incorrect - if (pjson.exists == Exists::No || - pjson.is_valid == IsValid::No || - pjson.has_main == HasMain::No) { +Maybe<URL> FinalizeResolution(Environment* env, + const URL& resolved, + const URL& base) { + if (env->options()->es_module_specifier_resolution == "node") { + Maybe<URL> file = ResolveExtensions<TRY_EXACT_NAME>(resolved); + if (!file.IsNothing()) { + return file; + } + if (resolved.path().back() != '/') { + file = ResolveIndex(URL(resolved.path() + "/", &base)); + } else { + file = ResolveIndex(resolved); + } + if (!file.IsNothing()) { + return file; + } + std::string msg = "Cannot find module '" + resolved.path() + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); return Nothing<URL>(); } - if (!ShouldBeTreatedAsRelativeOrAbsolutePath(pjson.main)) { - return Resolve(env, "./" + pjson.main, search, IgnoreMain); + + const std::string& path = resolved.ToFilePath(); + if (CheckDescriptorAtPath(path) != FILE) { + std::string msg = "Cannot find module '" + path + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + return Nothing<URL>(); } - return Resolve(env, pjson.main, search, IgnoreMain); + + return Just(resolved); } -Maybe<URL> ResolveModule(Environment* env, - const std::string& specifier, - const URL& base) { - URL parent(".", base); - URL dir(""); - do { - dir = parent; - Maybe<URL> check = - Resolve(env, "./node_modules/" + specifier, dir, CheckMain); - if (!check.IsNothing()) { - const size_t limit = specifier.find('/'); - const size_t spec_len = - limit == std::string::npos ? specifier.length() : - limit + 1; - std::string chroot = - dir.path() + "node_modules/" + specifier.substr(0, spec_len); - if (check.FromJust().path().substr(0, chroot.length()) != chroot) { - return Nothing<URL>(); +Maybe<URL> PackageMainResolve(Environment* env, + const URL& pjson_url, + const PackageConfig& pcfg, + const URL& base) { + if (pcfg.exists == Exists::Yes) { + if (pcfg.has_main == HasMain::Yes) { + URL resolved(pcfg.main, pjson_url); + const std::string& path = resolved.ToFilePath(); + if (CheckDescriptorAtPath(path) == FILE) { + return Just(resolved); + } + } + if (env->options()->es_module_specifier_resolution == "node") { + if (pcfg.has_main == HasMain::Yes) { + return FinalizeResolution(env, URL(pcfg.main, pjson_url), base); + } else { + return FinalizeResolution(env, URL("index", pjson_url), base); + } + } + if (pcfg.type != PackageType::Module) { + Maybe<URL> resolved = LegacyMainResolve(pjson_url, pcfg); + if (!resolved.IsNothing()) { + return resolved; } - return check; - } else { - // TODO(bmeck) PREVENT FALLTHROUGH } - parent = URL("..", &dir); - } while (parent.path() != dir.path()); + } + std::string msg = "Cannot find main entry point for '" + + URL(".", pjson_url).ToFilePath() + "' imported from " + + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); return Nothing<URL>(); } -Maybe<URL> ResolveDirectory(Environment* env, - const URL& search, - PackageMainCheck check_pjson_main) { - if (check_pjson_main) { - Maybe<URL> main = ResolveMain(env, search); - if (!main.IsNothing()) - return main; +Maybe<URL> PackageResolve(Environment* env, + const std::string& specifier, + const URL& base) { + size_t sep_index = specifier.find('/'); + if (specifier[0] == '@' && (sep_index == std::string::npos || + specifier.length() == 0)) { + std::string msg = "Invalid package name '" + specifier + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_MODULE_SPECIFIER(env, msg.c_str()); + return Nothing<URL>(); + } + bool scope = false; + if (specifier[0] == '@') { + scope = true; + sep_index = specifier.find('/', sep_index + 1); } - return ResolveIndex(search); + std::string pkg_name = specifier.substr(0, + sep_index == std::string::npos ? std::string::npos : sep_index); + std::string pkg_subpath; + if ((sep_index == std::string::npos || + sep_index == specifier.length() - 1)) { + pkg_subpath = ""; + } else { + pkg_subpath = "." + specifier.substr(sep_index); + } + URL pjson_url("./node_modules/" + pkg_name + "/package.json", &base); + std::string pjson_path = pjson_url.ToFilePath(); + std::string last_path; + do { + DescriptorType check = + CheckDescriptorAtPath(pjson_path.substr(0, pjson_path.length() - 13)); + if (check != DIRECTORY) { + last_path = pjson_path; + pjson_url = URL((scope ? + "../../../../node_modules/" : "../../../node_modules/") + + pkg_name + "/package.json", &pjson_url); + pjson_path = pjson_url.ToFilePath(); + continue; + } + + // Package match. + Maybe<const PackageConfig*> pcfg = GetPackageConfig(env, pjson_path, base); + // Invalid package configuration error. + if (pcfg.IsNothing()) return Nothing<URL>(); + if (!pkg_subpath.length()) { + return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base); + } else { + return FinalizeResolution(env, URL(pkg_subpath, pjson_url), base); + } + CHECK(false); + // Cross-platform root check. + } while (pjson_path.length() != last_path.length()); + + std::string msg = "Cannot find package '" + pkg_name + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + return Nothing<URL>(); } } // anonymous namespace Maybe<URL> Resolve(Environment* env, const std::string& specifier, - const URL& base, - PackageMainCheck check_pjson_main) { - URL pure_url(specifier); - if (!(pure_url.flags() & URL_FLAGS_FAILED)) { - // just check existence, without altering - Maybe<uv_file> check = CheckFile(pure_url.ToFilePath()); - if (check.IsNothing()) { - return Nothing<URL>(); - } - return Just(pure_url); - } - if (specifier.length() == 0) { - return Nothing<URL>(); - } + const URL& base) { + // Order swapped from spec for minor perf gain. + // Ok since relative URLs cannot parse as URLs. + URL resolved; if (ShouldBeTreatedAsRelativeOrAbsolutePath(specifier)) { - URL resolved(specifier, base); - Maybe<URL> file = ResolveExtensions<TRY_EXACT_NAME>(resolved); - if (!file.IsNothing()) - return file; - if (specifier.back() != '/') { - resolved = URL(specifier + "/", base); - } - return ResolveDirectory(env, resolved, check_pjson_main); + resolved = URL(specifier, base); } else { - return ResolveModule(env, specifier, base); + URL pure_url(specifier); + if (!(pure_url.flags() & URL_FLAGS_FAILED)) { + resolved = pure_url; + } else { + return PackageResolve(env, specifier, base); + } } + return FinalizeResolution(env, resolved, base); } void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) { @@ -705,15 +897,40 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) { env, "second argument is not a URL string"); } - Maybe<URL> result = node::loader::Resolve(env, specifier_std, url); - if (result.IsNothing() || (result.FromJust().flags() & URL_FLAGS_FAILED)) { - std::string msg = "Cannot find module " + specifier_std; - return node::THROW_ERR_MISSING_MODULE(env, msg.c_str()); + Maybe<URL> result = + node::loader::Resolve(env, + specifier_std, + url); + if (result.IsNothing()) { + return; + } + + URL resolution = result.FromJust(); + CHECK(!(resolution.flags() & URL_FLAGS_FAILED)); + + Local<Value> resolution_obj; + if (resolution.ToObject(env).ToLocal(&resolution_obj)) + args.GetReturnValue().Set(resolution_obj); +} + +void ModuleWrap::GetPackageType(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + // module.getPackageType(url) + CHECK_EQ(args.Length(), 1); + + CHECK(args[0]->IsString()); + Utf8Value url_utf8(env->isolate(), args[0]); + URL url(*url_utf8, url_utf8.length()); + + PackageType pkg_type = PackageType::None; + Maybe<const PackageConfig*> pcfg = + GetPackageScopeConfig(env, url, url); + if (!pcfg.IsNothing()) { + pkg_type = pcfg.FromJust()->type; } - MaybeLocal<Value> obj = result.FromJust().ToObject(env); - if (!obj.IsEmpty()) - args.GetReturnValue().Set(obj.ToLocalChecked()); + args.GetReturnValue().Set(Integer::New(env->isolate(), pkg_type)); } static MaybeLocal<Promise> ImportModuleDynamically( @@ -849,6 +1066,7 @@ void ModuleWrap::Initialize(Local<Object> target, target->Set(env->context(), FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), tpl->GetFunction(context).ToLocalChecked()).FromJust(); env->SetMethod(target, "resolve", Resolve); + env->SetMethod(target, "getPackageType", GetPackageType); env->SetMethod(target, "setImportModuleDynamicallyCallback", SetImportModuleDynamicallyCallback); |