| #include "module_wrap.h" |
| |
| #include "env.h" |
| #include "memory_tracker-inl.h" |
| #include "node_contextify.h" |
| #include "node_errors.h" |
| #include "node_internals.h" |
| #include "node_process.h" |
| #include "node_url.h" |
| #include "node_watchdog.h" |
| #include "util-inl.h" |
| |
| #include <sys/stat.h> // S_IFDIR |
| |
| #include <algorithm> |
| |
| namespace node { |
| namespace loader { |
| |
| using errors::TryCatchScope; |
| |
| using node::contextify::ContextifyContext; |
| using node::url::URL; |
| using node::url::URL_FLAGS_FAILED; |
| using v8::Array; |
| using v8::ArrayBufferView; |
| using v8::Context; |
| using v8::EscapableHandleScope; |
| using v8::Function; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::Global; |
| using v8::HandleScope; |
| using v8::Integer; |
| using v8::IntegrityLevel; |
| using v8::Isolate; |
| using v8::Just; |
| using v8::Local; |
| using v8::Maybe; |
| using v8::MaybeLocal; |
| using v8::Module; |
| using v8::Nothing; |
| using v8::Number; |
| using v8::Object; |
| using v8::PrimitiveArray; |
| using v8::Promise; |
| using v8::ScriptCompiler; |
| using v8::ScriptOrigin; |
| using v8::ScriptOrModule; |
| using v8::String; |
| using v8::UnboundModuleScript; |
| using v8::Undefined; |
| using v8::Value; |
| |
| static const char* const EXTENSIONS[] = { |
| ".js", |
| ".json", |
| ".node", |
| ".mjs" |
| }; |
| |
| ModuleWrap::ModuleWrap(Environment* env, |
| Local<Object> object, |
| Local<Module> module, |
| Local<String> url) : |
| BaseObject(env, object), |
| id_(env->get_next_module_id()) { |
| module_.Reset(env->isolate(), module); |
| url_.Reset(env->isolate(), url); |
| env->id_to_module_map.emplace(id_, this); |
| } |
| |
| ModuleWrap::~ModuleWrap() { |
| HandleScope scope(env()->isolate()); |
| Local<Module> module = module_.Get(env()->isolate()); |
| env()->id_to_module_map.erase(id_); |
| auto range = env()->hash_to_module_map.equal_range(module->GetIdentityHash()); |
| for (auto it = range.first; it != range.second; ++it) { |
| if (it->second == this) { |
| env()->hash_to_module_map.erase(it); |
| break; |
| } |
| } |
| } |
| |
| ModuleWrap* ModuleWrap::GetFromModule(Environment* env, |
| Local<Module> module) { |
| auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash()); |
| for (auto it = range.first; it != range.second; ++it) { |
| if (it->second->module_ == module) { |
| return it->second; |
| } |
| } |
| return nullptr; |
| } |
| |
| ModuleWrap* ModuleWrap::GetFromID(Environment* env, uint32_t id) { |
| auto module_wrap_it = env->id_to_module_map.find(id); |
| if (module_wrap_it == env->id_to_module_map.end()) { |
| return nullptr; |
| } |
| return module_wrap_it->second; |
| } |
| |
| // new ModuleWrap(url, context, source, lineOffset, columnOffset) |
| // new ModuleWrap(url, context, exportNames, syntheticExecutionFunction) |
| void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) { |
| CHECK(args.IsConstructCall()); |
| CHECK_GE(args.Length(), 3); |
| |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| Local<Object> that = args.This(); |
| |
| CHECK(args[0]->IsString()); |
| Local<String> url = args[0].As<String>(); |
| |
| Local<Context> context; |
| if (args[1]->IsUndefined()) { |
| context = that->CreationContext(); |
| } else { |
| CHECK(args[1]->IsObject()); |
| ContextifyContext* sandbox = |
| ContextifyContext::ContextFromContextifiedSandbox( |
| env, args[1].As<Object>()); |
| CHECK_NOT_NULL(sandbox); |
| context = sandbox->context(); |
| } |
| |
| Local<Integer> line_offset; |
| Local<Integer> column_offset; |
| |
| bool synthetic = args[2]->IsArray(); |
| if (synthetic) { |
| // new ModuleWrap(url, context, exportNames, syntheticExecutionFunction) |
| CHECK(args[3]->IsFunction()); |
| } else { |
| // new ModuleWrap(url, context, source, lineOffset, columOffset, cachedData) |
| CHECK(args[2]->IsString()); |
| CHECK(args[3]->IsNumber()); |
| line_offset = args[3].As<Integer>(); |
| CHECK(args[4]->IsNumber()); |
| column_offset = args[4].As<Integer>(); |
| } |
| |
| Local<PrimitiveArray> host_defined_options = |
| PrimitiveArray::New(isolate, HostDefinedOptions::kLength); |
| host_defined_options->Set(isolate, HostDefinedOptions::kType, |
| Number::New(isolate, ScriptType::kModule)); |
| |
| ShouldNotAbortOnUncaughtScope no_abort_scope(env); |
| TryCatchScope try_catch(env); |
| |
| Local<Module> module; |
| |
| { |
| Context::Scope context_scope(context); |
| if (synthetic) { |
| CHECK(args[2]->IsArray()); |
| Local<Array> export_names_arr = args[2].As<Array>(); |
| |
| uint32_t len = export_names_arr->Length(); |
| std::vector<Local<String>> export_names(len); |
| for (uint32_t i = 0; i < len; i++) { |
| Local<Value> export_name_val = |
| export_names_arr->Get(context, i).ToLocalChecked(); |
| CHECK(export_name_val->IsString()); |
| export_names[i] = export_name_val.As<String>(); |
| } |
| |
| module = Module::CreateSyntheticModule(isolate, url, export_names, |
| SyntheticModuleEvaluationStepsCallback); |
| } else { |
| ScriptCompiler::CachedData* cached_data = nullptr; |
| if (!args[5]->IsUndefined()) { |
| CHECK(args[5]->IsArrayBufferView()); |
| Local<ArrayBufferView> cached_data_buf = args[5].As<ArrayBufferView>(); |
| uint8_t* data = static_cast<uint8_t*>( |
| cached_data_buf->Buffer()->GetBackingStore()->Data()); |
| cached_data = |
| new ScriptCompiler::CachedData(data + cached_data_buf->ByteOffset(), |
| cached_data_buf->ByteLength()); |
| } |
| |
| Local<String> source_text = args[2].As<String>(); |
| ScriptOrigin origin(url, |
| line_offset, // line offset |
| column_offset, // column offset |
| True(isolate), // is cross origin |
| Local<Integer>(), // script id |
| Local<Value>(), // source map URL |
| False(isolate), // is opaque (?) |
| False(isolate), // is WASM |
| True(isolate), // is ES Module |
| host_defined_options); |
| ScriptCompiler::Source source(source_text, origin, cached_data); |
| ScriptCompiler::CompileOptions options; |
| if (source.GetCachedData() == nullptr) { |
| options = ScriptCompiler::kNoCompileOptions; |
| } else { |
| options = ScriptCompiler::kConsumeCodeCache; |
| } |
| if (!ScriptCompiler::CompileModule(isolate, &source, options) |
| .ToLocal(&module)) { |
| if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
| CHECK(!try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.Exception().IsEmpty()); |
| AppendExceptionLine(env, try_catch.Exception(), try_catch.Message(), |
| ErrorHandlingMode::MODULE_ERROR); |
| try_catch.ReThrow(); |
| } |
| return; |
| } |
| if (options == ScriptCompiler::kConsumeCodeCache && |
| source.GetCachedData()->rejected) { |
| THROW_ERR_VM_MODULE_CACHED_DATA_REJECTED( |
| env, "cachedData buffer was rejected"); |
| try_catch.ReThrow(); |
| return; |
| } |
| } |
| } |
| |
| if (!that->Set(context, env->url_string(), url).FromMaybe(false)) { |
| return; |
| } |
| |
| ModuleWrap* obj = new ModuleWrap(env, that, module, url); |
| |
| if (synthetic) { |
| obj->synthetic_ = true; |
| obj->synthetic_evaluation_steps_.Reset( |
| env->isolate(), args[3].As<Function>()); |
| } |
| |
| obj->context_.Reset(isolate, context); |
| |
| env->hash_to_module_map.emplace(module->GetIdentityHash(), obj); |
| |
| host_defined_options->Set(isolate, HostDefinedOptions::kID, |
| Number::New(isolate, obj->id())); |
| |
| that->SetIntegrityLevel(context, IntegrityLevel::kFrozen); |
| args.GetReturnValue().Set(that); |
| } |
| |
| void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsFunction()); |
| |
| Local<Object> that = args.This(); |
| |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, that); |
| |
| if (obj->linked_) |
| return; |
| obj->linked_ = true; |
| |
| Local<Function> resolver_arg = args[0].As<Function>(); |
| |
| Local<Context> mod_context = obj->context_.Get(isolate); |
| Local<Module> module = obj->module_.Get(isolate); |
| |
| const int module_requests_length = module->GetModuleRequestsLength(); |
| MaybeStackBuffer<Local<Value>, 16> promises(module_requests_length); |
| |
| // call the dependency resolve callbacks |
| for (int i = 0; i < module_requests_length; i++) { |
| Local<String> specifier = module->GetModuleRequest(i); |
| Utf8Value specifier_utf8(env->isolate(), specifier); |
| std::string specifier_std(*specifier_utf8, specifier_utf8.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].Reset(env->isolate(), resolve_promise); |
| |
| promises[i] = resolve_promise; |
| } |
| |
| args.GetReturnValue().Set( |
| Array::New(isolate, promises.out(), promises.length())); |
| } |
| |
| void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| Local<Context> context = obj->context_.Get(isolate); |
| Local<Module> module = obj->module_.Get(isolate); |
| TryCatchScope try_catch(env); |
| USE(module->InstantiateModule(context, ResolveCallback)); |
| |
| // clear resolve cache on instantiate |
| obj->resolve_cache_.clear(); |
| |
| if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
| CHECK(!try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.Exception().IsEmpty()); |
| AppendExceptionLine(env, try_catch.Exception(), try_catch.Message(), |
| ErrorHandlingMode::MODULE_ERROR); |
| try_catch.ReThrow(); |
| return; |
| } |
| } |
| |
| void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| Local<Context> context = obj->context_.Get(isolate); |
| Local<Module> module = obj->module_.Get(isolate); |
| |
| // module.evaluate(timeout, breakOnSigint) |
| CHECK_EQ(args.Length(), 2); |
| |
| CHECK(args[0]->IsNumber()); |
| int64_t timeout = args[0]->IntegerValue(env->context()).FromJust(); |
| |
| CHECK(args[1]->IsBoolean()); |
| bool break_on_sigint = args[1]->IsTrue(); |
| |
| ShouldNotAbortOnUncaughtScope no_abort_scope(env); |
| TryCatchScope try_catch(env); |
| |
| bool timed_out = false; |
| bool received_signal = false; |
| MaybeLocal<Value> result; |
| if (break_on_sigint && timeout != -1) { |
| Watchdog wd(isolate, timeout, &timed_out); |
| SigintWatchdog swd(isolate, &received_signal); |
| result = module->Evaluate(context); |
| } else if (break_on_sigint) { |
| SigintWatchdog swd(isolate, &received_signal); |
| result = module->Evaluate(context); |
| } else if (timeout != -1) { |
| Watchdog wd(isolate, timeout, &timed_out); |
| result = module->Evaluate(context); |
| } else { |
| result = module->Evaluate(context); |
| } |
| |
| if (result.IsEmpty()) { |
| CHECK(try_catch.HasCaught()); |
| } |
| |
| // Convert the termination exception into a regular exception. |
| if (timed_out || received_signal) { |
| if (!env->is_main_thread() && env->is_stopping()) |
| return; |
| env->isolate()->CancelTerminateExecution(); |
| // It is possible that execution was terminated by another timeout in |
| // which this timeout is nested, so check whether one of the watchdogs |
| // from this invocation is responsible for termination. |
| if (timed_out) { |
| THROW_ERR_SCRIPT_EXECUTION_TIMEOUT(env, timeout); |
| } else if (received_signal) { |
| THROW_ERR_SCRIPT_EXECUTION_INTERRUPTED(env); |
| } |
| } |
| |
| if (try_catch.HasCaught()) { |
| if (!try_catch.HasTerminated()) |
| try_catch.ReThrow(); |
| return; |
| } |
| |
| args.GetReturnValue().Set(result.ToLocalChecked()); |
| } |
| |
| void ModuleWrap::GetNamespace(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| |
| switch (module->GetStatus()) { |
| default: |
| return env->ThrowError( |
| "cannot get namespace, Module has not been instantiated"); |
| case v8::Module::Status::kInstantiated: |
| case v8::Module::Status::kEvaluating: |
| case v8::Module::Status::kEvaluated: |
| break; |
| } |
| |
| Local<Value> result = module->GetModuleNamespace(); |
| args.GetReturnValue().Set(result); |
| } |
| |
| void ModuleWrap::GetStatus(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| |
| args.GetReturnValue().Set(module->GetStatus()); |
| } |
| |
| void ModuleWrap::GetStaticDependencySpecifiers( |
| const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| Local<Module> module = obj->module_.Get(env->isolate()); |
| |
| int count = module->GetModuleRequestsLength(); |
| |
| MaybeStackBuffer<Local<Value>, 16> specifiers(count); |
| |
| for (int i = 0; i < count; i++) |
| specifiers[i] = module->GetModuleRequest(i); |
| |
| args.GetReturnValue().Set( |
| Array::New(env->isolate(), specifiers.out(), count)); |
| } |
| |
| void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| args.GetReturnValue().Set(module->GetException()); |
| } |
| |
| MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context, |
| Local<String> specifier, |
| Local<Module> referrer) { |
| Environment* env = Environment::GetCurrent(context); |
| CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. |
| Isolate* isolate = env->isolate(); |
| |
| ModuleWrap* dependent = GetFromModule(env, referrer); |
| if (dependent == nullptr) { |
| env->ThrowError("linking error, null dep"); |
| return MaybeLocal<Module>(); |
| } |
| |
| Utf8Value specifier_utf8(isolate, specifier); |
| std::string specifier_std(*specifier_utf8, specifier_utf8.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(isolate); |
| |
| if (resolve_promise->State() != Promise::kFulfilled) { |
| env->ThrowError("linking error, dependency promises must be resolved on " |
| "instantiate"); |
| return MaybeLocal<Module>(); |
| } |
| |
| Local<Object> 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* module; |
| ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>()); |
| return module->module_.Get(isolate); |
| } |
| |
| namespace { |
| |
| // Tests whether a path starts with /, ./ or ../ |
| // In WhatWG terminology, the alternative case is called a "bare" specifier |
| // (e.g. in `import "jquery"`). |
| inline bool ShouldBeTreatedAsRelativeOrAbsolutePath( |
| const std::string& specifier) { |
| size_t len = specifier.length(); |
| if (len == 0) |
| return false; |
| if (specifier[0] == '/') { |
| return true; |
| } else if (specifier[0] == '.') { |
| if (len == 1 || specifier[1] == '/') |
| return true; |
| if (specifier[1] == '.') { |
| if (len == 2 || specifier[2] == '/') |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::string ReadFile(uv_file file) { |
| std::string contents; |
| uv_fs_t req; |
| char buffer_memory[4096]; |
| uv_buf_t buf = uv_buf_init(buffer_memory, sizeof(buffer_memory)); |
| |
| do { |
| const int r = uv_fs_read(nullptr, |
| &req, |
| file, |
| &buf, |
| 1, |
| contents.length(), // offset |
| nullptr); |
| uv_fs_req_cleanup(&req); |
| |
| if (r <= 0) |
| break; |
| contents.append(buf.base, r); |
| } while (true); |
| return contents; |
| } |
| |
| enum DescriptorType { |
| FILE, |
| DIRECTORY, |
| NONE |
| }; |
| |
| // 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; |
| #ifdef _WIN32 |
| std::string pth = "\\\\.\\" + path; |
| uv_file fd = uv_fs_open(nullptr, &fs_req, pth.c_str(), O_RDONLY, 0, nullptr); |
| #else |
| uv_file fd = uv_fs_open(nullptr, &fs_req, path.c_str(), O_RDONLY, 0, nullptr); |
| #endif |
| uv_fs_req_cleanup(&fs_req); |
| if (fd < 0) return Nothing<uv_file>(); |
| return Just(fd); |
| } |
| |
| inline void CloseDescriptor(uv_file fd) { |
| uv_fs_t fs_req; |
| CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, fd, nullptr)); |
| uv_fs_req_cleanup(&fs_req); |
| } |
| |
| 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 is_directory ? DIRECTORY : FILE; |
| } |
| uv_fs_req_cleanup(&fs_req); |
| return NONE; |
| } |
| |
| // 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; |
| } |
| |
| 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 HasName = PackageConfig::HasName; |
| using PackageType = PackageConfig::PackageType; |
| |
| 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()) { |
| 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<std::string> source = ReadIfFile(path); |
| |
| if (source.IsNothing()) { |
| auto entry = env->package_json_cache.emplace(path, |
| PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", |
| HasName::No, "", |
| PackageType::None, Global<Value>() }); |
| return Just(&entry.first->second); |
| } |
| |
| std::string pkg_src = source.FromJust(); |
| |
| Isolate* isolate = env->isolate(); |
| HandleScope handle_scope(isolate); |
| |
| Local<Object> pkg_json; |
| { |
| 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, "", |
| HasName::No, "", |
| PackageType::None, Global<Value>() }); |
| 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)) { |
| if (pkg_main->IsString()) { |
| has_main = HasMain::Yes; |
| } |
| Utf8Value main_utf8(isolate, pkg_main); |
| main_std.assign(std::string(*main_utf8, main_utf8.length())); |
| } |
| |
| Local<Value> pkg_name; |
| HasName has_name = HasName::No; |
| std::string name_std; |
| if (pkg_json->Get(env->context(), env->name_string()).ToLocal(&pkg_name)) { |
| if (pkg_name->IsString()) { |
| has_name = HasName::Yes; |
| |
| Utf8Value name_utf8(isolate, pkg_name); |
| name_std.assign(std::string(*name_utf8, name_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->IsNullOrUndefined()) { |
| Global<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, |
| has_name, name_std, |
| pkg_type, std::move(exports) }); |
| return Just(&entry.first->second); |
| } |
| |
| auto entry = env->package_json_cache.emplace(path, |
| PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, |
| has_name, name_std, |
| pkg_type, Global<Value>() }); |
| 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) { |
| std::string pjson_url_path = pjson_url.path(); |
| if (pjson_url_path.length() > 25 && |
| pjson_url_path.substr(pjson_url_path.length() - 25, 25) == |
| "node_modules/package.json") { |
| break; |
| } |
| 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()) break; |
| } |
| auto entry = env->package_json_cache.emplace(pjson_url.ToFilePath(), |
| PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", |
| HasName::No, "", |
| PackageType::None, Global<Value>() }); |
| 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 { |
| TRY_EXACT_NAME, |
| ONLY_VIA_EXTENSIONS |
| }; |
| |
| template <ResolveExtensionsOptions options> |
| Maybe<URL> ResolveExtensions(const URL& search) { |
| if (options == TRY_EXACT_NAME) { |
| if (FileExists(search)) { |
| return Just(search); |
| } |
| } |
| |
| for (const char* extension : EXTENSIONS) { |
| URL guess(search.path() + extension, &search); |
| if (FileExists(guess)) { |
| return Just(guess); |
| } |
| } |
| |
| return Nothing<URL>(); |
| } |
| |
| inline Maybe<URL> ResolveIndex(const URL& search) { |
| return ResolveExtensions<ONLY_VIA_EXTENSIONS>(URL("index", search)); |
| } |
| |
| Maybe<URL> FinalizeResolution(Environment* env, |
| const URL& resolved, |
| const URL& base) { |
| if (env->options()->experimental_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 (resolved.path().back() == '/') { |
| return Just(resolved); |
| } |
| |
| const std::string& path = resolved.ToFilePath(); |
| if (CheckDescriptorAtPath(path) != FILE) { |
| std::string msg = "Cannot find module " + |
| (path.length() != 0 ? path : resolved.path()) + |
| " imported from " + base.ToFilePath(); |
| node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); |
| return Nothing<URL>(); |
| } |
| |
| return Just(resolved); |
| } |
| |
| void ThrowExportsNotFound(Environment* env, |
| const std::string& subpath, |
| const URL& pjson_url, |
| const URL& base) { |
| const std::string msg = "Package subpath '" + subpath + "' is not defined" + |
| " by \"exports\" in " + pjson_url.ToFilePath() + " imported from " + |
| base.ToFilePath(); |
| node::THROW_ERR_PACKAGE_PATH_NOT_EXPORTED(env, msg.c_str()); |
| } |
| |
| void ThrowSubpathInvalid(Environment* env, |
| const std::string& subpath, |
| const URL& pjson_url, |
| const URL& base) { |
| const std::string msg = "Package subpath '" + subpath + "' is not a valid " + |
| "module request for the \"exports\" resolution of " + |
| pjson_url.ToFilePath() + " imported from " + base.ToFilePath(); |
| node::THROW_ERR_INVALID_MODULE_SPECIFIER(env, msg.c_str()); |
| } |
| |
| void ThrowExportsInvalid(Environment* env, |
| const std::string& subpath, |
| const std::string& target, |
| const URL& pjson_url, |
| const URL& base) { |
| if (subpath.length()) { |
| const std::string msg = "Invalid \"exports\" target \"" + target + |
| "\" defined for '" + subpath + "' in the package config " + |
| pjson_url.ToFilePath() + " imported from " + base.ToFilePath(); |
| node::THROW_ERR_INVALID_PACKAGE_TARGET(env, msg.c_str()); |
| } else { |
| const std::string msg = "Invalid \"exports\" main target " + target + |
| " defined in the package config " + pjson_url.ToFilePath() + |
| " imported from " + base.ToFilePath(); |
| node::THROW_ERR_INVALID_PACKAGE_TARGET(env, msg.c_str()); |
| } |
| } |
| |
| void ThrowExportsInvalid(Environment* env, |
| const std::string& subpath, |
| Local<Value> target, |
| const URL& pjson_url, |
| const URL& base) { |
| Local<String> target_string; |
| if (target->IsObject()) { |
| if (!v8::JSON::Stringify(env->context(), target.As<Object>(), |
| v8::String::Empty(env->isolate())).ToLocal(&target_string)) |
| return; |
| } else { |
| if (!target->ToString(env->context()).ToLocal(&target_string)) |
| return; |
| } |
| Utf8Value target_utf8(env->isolate(), target_string); |
| std::string target_str(*target_utf8, target_utf8.length()); |
| if (target->IsArray()) { |
| target_str = '[' + target_str + ']'; |
| } |
| ThrowExportsInvalid(env, subpath, target_str, pjson_url, base); |
| } |
| |
| Maybe<URL> ResolveExportsTargetString(Environment* env, |
| const std::string& target, |
| const std::string& subpath, |
| const std::string& match, |
| const URL& pjson_url, |
| const URL& base) { |
| if (target.substr(0, 2) != "./") { |
| ThrowExportsInvalid(env, match, target, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| if (subpath.length() > 0 && target.back() != '/') { |
| ThrowExportsInvalid(env, match, target, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| URL resolved(target, pjson_url); |
| std::string resolved_path = resolved.path(); |
| std::string pkg_path = URL(".", pjson_url).path(); |
| if (resolved_path.find(pkg_path) != 0 || |
| resolved_path.find("/node_modules/", pkg_path.length() - 1) != |
| std::string::npos) { |
| ThrowExportsInvalid(env, match, target, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| if (subpath.length() == 0) return Just(resolved); |
| URL subpath_resolved(subpath, resolved); |
| std::string subpath_resolved_path = subpath_resolved.path(); |
| if (subpath_resolved_path.find(resolved_path) != 0 || |
| subpath_resolved_path.find("/node_modules/", pkg_path.length() - 1) |
| != std::string::npos) { |
| ThrowSubpathInvalid(env, match + subpath, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| return Just(subpath_resolved); |
| } |
| |
| bool IsArrayIndex(Environment* env, Local<Value> p) { |
| Local<Context> context = env->context(); |
| Local<String> p_str = p->ToString(context).ToLocalChecked(); |
| double n_dbl = static_cast<double>(p_str->NumberValue(context).FromJust()); |
| Local<Number> n = Number::New(env->isolate(), n_dbl); |
| Local<String> cmp_str = n->ToString(context).ToLocalChecked(); |
| if (!p_str->Equals(context, cmp_str).FromJust()) { |
| return false; |
| } |
| if (n_dbl == 0 && std::signbit(n_dbl) == false) { |
| return true; |
| } |
| Local<Integer> cmp_integer; |
| if (!n->ToInteger(context).ToLocal(&cmp_integer)) { |
| return false; |
| } |
| return n_dbl > 0 && n_dbl < (1LL << 32) - 1; |
| } |
| |
| Maybe<URL> ResolveExportsTarget(Environment* env, |
| const URL& pjson_url, |
| Local<Value> target, |
| const std::string& subpath, |
| const std::string& pkg_subpath, |
| const URL& base) { |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| if (target->IsString()) { |
| Utf8Value target_utf8(isolate, target.As<String>()); |
| std::string target_str(*target_utf8, target_utf8.length()); |
| Maybe<URL> resolved = ResolveExportsTargetString(env, target_str, subpath, |
| pkg_subpath, pjson_url, base); |
| if (resolved.IsNothing()) { |
| return Nothing<URL>(); |
| } |
| return FinalizeResolution(env, resolved.FromJust(), base); |
| } else if (target->IsArray()) { |
| Local<Array> target_arr = target.As<Array>(); |
| const uint32_t length = target_arr->Length(); |
| if (length == 0) { |
| ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| for (uint32_t i = 0; i < length; i++) { |
| auto target_item = target_arr->Get(context, i).ToLocalChecked(); |
| { |
| TryCatchScope try_catch(env); |
| Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, |
| target_item, subpath, pkg_subpath, base); |
| if (resolved.IsNothing()) { |
| CHECK(try_catch.HasCaught()); |
| if (try_catch.Exception().IsEmpty()) return Nothing<URL>(); |
| Local<Object> e; |
| if (!try_catch.Exception()->ToObject(context).ToLocal(&e)) |
| return Nothing<URL>(); |
| Local<Value> code; |
| if (!e->Get(context, env->code_string()).ToLocal(&code)) |
| return Nothing<URL>(); |
| Local<String> code_string; |
| if (!code->ToString(context).ToLocal(&code_string)) |
| return Nothing<URL>(); |
| Utf8Value code_utf8(env->isolate(), code_string); |
| if (strcmp(*code_utf8, "ERR_PACKAGE_PATH_NOT_EXPORTED") == 0 || |
| strcmp(*code_utf8, "ERR_INVALID_PACKAGE_TARGET") == 0) { |
| continue; |
| } |
| try_catch.ReThrow(); |
| return Nothing<URL>(); |
| } |
| CHECK(!try_catch.HasCaught()); |
| return FinalizeResolution(env, resolved.FromJust(), base); |
| } |
| } |
| auto invalid = target_arr->Get(context, length - 1).ToLocalChecked(); |
| Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, invalid, |
| subpath, pkg_subpath, base); |
| CHECK(resolved.IsNothing()); |
| return Nothing<URL>(); |
| } else if (target->IsObject()) { |
| Local<Object> target_obj = target.As<Object>(); |
| Local<Array> target_obj_keys = |
| target_obj->GetOwnPropertyNames(context).ToLocalChecked(); |
| Local<Value> conditionalTarget; |
| for (uint32_t i = 0; i < target_obj_keys->Length(); ++i) { |
| Local<Value> key = |
| target_obj_keys->Get(context, i).ToLocalChecked(); |
| if (IsArrayIndex(env, key)) { |
| const std::string msg = "Invalid package config " + |
| pjson_url.ToFilePath() + " imported from " + base.ToFilePath() + |
| ". \"exports\" cannot contain numeric property keys."; |
| node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); |
| return Nothing<URL>(); |
| } |
| } |
| for (uint32_t i = 0; i < target_obj_keys->Length(); ++i) { |
| Local<Value> key = target_obj_keys->Get(context, i).ToLocalChecked(); |
| Utf8Value key_utf8(env->isolate(), |
| key->ToString(context).ToLocalChecked()); |
| std::string key_str(*key_utf8, key_utf8.length()); |
| if (key_str == "node" || key_str == "import") { |
| conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); |
| { |
| TryCatchScope try_catch(env); |
| Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, |
| conditionalTarget, subpath, pkg_subpath, base); |
| if (resolved.IsNothing()) { |
| CHECK(try_catch.HasCaught()); |
| if (try_catch.Exception().IsEmpty()) return Nothing<URL>(); |
| Local<Object> e; |
| if (!try_catch.Exception()->ToObject(context).ToLocal(&e)) |
| return Nothing<URL>(); |
| Local<Value> code; |
| if (!e->Get(context, env->code_string()).ToLocal(&code)) |
| return Nothing<URL>(); |
| Local<String> code_string; |
| if (!code->ToString(context).ToLocal(&code_string)) |
| return Nothing<URL>(); |
| Utf8Value code_utf8(env->isolate(), code_string); |
| if (strcmp(*code_utf8, "ERR_PACKAGE_PATH_NOT_EXPORTED") == 0) |
| continue; |
| try_catch.ReThrow(); |
| return Nothing<URL>(); |
| } |
| CHECK(!try_catch.HasCaught()); |
| ProcessEmitExperimentalWarning(env, "Conditional exports"); |
| return resolved; |
| } |
| } else if (key_str == "default") { |
| conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); |
| { |
| TryCatchScope try_catch(env); |
| Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, |
| conditionalTarget, subpath, pkg_subpath, base); |
| if (resolved.IsNothing()) { |
| CHECK(try_catch.HasCaught() && !try_catch.Exception().IsEmpty()); |
| auto e = try_catch.Exception()->ToObject(context).ToLocalChecked(); |
| auto code = e->Get(context, env->code_string()).ToLocalChecked(); |
| Utf8Value code_utf8(env->isolate(), |
| code->ToString(context).ToLocalChecked()); |
| std::string code_str(*code_utf8, code_utf8.length()); |
| if (code_str == "ERR_PACKAGE_PATH_NOT_EXPORTED") continue; |
| try_catch.ReThrow(); |
| return Nothing<URL>(); |
| } |
| CHECK(!try_catch.HasCaught()); |
| ProcessEmitExperimentalWarning(env, "Conditional exports"); |
| return resolved; |
| } |
| } |
| } |
| ThrowExportsNotFound(env, pkg_subpath, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| |
| Maybe<bool> IsConditionalExportsMainSugar(Environment* env, |
| Local<Value> exports, |
| const URL& pjson_url, |
| const URL& base) { |
| if (exports->IsString() || exports->IsArray()) return Just(true); |
| if (!exports->IsObject()) return Just(false); |
| Local<Context> context = env->context(); |
| Local<Object> exports_obj = exports.As<Object>(); |
| Local<Array> keys = |
| exports_obj->GetOwnPropertyNames(context).ToLocalChecked(); |
| bool isConditionalSugar = false; |
| for (uint32_t i = 0; i < keys->Length(); ++i) { |
| Local<Value> key = keys->Get(context, i).ToLocalChecked(); |
| Utf8Value key_utf8(env->isolate(), key->ToString(context).ToLocalChecked()); |
| bool curIsConditionalSugar = key_utf8.length() == 0 || key_utf8[0] != '.'; |
| if (i == 0) { |
| isConditionalSugar = curIsConditionalSugar; |
| } else if (isConditionalSugar != curIsConditionalSugar) { |
| const std::string msg = "Invalid package config " + pjson_url.ToFilePath() |
| + " imported from " + base.ToFilePath() + ". " + |
| "\"exports\" cannot contain some keys starting with '.' and some not." + |
| " The exports object must either be an object of package subpath keys" + |
| " or an object of main entry condition name keys only."; |
| node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); |
| return Nothing<bool>(); |
| } |
| } |
| return Just(isConditionalSugar); |
| } |
| |
| Maybe<URL> PackageMainResolve(Environment* env, |
| const URL& pjson_url, |
| const PackageConfig& pcfg, |
| const URL& base) { |
| if (pcfg.exists == Exists::Yes) { |
| Isolate* isolate = env->isolate(); |
| |
| if (!pcfg.exports.IsEmpty()) { |
| Local<Value> exports = pcfg.exports.Get(isolate); |
| Maybe<bool> isConditionalExportsMainSugar = |
| IsConditionalExportsMainSugar(env, exports, pjson_url, base); |
| if (isConditionalExportsMainSugar.IsNothing()) |
| return Nothing<URL>(); |
| if (isConditionalExportsMainSugar.FromJust()) { |
| return ResolveExportsTarget(env, pjson_url, exports, "", "", base); |
| } else if (exports->IsObject()) { |
| Local<Object> exports_obj = exports.As<Object>(); |
| if (exports_obj->HasOwnProperty(env->context(), env->dot_string()) |
| .FromJust()) { |
| Local<Value> target = |
| exports_obj->Get(env->context(), env->dot_string()) |
| .ToLocalChecked(); |
| return ResolveExportsTarget(env, pjson_url, target, "", "", base); |
| } |
| } |
| std::string msg = "No \"exports\" main resolved in " + |
| pjson_url.ToFilePath(); |
| node::THROW_ERR_PACKAGE_PATH_NOT_EXPORTED(env, msg.c_str()); |
| } |
| 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()->experimental_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; |
| } |
| } |
| } |
| 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> PackageExportsResolve(Environment* env, |
| const URL& pjson_url, |
| const std::string& pkg_subpath, |
| const PackageConfig& pcfg, |
| const URL& base) { |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| Local<Value> exports = pcfg.exports.Get(isolate); |
| Maybe<bool> isConditionalExportsMainSugar = |
| IsConditionalExportsMainSugar(env, exports, pjson_url, base); |
| if (isConditionalExportsMainSugar.IsNothing()) |
| return Nothing<URL>(); |
| if (!exports->IsObject() || isConditionalExportsMainSugar.FromJust()) { |
| ThrowExportsNotFound(env, pkg_subpath, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| Local<Object> exports_obj = exports.As<Object>(); |
| Local<String> subpath = String::NewFromUtf8(isolate, |
| pkg_subpath.c_str(), v8::NewStringType::kNormal).ToLocalChecked(); |
| |
| if (exports_obj->HasOwnProperty(context, subpath).FromJust()) { |
| Local<Value> target = exports_obj->Get(context, subpath).ToLocalChecked(); |
| Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, target, "", |
| pkg_subpath, base); |
| if (resolved.IsNothing()) { |
| return Nothing<URL>(); |
| } |
| return FinalizeResolution(env, resolved.FromJust(), base); |
| } |
| |
| Local<String> best_match; |
| std::string best_match_str = ""; |
| Local<Array> keys = |
| exports_obj->GetOwnPropertyNames(context).ToLocalChecked(); |
| for (uint32_t i = 0; i < keys->Length(); ++i) { |
| Local<Value> key = keys->Get(context, i).ToLocalChecked(); |
| Utf8Value key_utf8(isolate, key->ToString(context).ToLocalChecked()); |
| std::string key_str(*key_utf8, key_utf8.length()); |
| if (key_str.back() != '/') continue; |
| if (pkg_subpath.substr(0, key_str.length()) == key_str && |
| key_str.length() > best_match_str.length()) { |
| best_match = key->ToString(context).ToLocalChecked(); |
| best_match_str = key_str; |
| } |
| } |
| |
| if (best_match_str.length() > 0) { |
| auto target = exports_obj->Get(context, best_match).ToLocalChecked(); |
| std::string subpath = pkg_subpath.substr(best_match_str.length()); |
| |
| Maybe<URL> resolved = ResolveExportsTarget(env, pjson_url, target, subpath, |
| pkg_subpath, base); |
| if (resolved.IsNothing()) { |
| return Nothing<URL>(); |
| } |
| return FinalizeResolution(env, resolved.FromJust(), base); |
| } |
| |
| ThrowExportsNotFound(env, pkg_subpath, pjson_url, base); |
| return Nothing<URL>(); |
| } |
| |
| Maybe<URL> PackageResolve(Environment* env, |
| const std::string& specifier, |
| const URL& base) { |
| size_t sep_index = specifier.find('/'); |
| bool valid_package_name = true; |
| bool scope = false; |
| if (specifier[0] == '@') { |
| scope = true; |
| if (sep_index == std::string::npos || specifier.length() == 0) { |
| valid_package_name = false; |
| } else { |
| sep_index = specifier.find('/', sep_index + 1); |
| } |
| } else if (specifier[0] == '.') { |
| valid_package_name = false; |
| } |
| std::string pkg_name = specifier.substr(0, |
| sep_index == std::string::npos ? std::string::npos : sep_index); |
| // Package name cannot have leading . and cannot have percent-encoding or |
| // separators. |
| for (size_t i = 0; i < pkg_name.length(); i++) { |
| char c = pkg_name[i]; |
| if (c == '%' || c == '\\') { |
| valid_package_name = false; |
| break; |
| } |
| } |
| if (!valid_package_name) { |
| std::string msg = "Invalid package name '" + specifier + |
| "' imported from " + base.ToFilePath(); |
| node::THROW_ERR_INVALID_MODULE_SPECIFIER(env, msg.c_str()); |
| return Nothing<URL>(); |
| } |
| std::string pkg_subpath; |
| if (sep_index == std::string::npos) { |
| pkg_subpath = ""; |
| } else { |
| pkg_subpath = "." + specifier.substr(sep_index); |
| } |
| |
| // ResolveSelf |
| const PackageConfig* pcfg; |
| if (GetPackageScopeConfig(env, base, base).To(&pcfg) && |
| pcfg->exists == Exists::Yes) { |
| // TODO(jkrems): Find a way to forward the pair/iterator already generated |
| // while executing GetPackageScopeConfig |
| URL pjson_url(""); |
| bool found_pjson = false; |
| for (const auto& it : env->package_json_cache) { |
| if (&it.second == pcfg) { |
| pjson_url = URL::FromFilePath(it.first); |
| found_pjson = true; |
| } |
| } |
| if (found_pjson && pcfg->name == pkg_name && !pcfg->exports.IsEmpty()) { |
| ProcessEmitExperimentalWarning(env, "Package name self resolution"); |
| if (pkg_subpath == "./") { |
| return Just(URL("./", pjson_url)); |
| } else if (!pkg_subpath.length()) { |
| return PackageMainResolve(env, pjson_url, *pcfg, base); |
| } else { |
| return PackageExportsResolve(env, pjson_url, pkg_subpath, *pcfg, base); |
| } |
| } |
| } |
| |
| 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 == "./") { |
| return Just(URL("./", pjson_url)); |
| } else if (!pkg_subpath.length()) { |
| return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base); |
| } else { |
| if (!pcfg.FromJust()->exports.IsEmpty()) { |
| return PackageExportsResolve(env, pjson_url, pkg_subpath, |
| *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) { |
| // Order swapped from spec for minor perf gain. |
| // Ok since relative URLs cannot parse as URLs. |
| URL resolved; |
| if (ShouldBeTreatedAsRelativeOrAbsolutePath(specifier)) { |
| resolved = URL(specifier, base); |
| } else { |
| 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) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| // module.resolve(specifier, url) |
| CHECK_EQ(args.Length(), 2); |
| |
| CHECK(args[0]->IsString()); |
| Utf8Value specifier_utf8(env->isolate(), args[0]); |
| std::string specifier_std(*specifier_utf8, specifier_utf8.length()); |
| |
| CHECK(args[1]->IsString()); |
| Utf8Value url_utf8(env->isolate(), args[1]); |
| URL url(*url_utf8, url_utf8.length()); |
| |
| if (url.flags() & URL_FLAGS_FAILED) { |
| return node::THROW_ERR_INVALID_ARG_TYPE( |
| env, "second argument is not a URL string"); |
| } |
| |
| 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; |
| } |
| |
| args.GetReturnValue().Set(Integer::New(env->isolate(), pkg_type)); |
| } |
| |
| static MaybeLocal<Promise> ImportModuleDynamically( |
| Local<Context> context, |
| Local<ScriptOrModule> referrer, |
| Local<String> specifier) { |
| Isolate* iso = context->GetIsolate(); |
| Environment* env = Environment::GetCurrent(context); |
| CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. |
| EscapableHandleScope handle_scope(iso); |
| |
| Local<Function> import_callback = |
| env->host_import_module_dynamically_callback(); |
| |
| Local<PrimitiveArray> options = referrer->GetHostDefinedOptions(); |
| if (options->Length() != HostDefinedOptions::kLength) { |
| Local<Promise::Resolver> resolver = |
| Promise::Resolver::New(context).ToLocalChecked(); |
| resolver |
| ->Reject(context, |
| v8::Exception::TypeError(FIXED_ONE_BYTE_STRING( |
| context->GetIsolate(), "Invalid host defined options"))) |
| .ToChecked(); |
| return handle_scope.Escape(resolver->GetPromise()); |
| } |
| |
| Local<Value> object; |
| |
| int type = options->Get(iso, HostDefinedOptions::kType) |
| .As<Number>() |
| ->Int32Value(context) |
| .ToChecked(); |
| uint32_t id = options->Get(iso, HostDefinedOptions::kID) |
| .As<Number>() |
| ->Uint32Value(context) |
| .ToChecked(); |
| if (type == ScriptType::kScript) { |
| contextify::ContextifyScript* wrap = env->id_to_script_map.find(id)->second; |
| object = wrap->object(); |
| } else if (type == ScriptType::kModule) { |
| ModuleWrap* wrap = ModuleWrap::GetFromID(env, id); |
| object = wrap->object(); |
| } else if (type == ScriptType::kFunction) { |
| auto it = env->id_to_function_map.find(id); |
| CHECK_NE(it, env->id_to_function_map.end()); |
| object = it->second->object(); |
| } else { |
| UNREACHABLE(); |
| } |
| |
| Local<Value> import_args[] = { |
| object, |
| Local<Value>(specifier), |
| }; |
| |
| Local<Value> result; |
| if (import_callback->Call( |
| context, |
| Undefined(iso), |
| arraysize(import_args), |
| import_args).ToLocal(&result)) { |
| CHECK(result->IsPromise()); |
| return handle_scope.Escape(result.As<Promise>()); |
| } |
| |
| return MaybeLocal<Promise>(); |
| } |
| |
| void ModuleWrap::SetImportModuleDynamicallyCallback( |
| const FunctionCallbackInfo<Value>& args) { |
| Isolate* iso = args.GetIsolate(); |
| Environment* env = Environment::GetCurrent(args); |
| HandleScope handle_scope(iso); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsFunction()); |
| Local<Function> import_callback = args[0].As<Function>(); |
| env->set_host_import_module_dynamically_callback(import_callback); |
| |
| iso->SetHostImportModuleDynamicallyCallback(ImportModuleDynamically); |
| } |
| |
| void ModuleWrap::HostInitializeImportMetaObjectCallback( |
| Local<Context> context, Local<Module> module, Local<Object> meta) { |
| Environment* env = Environment::GetCurrent(context); |
| CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. |
| ModuleWrap* module_wrap = GetFromModule(env, module); |
| |
| if (module_wrap == nullptr) { |
| return; |
| } |
| |
| Local<Object> wrap = module_wrap->object(); |
| Local<Function> callback = |
| env->host_initialize_import_meta_object_callback(); |
| Local<Value> args[] = { wrap, meta }; |
| TryCatchScope try_catch(env); |
| USE(callback->Call( |
| context, Undefined(env->isolate()), arraysize(args), args)); |
| if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
| try_catch.ReThrow(); |
| } |
| } |
| |
| void ModuleWrap::SetInitializeImportMetaObjectCallback( |
| const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| CHECK_EQ(args.Length(), 1); |
| CHECK(args[0]->IsFunction()); |
| Local<Function> import_meta_callback = args[0].As<Function>(); |
| env->set_host_initialize_import_meta_object_callback(import_meta_callback); |
| |
| isolate->SetHostInitializeImportMetaObjectCallback( |
| HostInitializeImportMetaObjectCallback); |
| } |
| |
| MaybeLocal<Value> ModuleWrap::SyntheticModuleEvaluationStepsCallback( |
| Local<Context> context, Local<Module> module) { |
| Environment* env = Environment::GetCurrent(context); |
| Isolate* isolate = env->isolate(); |
| |
| ModuleWrap* obj = GetFromModule(env, module); |
| |
| TryCatchScope try_catch(env); |
| Local<Function> synthetic_evaluation_steps = |
| obj->synthetic_evaluation_steps_.Get(isolate); |
| MaybeLocal<Value> ret = synthetic_evaluation_steps->Call(context, |
| obj->object(), 0, nullptr); |
| if (ret.IsEmpty()) { |
| CHECK(try_catch.HasCaught()); |
| } |
| obj->synthetic_evaluation_steps_.Reset(); |
| if (try_catch.HasCaught() && !try_catch.HasTerminated()) { |
| CHECK(!try_catch.Message().IsEmpty()); |
| CHECK(!try_catch.Exception().IsEmpty()); |
| try_catch.ReThrow(); |
| return MaybeLocal<Value>(); |
| } |
| return ret; |
| } |
| |
| void ModuleWrap::SetSyntheticExport(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Object> that = args.This(); |
| |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, that); |
| |
| CHECK(obj->synthetic_); |
| |
| CHECK_EQ(args.Length(), 2); |
| |
| CHECK(args[0]->IsString()); |
| Local<String> export_name = args[0].As<String>(); |
| |
| Local<Value> export_value = args[1]; |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| USE(module->SetSyntheticModuleExport(isolate, export_name, export_value)); |
| } |
| |
| void ModuleWrap::CreateCachedData(const FunctionCallbackInfo<Value>& args) { |
| Isolate* isolate = args.GetIsolate(); |
| Local<Object> that = args.This(); |
| |
| ModuleWrap* obj; |
| ASSIGN_OR_RETURN_UNWRAP(&obj, that); |
| |
| CHECK(!obj->synthetic_); |
| |
| Local<Module> module = obj->module_.Get(isolate); |
| |
| CHECK_LT(module->GetStatus(), v8::Module::Status::kEvaluating); |
| |
| Local<UnboundModuleScript> unbound_module_script = |
| module->GetUnboundModuleScript(); |
| std::unique_ptr<ScriptCompiler::CachedData> cached_data( |
| ScriptCompiler::CreateCodeCache(unbound_module_script)); |
| Environment* env = Environment::GetCurrent(args); |
| if (!cached_data) { |
| args.GetReturnValue().Set(Buffer::New(env, 0).ToLocalChecked()); |
| } else { |
| MaybeLocal<Object> buf = |
| Buffer::Copy(env, |
| reinterpret_cast<const char*>(cached_data->data), |
| cached_data->length); |
| args.GetReturnValue().Set(buf.ToLocalChecked()); |
| } |
| } |
| |
| void ModuleWrap::Initialize(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| 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); |
| env->SetProtoMethod(tpl, "setExport", SetSyntheticExport); |
| env->SetProtoMethodNoSideEffect(tpl, "createCachedData", CreateCachedData); |
| env->SetProtoMethodNoSideEffect(tpl, "getNamespace", GetNamespace); |
| env->SetProtoMethodNoSideEffect(tpl, "getStatus", GetStatus); |
| env->SetProtoMethodNoSideEffect(tpl, "getError", GetError); |
| env->SetProtoMethodNoSideEffect(tpl, "getStaticDependencySpecifiers", |
| GetStaticDependencySpecifiers); |
| |
| target->Set(env->context(), FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), |
| tpl->GetFunction(context).ToLocalChecked()).Check(); |
| env->SetMethod(target, "resolve", Resolve); |
| env->SetMethod(target, "getPackageType", GetPackageType); |
| env->SetMethod(target, |
| "setImportModuleDynamicallyCallback", |
| SetImportModuleDynamicallyCallback); |
| env->SetMethod(target, |
| "setInitializeImportMetaObjectCallback", |
| SetInitializeImportMetaObjectCallback); |
| |
| #define V(name) \ |
| target->Set(context, \ |
| FIXED_ONE_BYTE_STRING(env->isolate(), #name), \ |
| Integer::New(env->isolate(), Module::Status::name)) \ |
| .FromJust() |
| V(kUninstantiated); |
| V(kInstantiating); |
| V(kInstantiated); |
| V(kEvaluating); |
| V(kEvaluated); |
| V(kErrored); |
| #undef V |
| } |
| |
| } // namespace loader |
| } // namespace node |
| |
| NODE_MODULE_CONTEXT_AWARE_INTERNAL(module_wrap, |
| node::loader::ModuleWrap::Initialize) |