/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v26.5.1
src/node_modules.cc
712 строк
24 KB
Stefan Stojanovic
module,win: fix long subpath import
05 май 2026, 17:19
Не верифицирован
05 май 2026, 17:19
34ec8c9
Код
Авторство
О чём код?
#include "node_modules.h" #include <cstdio> #include "base_object-inl.h" #include "compile_cache.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_url.h" #include "path.h" #include "permission/permission.h" #include "permission/permission_base.h" #include "util-inl.h" #include "v8-fast-api-calls.h" #include "v8-function-callback.h" #include "v8-primitive.h" #include "v8-value.h" #include "v8.h" #include "simdjson.h" namespace node { namespace modules { using v8::Array; using v8::Context; using v8::External; using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::Integer; using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::Name; using v8::NewStringType; using v8::Null; using v8::Object; using v8::ObjectTemplate; using v8::Primitive; using v8::String; using v8::Undefined; using v8::Value; void BindingData::MemoryInfo(MemoryTracker* tracker) const { // Do nothing } BindingData::BindingData(Realm* realm, v8::Local<v8::Object> object, InternalFieldInfo* info) : SnapshotableObject(realm, object, type_int) {} bool BindingData::PrepareForSerialization(v8::Local<v8::Context> context, v8::SnapshotCreator* creator) { // Return true because we need to maintain the reference to the binding from // JS land. return true; } InternalFieldInfoBase* BindingData::Serialize(int index) { DCHECK_IS_SNAPSHOT_SLOT(index); InternalFieldInfo* info = InternalFieldInfoBase::New<InternalFieldInfo>(type()); return info; } void BindingData::Deserialize(v8::Local<v8::Context> context, v8::Local<v8::Object> holder, int index, InternalFieldInfoBase* info) { DCHECK_IS_SNAPSHOT_SLOT(index); HandleScope scope(Isolate::GetCurrent()); Realm* realm = Realm::GetCurrent(context); BindingData* binding = realm->AddBindingData<BindingData>(holder); CHECK_NOT_NULL(binding); } Local<Array> BindingData::PackageConfig::Serialize(Realm* realm) const { auto isolate = realm->isolate(); const auto ToString = [isolate](std::string_view input) -> Local<Primitive> { return String::NewFromUtf8( isolate, input.data(), NewStringType::kNormal, input.size()) .ToLocalChecked(); }; Local<Value> values[6] = { name.has_value() ? ToString(*name) : Undefined(isolate), main.has_value() ? ToString(*main) : Undefined(isolate), ToString(type), imports.has_value() ? ToString(*imports) : Undefined(isolate), exports.has_value() ? ToString(*exports) : Undefined(isolate), ToString(file_path), }; return Array::New(isolate, values, 6); } const BindingData::PackageConfig* BindingData::GetPackageJSON( Realm* realm, std::string_view path, ErrorContext* error_context) { auto binding_data = realm->GetBindingData<BindingData>(); auto cache_entry = binding_data->package_configs_.find(path.data()); if (cache_entry != binding_data->package_configs_.end()) { auto& cache_value = cache_entry->second; if (cache_value) { return &*cache_value; } // If we have a cache entry without a value, we've already // attempted to open and read this path and couldn't (it most // likely doesn't exist) return nullptr; } PackageConfig package_config{}; package_config.file_path = path; // No need to exclude BOM since simdjson will skip it. if (ReadFileSync(&package_config.raw_json, path.data()) < 0) { // Add `nullopt` to the package config cache so that we don't // need to open and attempt to read this path again binding_data->package_configs_.insert({std::string(path), std::nullopt}); return nullptr; } simdjson::ondemand::document document; simdjson::ondemand::object main_object; simdjson::error_code error = binding_data->json_parser.iterate(simdjson::pad(package_config.raw_json)) .get(document); const auto throw_invalid_package_config = [error_context, path, realm]() { if (error_context == nullptr) { THROW_ERR_INVALID_PACKAGE_CONFIG( realm->isolate(), "Invalid package config %s.", path.data()); } else if (error_context->base.has_value()) { auto file_url = ada::parse(error_context->base.value()); CHECK(file_url); auto file_path = url::FileURLToPath(realm->env(), *file_url); CHECK(file_path.has_value()); THROW_ERR_INVALID_PACKAGE_CONFIG( realm->isolate(), "Invalid package config %s while importing \"%s\" from %s.", path.data(), error_context->specifier.c_str(), file_path->c_str()); } else { THROW_ERR_INVALID_PACKAGE_CONFIG( realm->isolate(), "Invalid package config %s.", path.data()); } return nullptr; }; if (error || document.get_object().get(main_object)) { return throw_invalid_package_config(); } simdjson::ondemand::raw_json_string key; simdjson::ondemand::value value; std::string_view field_value; simdjson::ondemand::json_type field_type; for (auto field : main_object) { // Throw error if getting key or value fails. if (field.key().get(key) || field.value().get(value)) { return throw_invalid_package_config(); } // based on coverity using key with == derefs the raw value // avoid derefing if its null if (key.raw() == nullptr) continue; if (key == "name") { // Though there is a key "name" with a corresponding value, // the value may not be a string or could be an invalid JSON string if (value.get_string(package_config.name)) { return throw_invalid_package_config(); } } else if (key == "main") { // Omit all non-string values USE(value.get_string(package_config.main)); } else if (key == "exports") { if (value.type().get(field_type)) { return throw_invalid_package_config(); } switch (field_type) { case simdjson::ondemand::json_type::object: case simdjson::ondemand::json_type::array: { if (value.raw_json().get(field_value)) { return throw_invalid_package_config(); } package_config.exports = field_value; break; } case simdjson::ondemand::json_type::string: { if (value.get_string(package_config.exports)) { return throw_invalid_package_config(); } break; } default: break; } } else if (key == "imports") { if (value.type().get(field_type)) { return throw_invalid_package_config(); } switch (field_type) { case simdjson::ondemand::json_type::array: case simdjson::ondemand::json_type::object: { if (value.raw_json().get(field_value)) { return throw_invalid_package_config(); } package_config.imports = field_value; break; } case simdjson::ondemand::json_type::string: { if (value.get_string(package_config.imports)) { return throw_invalid_package_config(); } break; } default: break; } } else if (key == "type") { if (value.get_string().get(field_value)) { return throw_invalid_package_config(); } // Only update type if it is "commonjs" or "module" // The default value is "none" for backward compatibility. if (field_value == "commonjs" || field_value == "module") { package_config.type = field_value; } } else if (key == "scripts") { if (value.type().get(field_type)) { return throw_invalid_package_config(); } switch (field_type) { case simdjson::ondemand::json_type::object: { if (value.raw_json().get(field_value)) { return throw_invalid_package_config(); } package_config.scripts = field_value; break; } default: break; } } } // package_config could be quite large, so we should move it instead of // copying it. auto cached = binding_data->package_configs_.insert( {std::string(path), std::move(package_config)}); return &*cached.first->second; } void BindingData::ReadPackageJSON(const FunctionCallbackInfo<Value>& args) { CHECK_GE(args.Length(), 1); // path, [is_esm, base, specifier] CHECK(args[0]->IsString()); // path Realm* realm = Realm::GetCurrent(args); auto isolate = realm->isolate(); BufferValue path(isolate, args[0]); bool is_esm = args[1]->IsTrue(); auto error_context = ErrorContext(); if (is_esm) { CHECK(args[2]->IsUndefined() || args[2]->IsString()); // base CHECK(args[3]->IsString()); // specifier if (args[2]->IsString()) { Utf8Value base_value(isolate, args[2]); error_context.base = base_value.ToString(); } Utf8Value specifier(isolate, args[3]); error_context.specifier = specifier.ToString(); } THROW_IF_INSUFFICIENT_PERMISSIONS( realm->env(), permission::PermissionScope::kFileSystemRead, path.ToStringView()); ToNamespacedPath(realm->env(), &path); auto package_json = GetPackageJSON( realm, path.ToStringView(), is_esm ? &error_context : nullptr); if (package_json == nullptr) { return; } args.GetReturnValue().Set(package_json->Serialize(realm)); } const BindingData::PackageConfig* BindingData::TraverseParent( Realm* realm, const std::filesystem::path& check_path) { std::filesystem::path current_path = check_path; auto env = realm->env(); const bool is_permissions_enabled = env->permission()->enabled(); do { current_path = current_path.parent_path(); // We don't need to try "/" if (current_path.parent_path() == current_path) { break; } // Stop the search when the process doesn't have permissions // to walk upwards if (is_permissions_enabled) { if (!env->permission()->is_granted( env, permission::PermissionScope::kFileSystemRead, ConvertGenericPathToUTF8(current_path))) [[unlikely]] { return nullptr; } } // Check if the path ends with `/node_modules` if (current_path.filename() == "node_modules") { return nullptr; } auto package_json_path = current_path / "package.json"; auto package_json = GetPackageJSON(realm, ConvertPathToUTF8(package_json_path), nullptr); if (package_json != nullptr) { return package_json; } } while (true); return nullptr; } const std::filesystem::path BindingData::NormalizePath( Realm* realm, BufferValue* path_value) { // Check if the path has a trailing slash. If so, add it after // ToNamespacedPath() as it will be deleted by ToNamespacedPath() bool slashCheck = path_value->ToStringView().ends_with(kPathSeparator); ToNamespacedPath(realm->env(), path_value); auto path = path_value->ToPath(); if (slashCheck) { path /= ""; } return path; } void BindingData::GetNearestParentPackageJSON( const v8::FunctionCallbackInfo<v8::Value>& args) { CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); Realm* realm = Realm::GetCurrent(args); BufferValue path_value(realm->isolate(), args[0]); auto path = NormalizePath(realm, &path_value); auto package_json = TraverseParent(realm, path); if (package_json != nullptr) { args.GetReturnValue().Set(package_json->Serialize(realm)); } } void BindingData::GetNearestParentPackageJSONType( const FunctionCallbackInfo<Value>& args) { CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); Realm* realm = Realm::GetCurrent(args); BufferValue path_value(realm->isolate(), args[0]); auto path = NormalizePath(realm, &path_value); auto package_json = TraverseParent(realm, path); if (package_json == nullptr) { return; } Local<Value> value; if (ToV8Value(realm->context(), package_json->type).ToLocal(&value)) { args.GetReturnValue().Set(value); } } template <bool return_only_type> void BindingData::GetPackageScopeConfig( const FunctionCallbackInfo<Value>& args) { CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); Realm* realm = Realm::GetCurrent(args); Utf8Value resolved(realm->isolate(), args[0]); auto package_json_url_base = ada::parse(resolved.ToStringView()); if (!package_json_url_base) { url::ThrowInvalidURL(realm->env(), resolved.ToStringView(), std::nullopt); return; } auto package_json_url = ada::parse("./package.json", &package_json_url_base.value()); if (!package_json_url) { url::ThrowInvalidURL(realm->env(), "./package.json", resolved.ToString()); return; } std::string_view node_modules_package_path = "/node_modules/package.json"; auto error_context = ErrorContext(); error_context.is_esm = true; // TODO(@anonrig): Rewrite this function and avoid calling URL parser. while (true) { auto pathname = package_json_url->get_pathname(); if (pathname.ends_with(node_modules_package_path)) { break; } auto file_url = url::FileURLToPath(realm->env(), *package_json_url); if (!file_url) { url::ThrowInvalidURL(realm->env(), resolved.ToStringView(), std::nullopt); return; } BufferValue file_path_buf(realm->isolate(), String::NewFromUtf8(realm->isolate(), file_url->c_str(), NewStringType::kInternalized, file_url->size()) .ToLocalChecked()); ToNamespacedPath(realm->env(), &file_path_buf); error_context.specifier = resolved.ToString(); auto package_json = GetPackageJSON(realm, file_path_buf.ToStringView(), &error_context); if (package_json != nullptr) { if constexpr (return_only_type) { Local<Value> value; if (ToV8Value(realm->context(), package_json->type).ToLocal(&value)) { args.GetReturnValue().Set(value); } return; } else { return args.GetReturnValue().Set(package_json->Serialize(realm)); } } auto last_href = std::string(package_json_url->get_href()); auto last_pathname = std::string(package_json_url->get_pathname()); package_json_url = ada::parse("../package.json", &package_json_url.value()); if (!package_json_url) { url::ThrowInvalidURL(realm->env(), "../package.json", last_href); return; } // Terminates at root where ../package.json equals ../../package.json // (can't just check "/package.json" for Windows support). if (package_json_url->get_pathname() == last_pathname) { break; } } if constexpr (return_only_type) { return; } // If the package.json could not be found return a string containing a path // to the non-existent package.json file in the initial requested location auto package_json_url_as_path = url::FileURLToPath(realm->env(), *package_json_url); CHECK(package_json_url_as_path); Local<Value> ret; if (ToV8Value(realm->context(), *package_json_url_as_path, realm->isolate()) .ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void FlushCompileCache(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!args[0]->IsBoolean() && !args[0]->IsUndefined()) { THROW_ERR_INVALID_ARG_TYPE(env, "keepDeserializedCache should be a boolean"); return; } Debug(env, DebugCategory::COMPILE_CACHE, "[compile cache] module.flushCompileCache() requested.\n"); env->FlushCompileCache(); Debug(env, DebugCategory::COMPILE_CACHE, "[compile cache] module.flushCompileCache() finished.\n"); } void EnableCompileCache(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env, "cacheDir should be a string"); return; } EnableOption option = EnableOption::DEFAULT; if (args.Length() > 1 && args[1]->IsTrue()) { option = EnableOption::PORTABLE; } Utf8Value value(isolate, args[0]); CompileCacheEnableResult result = env->EnableCompileCache(*value, option); Local<Value> values[3]; values[0] = v8::Integer::New(isolate, static_cast<uint8_t>(result.status)); if (ToV8Value(context, result.message).ToLocal(&values[1]) && ToV8Value(context, result.cache_directory).ToLocal(&values[2])) { args.GetReturnValue().Set( Array::New(isolate, &values[0], arraysize(values))); } } void GetCompileCacheDir(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!env->use_compile_cache()) { args.GetReturnValue().Set(v8::String::Empty(isolate)); return; } Local<Value> ret; if (ToV8Value(context, env->compile_cache_handler()->cache_dir()) .ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void GetCompileCacheEntry(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); CHECK(args[0]->IsString()); // TODO(joyeecheung): accept buffer. CHECK(args[1]->IsString()); CHECK(args[2]->IsUint32()); Local<Context> context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); if (!env->use_compile_cache()) { return; } Local<String> source = args[0].As<String>(); Local<String> filename = args[1].As<String>(); CachedCodeType type = static_cast<CachedCodeType>(args[2].As<v8::Uint32>()->Value()); auto* cache_entry = env->compile_cache_handler()->GetOrInsert(source, filename, type); if (cache_entry == nullptr) { return; } v8::LocalVector<v8::Name> names(isolate, {FIXED_ONE_BYTE_STRING(isolate, "external")}); v8::LocalVector<v8::Value> values( isolate, {v8::External::New( isolate, cache_entry, v8::kExternalPointerTypeTagDefault)}); if (cache_entry->cache != nullptr) { Debug(env, DebugCategory::COMPILE_CACHE, "[compile cache] retrieving transpile cache for %s %s...", cache_entry->type_name(), cache_entry->source_filename); std::string_view cache( reinterpret_cast<const char*>(cache_entry->cache->data), cache_entry->cache->length); Local<Value> transpiled; // TODO(joyeecheung): convert with simdutf and into external strings if (!ToV8Value(context, cache).ToLocal(&transpiled)) { Debug(env, DebugCategory::COMPILE_CACHE, "failed\n"); return; } else { Debug(env, DebugCategory::COMPILE_CACHE, "success\n"); } names.push_back(FIXED_ONE_BYTE_STRING(isolate, "transpiled")); values.push_back(transpiled); } else { Debug(env, DebugCategory::COMPILE_CACHE, "[compile cache] no transpile cache for %s %s\n", cache_entry->type_name(), cache_entry->source_filename); } args.GetReturnValue().Set(Object::New( isolate, v8::Null(isolate), names.data(), values.data(), names.size())); } void SaveCompileCacheEntry(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); Environment* env = Environment::GetCurrent(context); DCHECK(env->use_compile_cache()); CHECK(args[0]->IsExternal()); CHECK(args[1]->IsString()); // TODO(joyeecheung): accept buffer. auto* cache_entry = static_cast<CompileCacheEntry*>( args[0].As<External>()->Value(v8::kExternalPointerTypeTagDefault)); Utf8Value utf8(isolate, args[1].As<String>()); env->compile_cache_handler()->MaybeSave(cache_entry, utf8.ToStringView()); } void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, Local<ObjectTemplate> target) { Isolate* isolate = isolate_data->isolate(); SetMethod(isolate, target, "readPackageJSON", ReadPackageJSON); SetMethod(isolate, target, "getNearestParentPackageJSONType", GetNearestParentPackageJSONType); SetMethod(isolate, target, "getNearestParentPackageJSON", GetNearestParentPackageJSON); SetMethod( isolate, target, "getPackageScopeConfig", GetPackageScopeConfig<false>); SetMethod(isolate, target, "getPackageType", GetPackageScopeConfig<true>); SetMethod(isolate, target, "enableCompileCache", EnableCompileCache); SetMethod(isolate, target, "getCompileCacheDir", GetCompileCacheDir); SetMethod(isolate, target, "flushCompileCache", FlushCompileCache); SetMethod(isolate, target, "getCompileCacheEntry", GetCompileCacheEntry); SetMethod(isolate, target, "saveCompileCacheEntry", SaveCompileCacheEntry); } void BindingData::CreatePerContextProperties(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) { Realm* realm = Realm::GetCurrent(context); realm->AddBindingData<BindingData>(target); Isolate* isolate = Isolate::GetCurrent(); LocalVector<Value> compile_cache_status_values(isolate); #define V(status) \ compile_cache_status_values.push_back( \ FIXED_ONE_BYTE_STRING(isolate, #status)); COMPILE_CACHE_STATUS(V) #undef V USE(target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "compileCacheStatus"), Array::New(isolate, compile_cache_status_values.data(), compile_cache_status_values.size()))); LocalVector<Name> cached_code_type_keys(isolate); LocalVector<Value> cached_code_type_values(isolate); #define V(type, value) \ cached_code_type_keys.push_back(FIXED_ONE_BYTE_STRING(isolate, #type)); \ cached_code_type_values.push_back(Integer::New(isolate, value)); \ DCHECK_EQ(value, cached_code_type_values.size() - 1); CACHED_CODE_TYPES(V) #undef V USE(target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cachedCodeTypes"), Object::New(isolate, Null(isolate), cached_code_type_keys.data(), cached_code_type_values.data(), cached_code_type_keys.size()))); LocalVector<Name> module_format_keys(isolate); LocalVector<Value> module_format_values(isolate); module_format_keys.push_back(FIXED_ONE_BYTE_STRING(isolate, "kCommonJS")); module_format_values.push_back( Integer::New(isolate, static_cast<int>(ModuleFormat::kCommonJS))); module_format_keys.push_back(FIXED_ONE_BYTE_STRING(isolate, "kModule")); module_format_values.push_back( Integer::New(isolate, static_cast<int>(ModuleFormat::kModule))); USE(target->Set(context, FIXED_ONE_BYTE_STRING(isolate, "moduleFormats"), Object::New(isolate, Null(isolate), module_format_keys.data(), module_format_values.data(), module_format_keys.size()))); } void BindingData::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(ReadPackageJSON); registry->Register(GetNearestParentPackageJSONType); registry->Register(GetNearestParentPackageJSON); registry->Register(GetPackageScopeConfig<false>); registry->Register(GetPackageScopeConfig<true>); registry->Register(EnableCompileCache); registry->Register(GetCompileCacheDir); registry->Register(FlushCompileCache); registry->Register(GetCompileCacheEntry); registry->Register(SaveCompileCacheEntry); } } // namespace modules } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL( modules, node::modules::BindingData::CreatePerContextProperties) NODE_BINDING_PER_ISOLATE_INIT( modules, node::modules::BindingData::CreatePerIsolateProperties) NODE_BINDING_EXTERNAL_REFERENCE( modules, node::modules::BindingData::RegisterExternalReferences)