From 888fae150f52902899015b61811dce5077a6be07 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 3 Feb 2020 13:31:12 +0200 Subject: [PATCH 1/9] module: "exports" resolution error handling refinements --- doc/api/errors.md | 20 ++ doc/api/esm.md | 75 +++--- lib/internal/errors.js | 27 +- lib/internal/modules/cjs/loader.js | 104 ++++---- src/env.h | 1 + src/module_wrap.cc | 247 ++++++++++-------- src/node_errors.h | 4 +- test/es-module/test-esm-exports.mjs | 54 ++-- .../node_modules/pkgexports/package.json | 4 + .../pkgexports/resolve-self-invalid.js | 1 + .../pkgexports/resolve-self-invalid.mjs | 1 + 11 files changed, 318 insertions(+), 220 deletions(-) create mode 100644 test/fixtures/node_modules/pkgexports/resolve-self-invalid.js create mode 100644 test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs diff --git a/doc/api/errors.md b/doc/api/errors.md index b186275807aee7..434f42d9863c4b 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1319,6 +1319,12 @@ An invalid HTTP token was supplied. An IP address is not valid. + +### `ERR_INVALID_MODULE_SPECIFIER` + +The imported module string is an invalid URL, package name, or package subpath +specifier. + ### `ERR_INVALID_OPT_VALUE` @@ -1334,6 +1340,12 @@ An invalid or unknown file encoding was passed. An invalid `package.json` file was found which failed parsing. + +### `ERR_INVALID_PACKAGE_TARGET` + +The `package.json` [exports][] field contains an invalid target mapping value +for the attempted module resolution. + ### `ERR_INVALID_PERFORMANCE_MARK` @@ -1640,6 +1652,13 @@ A non-context-aware native addon was loaded in a process that disallows them. A given value is out of the accepted range. + +### `ERR_PKG_PATH_NOT_EXPORTED` + +The `package.json` [exports][] field does not export the requested subpath. +Because exports are encapsulated, private internal modules that are not exported +cannot be imported through the package resolution, unless using an absolute URL. + ### `ERR_REQUIRE_ESM` @@ -2505,6 +2524,7 @@ such as `process.stdout.on('data')`. [crypto digest algorithm]: crypto.html#crypto_crypto_gethashes [domains]: domain.html [event emitter-based]: events.html#events_class_eventemitter +[exports]: esm.html#esm_package_exports [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor [policy]: policy.html [stream-based]: stream.html diff --git a/doc/api/esm.md b/doc/api/esm.md index f1378b59ad10a2..7abaab87e0f6f5 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -1391,6 +1391,17 @@ of these top-level routines unless stated otherwise. _defaultEnv_ is the conditional environment name priority array, `["node", "import"]`. +The resolver can throw the following errors: +* _Invalid Module Specifier_: Module specifier is an invalid URL, package name + or package subpath specifier. +* _Invalid Package Configuration_: package.json configuration is invalid or + contains an invalid configuration. +* _Invalid Package Target_: Package exports define a target module within the + package that is an invalid type or string target. +* _Package Path Not Exported_: Package exports do not define or permit a target + subpath in the package for the given module. +* _Module Not Found_: The package or module requested does not exist. +
Resolver algorithm specification @@ -1401,7 +1412,7 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Set _resolvedURL_ to the result of parsing and reserializing > _specifier_ as a URL. > 1. Otherwise, if _specifier_ starts with _"/"_, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then > 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to > _parentURL_. @@ -1411,7 +1422,7 @@ _defaultEnv_ is the conditional environment name priority array, > **PACKAGE_RESOLVE**(_specifier_, _parentURL_). > 1. If _resolvedURL_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_ > and _"%5C"_ respectively), then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. If _resolvedURL_ does not end with a trailing _"/"_ and the file at > _resolvedURL_ does not exist, then > 1. Throw a _Module Not Found_ error. @@ -1425,14 +1436,14 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Let _packageName_ be *undefined*. > 1. Let _packageSubpath_ be *undefined*. > 1. If _packageSpecifier_ is an empty string, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Otherwise, > 1. If _packageSpecifier_ does not contain a _"/"_ separator, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Set _packageName_ to the substring of _packageSpecifier_ > until the second _"/"_ separator or the end of the string. > 1. If _packageName_ starts with _"."_ or contains _"\\"_ or _"%"_, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Let _packageSubpath_ be _undefined_. > 1. If the length of _packageSpecifier_ is greater than the length of > _packageName_, then @@ -1440,7 +1451,7 @@ _defaultEnv_ is the conditional environment name priority array, > _packageSpecifier_ from the position at the length of _packageName_. > 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent > encoded strings for _"/"_ or _"\\"_, then -> 1. Throw an _Invalid Specifier_ error. +> 1. Throw an _Invalid Module Specifier_ error. > 1. Set _selfUrl_ to the result of > **SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_). > 1. If _selfUrl_ isn't empty, return _selfUrl_. @@ -1497,7 +1508,7 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Throw a _Module Not Found_ error. > 1. If _pjson.exports_ is not **null** or **undefined**, then > 1. If _exports_ is an Object with both a key starting with _"."_ and a key -> not starting with _"."_, throw an "Invalid Package Configuration" error. +> not starting with _"."_, throw an _Invalid Package Configuration_ error. > 1. If _pjson.exports_ is a String or Array, or an Object containing no > keys starting with _"."_, then > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, @@ -1506,6 +1517,7 @@ _defaultEnv_ is the conditional environment name priority array, > 1. Let _mainExport_ be the _"."_ property in _pjson.exports_. > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, > _mainExport_, _""_). +> 1. Throw a _Package Path Not Exported_ error. > 1. If _pjson.main_ is a String, then > 1. Let _resolvedMain_ be the URL resolution of _packageURL_, "/", and > _pjson.main_. @@ -1520,7 +1532,7 @@ _defaultEnv_ is the conditional environment name priority array, **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, _exports_) > 1. If _exports_ is an Object with both a key starting with _"."_ and a key not -> starting with _"."_, throw an "Invalid Package Configuration" error. +> starting with _"."_, throw an _Invalid Package Configuration_ error. > 1. If _exports_ is an Object and all keys of _exports_ start with _"."_, then > 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_. > 1. If _packagePath_ is a key of _exports_, then @@ -1536,43 +1548,44 @@ _defaultEnv_ is the conditional environment name priority array, > of the length of _directory_. > 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, > _subpath_, _defaultEnv_). -> 1. Throw a _Module Not Found_ error. +> 1. Throw a _Package Path Not Exported_ error. **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_) -> 1. If _target_ is a String, then -> 1. If _target_ does not start with _"./"_, throw a _Module Not Found_ -> error. -> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_, -> throw a _Module Not Found_ error. -> 1. If _target_ or _subpath_ contain any _"node_modules"_ segments including -> _"node_modules"_ percent-encoding, throw a _Module Not Found_ error. +> 1.If _target_ is a String, then +> 1. If _target_ does not start with _"./"_ or contains any _"node_modules"_ +> segments including _"node_modules"_ percent-encoding, throw an +> _Invalid Package Target_ error. > 1. Let _resolvedTarget_ be the URL resolution of the concatenation of > _packageURL_ and _target_. -> 1. If _resolvedTarget_ is contained in _packageURL_, then -> 1. Let _resolved_ be the URL resolution of the concatenation of -> _subpath_ and _resolvedTarget_. -> 1. If _resolved_ is contained in _resolvedTarget_, then -> 1. Return _resolved_. +> 1. If _resolvedTarget_ is not contained in _packageURL_, throw an +> _Invalid Package Target_ error. +> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_, +> throw an _Invalid Module Specifier_ error. +> 1. Let _resolved_ be the URL resolution of the concatenation of +> _subpath_ and _resolvedTarget_. +> 1. If _resolved_ is not contained in _resolvedTarget_, throw an +> _Invalid Module Specifier_ error. +> 1. Return _resolved_. > 1. Otherwise, if _target_ is a non-null Object, then > 1. If _exports_ contains any index property keys, as defined in ECMA-262 > [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error. > 1. For each property _p_ of _target_, in object insertion order as, > 1. If _env_ contains an entry for _p_, then > 1. Let _targetValue_ be the value of the _p_ property in _target_. -> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_TARGET_RESOLVE** -> (_packageURL_, _targetValue_, _subpath_, _env_). -> 1. Assert: _resolved_ is a String. -> 1. Return _resolved_. +> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**( +> _packageURL_, _targetValue_, _subpath_, _env_), continuing the +> loop on any _Package Path Not Exported_ error. +> 1. Throw a _Package Path Not Exported_ error. > 1. Otherwise, if _target_ is an Array, then +> 1. If _target.length is zero, throw an _Invalid Package Target_ error. > 1. For each item _targetValue_ in _target_, do > 1. If _targetValue_ is an Array, continue the loop. -> 1. Let _resolved_ be the result of -> **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _targetValue_, -> _subpath_, _env_), continuing the loop on abrupt completion. -> 1. Assert: _resolved_ is a String. -> 1. Return _resolved_. -> 1. Throw a _Module Not Found_ error. +> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, +> _targetValue_, _subpath_, _env_), continuing the loop on any +> _Package Path Not Exported_ or _Invalid Package Target_ error. +> 1. Throw the last fallback resolution error. +> 1. Otherwise throw an _Invalid Package Target_ error. **ESM_FORMAT**(_url_) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 0d88a92eb41ecc..c0522de32da0b0 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -13,6 +13,7 @@ const { ArrayIsArray, Error, + JSON, Map, MathAbs, NumberIsInteger, @@ -22,6 +23,7 @@ const { SymbolFor, WeakMap, } = primordials; +const sep = process.platform === 'win32' ? '\\' : '/'; const messages = new Map(); const codes = {}; @@ -1073,6 +1075,11 @@ E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s', TypeError); E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent', TypeError); E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError); E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s', TypeError); +E('ERR_INVALID_MODULE_SPECIFIER', (pkgPath, subpath) => { + assert(subpath !== '.'); + return `Package subpath '${subpath}' is not a valid module request for the ` + + `"exports" resolution of ${pkgPath}${sep}package.json`; +}, TypeError); E('ERR_INVALID_OPT_VALUE', (name, value) => `The value "${String(value)}" is invalid for option "${name}"`, TypeError, @@ -1080,7 +1087,17 @@ E('ERR_INVALID_OPT_VALUE', (name, value) => E('ERR_INVALID_OPT_VALUE_ENCODING', 'The value "%s" is invalid for option "encoding"', TypeError); E('ERR_INVALID_PACKAGE_CONFIG', - 'Invalid package config for \'%s\', %s', Error); + `Invalid package config %s${sep}package.json, %s`, Error); +E('ERR_INVALID_PACKAGE_TARGET', (pkgPath, key, subpath, target) => { + if (key === '.') { + return `Invalid "exports" main target ${JSON.stringify(target)} defined ` + + `in the package config ${pkgPath}${sep}package.json`; + } else { + return `Invalid "exports" target ${JSON.stringify(target)} defined for '${ + key.slice(0, -subpath.length || key.length)}' in the package config ${ + pkgPath}${sep}package.json`; + } +}, Error); E('ERR_INVALID_PERFORMANCE_MARK', 'The "%s" performance mark has not been set', Error); E('ERR_INVALID_PROTOCOL', @@ -1225,6 +1242,14 @@ E('ERR_OUT_OF_RANGE', msg += ` It must be ${range}. Received ${received}`; return msg; }, RangeError); +E('ERR_PKG_PATH_NOT_EXPORTED', (pkgPath, subpath) => { + if (subpath === '.') { + return `No "exports" main resolved in ${pkgPath}${sep}package.json`; + } else { + return `Package subpath '${subpath}' is not defined by "exports" in ${ + pkgPath}${sep}package.json`; + } +}, Error); E('ERR_REQUIRE_ESM', (filename, parentPath = null, packageJsonPath = null) => { let msg = `Must use import to load ES Module: ${filename}`; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index ed0f201bdb2de7..f685090c896f65 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -85,6 +85,9 @@ const { ERR_INVALID_ARG_VALUE, ERR_INVALID_OPT_VALUE, ERR_INVALID_PACKAGE_CONFIG, + ERR_INVALID_PACKAGE_TARGET, + ERR_INVALID_MODULE_SPECIFIER, + ERR_PKG_PATH_NOT_EXPORTED, ERR_REQUIRE_ESM } = require('internal/errors').codes; const { validateString } = require('internal/validators'); @@ -501,13 +504,9 @@ function applyExports(basePath, expansion) { if (ObjectPrototypeHasOwnProperty(pkgExports, mappingKey)) { const mapping = pkgExports[mappingKey]; return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '', - basePath, mappingKey); + mappingKey); } - // Fallback to CJS main lookup when no main export is defined - if (mappingKey === '.') - return basePath; - let dirMatch = ''; for (const candidateKey of ObjectKeys(pkgExports)) { if (candidateKey[candidateKey.length - 1] !== '/') continue; @@ -521,18 +520,11 @@ function applyExports(basePath, expansion) { const mapping = pkgExports[dirMatch]; const subpath = StringPrototypeSlice(mappingKey, dirMatch.length); return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, - subpath, basePath, mappingKey); + subpath, mappingKey); } } - // Fallback to CJS main lookup when no main export is defined - if (mappingKey === '.') - return basePath; - // eslint-disable-next-line no-restricted-syntax - const e = new Error(`Package exports for '${basePath}' do not define ` + - `a '${mappingKey}' subpath`); - e.code = 'MODULE_NOT_FOUND'; - throw e; + throw new ERR_PKG_PATH_NOT_EXPORTED(basePath, mappingKey); } // This only applies to requests of a specific form: @@ -567,39 +559,53 @@ function isArrayIndex(p) { return n >= 0 && n < (2 ** 32) - 1; } -function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) { +function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { if (typeof target === 'string') { - if (target.startsWith('./') && - (subpath.length === 0 || target.endsWith('/'))) { - const resolvedTarget = new URL(target, pkgPath); - const pkgPathPath = pkgPath.pathname; - const resolvedTargetPath = resolvedTarget.pathname; - if (StringPrototypeStartsWith(resolvedTargetPath, pkgPathPath) && + let resolvedTarget, resolvedTargetPath; + const pkgPathPath = baseUrl.pathname; + if (target.startsWith('./')) { + resolvedTarget = new URL(target, baseUrl); + resolvedTargetPath = resolvedTarget.pathname; + if (!StringPrototypeStartsWith(resolvedTargetPath, pkgPathPath) || StringPrototypeIndexOf(resolvedTargetPath, '/node_modules/', - pkgPathPath.length - 1) === -1) { - const resolved = new URL(subpath, resolvedTarget); - const resolvedPath = resolved.pathname; - if (StringPrototypeStartsWith(resolvedPath, resolvedTargetPath) && - StringPrototypeIndexOf(resolvedPath, '/node_modules/', - pkgPathPath.length - 1) === -1) { - return fileURLToPath(resolved); - } - } + pkgPathPath.length - 1) !== -1) + resolvedTarget = undefined; } + if (subpath.length > 0 && !target.endsWith('/')) + resolvedTarget = undefined; + if (resolvedTarget === undefined) + throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname.slice(0, -1), + mappingKey, subpath, target); + const resolved = new URL(subpath, resolvedTarget); + const resolvedPath = resolved.pathname; + if (StringPrototypeStartsWith(resolvedPath, resolvedTargetPath) && + StringPrototypeIndexOf(resolvedPath, '/node_modules/', + pkgPathPath.length - 1) === -1) { + return fileURLToPath(resolved); + } + throw new ERR_INVALID_MODULE_SPECIFIER(baseUrl.pathname.slice(0, -1), + mappingKey); } else if (ArrayIsArray(target)) { + if (target.length === 0) + throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname.slice(0, -1), + mappingKey, subpath, target); for (const targetValue of target) { - if (ArrayIsArray(targetValue)) continue; try { - return resolveExportsTarget(pkgPath, targetValue, subpath, basePath, - mappingKey); + return resolveExportsTarget(baseUrl, targetValue, subpath, mappingKey); } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') throw e; + if (e.code !== 'ERR_PKG_PATH_NOT_EXPORTED' && + e.code !== 'ERR_INVALID_PACKAGE_TARGET') + throw e; } } + // Throw last fallback error + resolveExportsTarget(baseUrl, target[target.length - 1], subpath, + mappingKey); + assert(false); } else if (typeof target === 'object' && target !== null) { const keys = ObjectKeys(target); if (keys.some(isArrayIndex)) { - throw new ERR_INVALID_PACKAGE_CONFIG(basePath, '"exports" cannot ' + + throw new ERR_INVALID_PACKAGE_CONFIG(baseUrl, '"exports" cannot ' + 'contain numeric property keys.'); } for (const p of keys) { @@ -608,34 +614,26 @@ function resolveExportsTarget(pkgPath, target, subpath, basePath, mappingKey) { case 'require': try { emitExperimentalWarning('Conditional exports'); - const result = resolveExportsTarget(pkgPath, target[p], subpath, - basePath, mappingKey); - return result; + return resolveExportsTarget(baseUrl, target[p], subpath, + mappingKey); } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') throw e; + if (e.code !== 'ERR_PKG_PATH_NOT_EXPORTED') throw e; } break; case 'default': try { - return resolveExportsTarget(pkgPath, target.default, subpath, - basePath, mappingKey); + return resolveExportsTarget(baseUrl, target.default, subpath, + mappingKey); } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') throw e; + if (e.code !== 'ERR_PKG_PATH_NOT_EXPORTED') throw e; } } } + throw new ERR_PKG_PATH_NOT_EXPORTED(baseUrl.pathname.slice(0, -1), + mappingKey + subpath); } - let e; - if (mappingKey !== '.') { - // eslint-disable-next-line no-restricted-syntax - e = new Error(`Package exports for '${basePath}' do not define a ` + - `valid '${mappingKey}' target${subpath ? ' for ' + subpath : ''}`); - } else { - // eslint-disable-next-line no-restricted-syntax - e = new Error(`No valid exports main found for '${basePath}'`); - } - e.code = 'MODULE_NOT_FOUND'; - throw e; + throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname.slice(0, -1), + mappingKey, subpath, target); } Module._findPath = function(request, paths, isMain) { diff --git a/src/env.h b/src/env.h index 3b636dafd2c7d7..6d268e5779b7cd 100644 --- a/src/env.h +++ b/src/env.h @@ -221,6 +221,7 @@ constexpr size_t kFsStatsBufferLength = V(duration_string, "duration") \ V(emit_warning_string, "emitWarning") \ V(empty_object_string, "{}") \ + V(empty_string, "") \ V(encoding_string, "encoding") \ V(entries_string, "entries") \ V(entry_type_string, "entryType") \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 30a2bf96366bcf..50c554e28d799a 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -856,10 +856,20 @@ void ThrowExportsNotFound(Environment* env, const std::string& subpath, const URL& pjson_url, const URL& base) { - const std::string msg = "Package exports for " + - pjson_url.ToFilePath() + " do not define a '" + subpath + - "' subpath, imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + const std::string msg = "Package subpath '" + subpath + "' is not defined" + + " by \"exports\" in " + pjson_url.ToFilePath() + " imported from " + + base.ToFilePath(); + node::THROW_ERR_PKG_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, @@ -868,14 +878,15 @@ void ThrowExportsInvalid(Environment* env, const URL& pjson_url, const URL& base) { if (subpath.length()) { - const std::string msg = "Cannot resolve package exports target '" + target + - "' matched for '" + subpath + "' in " + pjson_url.ToFilePath() + - ", imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + 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 = "Cannot resolve package main '" + target + "' in" + - pjson_url.ToFilePath() + ", imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + 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()); } } @@ -885,14 +896,18 @@ void ThrowExportsInvalid(Environment* env, const URL& pjson_url, const URL& base) { Local target_string; - if (target->ToString(env->context()).ToLocal(&target_string)) { - 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); + if (target->IsObject()) { + target_string = v8::JSON::Stringify(env->context(), target.As(), + env->empty_string()).ToLocalChecked(); + } else { + target_string = target->ToString(env->context()).ToLocalChecked(); + } + 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 ResolveExportsTargetString(Environment* env, @@ -900,18 +915,13 @@ Maybe ResolveExportsTargetString(Environment* env, const std::string& subpath, const std::string& match, const URL& pjson_url, - const URL& base, - bool throw_invalid = true) { + const URL& base) { if (target.substr(0, 2) != "./") { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target, pjson_url, base); - } + ThrowExportsInvalid(env, match, target, pjson_url, base); return Nothing(); } if (subpath.length() > 0 && target.back() != '/') { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target, pjson_url, base); - } + ThrowExportsInvalid(env, match, target, pjson_url, base); return Nothing(); } URL resolved(target, pjson_url); @@ -920,9 +930,7 @@ Maybe ResolveExportsTargetString(Environment* env, if (resolved_path.find(pkg_path) != 0 || resolved_path.find("/node_modules/", pkg_path.length() - 1) != std::string::npos) { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target, pjson_url, base); - } + ThrowExportsInvalid(env, match, target, pjson_url, base); return Nothing(); } if (subpath.length() == 0) return Just(resolved); @@ -931,9 +939,7 @@ Maybe ResolveExportsTargetString(Environment* env, if (subpath_resolved_path.find(resolved_path) != 0 || subpath_resolved_path.find("/node_modules/", pkg_path.length() - 1) != std::string::npos) { - if (throw_invalid) { - ThrowExportsInvalid(env, match, target + subpath, pjson_url, base); - } + ThrowSubpathInvalid(env, match + subpath, pjson_url, base); return Nothing(); } return Just(subpath_resolved); @@ -955,7 +961,7 @@ bool IsArrayIndex(Environment* env, Local p) { if (!n->ToInteger(context).ToLocal(&cmp_integer)) { return false; } - return n_dbl > 0 && n_dbl < (2 ^ 32) - 1; + return n_dbl > 0 && n_dbl < (1LL << 32) - 1; } Maybe ResolveExportsTarget(Environment* env, @@ -963,15 +969,14 @@ Maybe ResolveExportsTarget(Environment* env, Local target, const std::string& subpath, const std::string& pkg_subpath, - const URL& base, - bool throw_invalid = true) { + const URL& base) { Isolate* isolate = env->isolate(); Local context = env->context(); if (target->IsString()) { Utf8Value target_utf8(isolate, target.As()); std::string target_str(*target_utf8, target_utf8.length()); Maybe resolved = ResolveExportsTargetString(env, target_str, subpath, - pkg_subpath, pjson_url, base, throw_invalid); + pkg_subpath, pjson_url, base); if (resolved.IsNothing()) { return Nothing(); } @@ -980,40 +985,50 @@ Maybe ResolveExportsTarget(Environment* env, Local target_arr = target.As(); const uint32_t length = target_arr->Length(); if (length == 0) { - if (throw_invalid) { - ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); - } + ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); return Nothing(); } for (uint32_t i = 0; i < length; i++) { auto target_item = target_arr->Get(context, i).ToLocalChecked(); - if (!target_item->IsArray()) { + { + TryCatchScope try_catch(env); Maybe resolved = ResolveExportsTarget(env, pjson_url, - target_item, subpath, pkg_subpath, base, false); - if (resolved.IsNothing()) continue; + target_item, 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_PKG_PATH_NOT_EXPORTED" || + code_str == "ERR_INVALID_PACKAGE_TARGET") { + continue; + } + try_catch.ReThrow(); + return Nothing(); + } + CHECK(!try_catch.HasCaught()); return FinalizeResolution(env, resolved.FromJust(), base); } } - if (throw_invalid) { - auto invalid = target_arr->Get(context, length - 1).ToLocalChecked(); - Maybe resolved = ResolveExportsTarget(env, pjson_url, invalid, - subpath, pkg_subpath, base, true); - CHECK(resolved.IsNothing()); - } + auto invalid = target_arr->Get(context, length - 1).ToLocalChecked(); + Maybe resolved = ResolveExportsTarget(env, pjson_url, invalid, + subpath, pkg_subpath, base); + CHECK(resolved.IsNothing()); return Nothing(); } else if (target->IsObject()) { Local target_obj = target.As(); Local target_obj_keys = target_obj->GetOwnPropertyNames(context).ToLocalChecked(); Local conditionalTarget; - bool matched = false; for (uint32_t i = 0; i < target_obj_keys->Length(); ++i) { Local key = target_obj_keys->Get(context, i).ToLocalChecked(); if (IsArrayIndex(env, key)) { - const std::string msg = "Invalid package config for " + - pjson_url.ToFilePath() + ", \"exports\" cannot contain numeric " + - "property keys."; + 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(); } @@ -1024,35 +1039,53 @@ Maybe ResolveExportsTarget(Environment* env, key->ToString(context).ToLocalChecked()); std::string key_str(*key_utf8, key_utf8.length()); if (key_str == "node" || key_str == "import") { - matched = true; conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); - Maybe resolved = ResolveExportsTarget(env, pjson_url, - conditionalTarget, subpath, pkg_subpath, base, false); - if (!resolved.IsNothing()) { + { + TryCatchScope try_catch(env); + Maybe 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_PKG_PATH_NOT_EXPORTED") continue; + try_catch.ReThrow(); + return Nothing(); + } + CHECK(!try_catch.HasCaught()); ProcessEmitExperimentalWarning(env, "Conditional exports"); return resolved; } } else if (key_str == "default") { - matched = true; conditionalTarget = target_obj->Get(context, key).ToLocalChecked(); - Maybe resolved = ResolveExportsTarget(env, pjson_url, - conditionalTarget, subpath, pkg_subpath, base, false); - if (!resolved.IsNothing()) { + { + TryCatchScope try_catch(env); + Maybe 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(); + } + CHECK(!try_catch.HasCaught()); ProcessEmitExperimentalWarning(env, "Conditional exports"); return resolved; } } } - if (matched && throw_invalid) { - Maybe resolved = ResolveExportsTarget(env, pjson_url, - conditionalTarget, subpath, pkg_subpath, base, true); - CHECK(resolved.IsNothing()); - return Nothing(); - } - } - if (throw_invalid) { - ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); + ThrowExportsNotFound(env, pkg_subpath, pjson_url, base); + return Nothing(); } + ThrowExportsInvalid(env, pkg_subpath, target, pjson_url, base); return Nothing(); } @@ -1074,8 +1107,8 @@ Maybe IsConditionalExportsMainSugar(Environment* env, if (i == 0) { isConditionalSugar = curIsConditionalSugar; } else if (isConditionalSugar != curIsConditionalSugar) { - const std::string msg = "Cannot resolve package exports in " + - pjson_url.ToFilePath() + ", imported from " + base.ToFilePath() + ". " + + 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."; @@ -1100,8 +1133,7 @@ Maybe PackageMainResolve(Environment* env, if (isConditionalExportsMainSugar.IsNothing()) return Nothing(); if (isConditionalExportsMainSugar.FromJust()) { - return ResolveExportsTarget(env, pjson_url, exports, "", "", base, - true); + return ResolveExportsTarget(env, pjson_url, exports, "", "", base); } else if (exports->IsObject()) { Local exports_obj = exports.As(); if (exports_obj->HasOwnProperty(env->context(), env->dot_string()) @@ -1109,10 +1141,12 @@ Maybe PackageMainResolve(Environment* env, Local target = exports_obj->Get(env->context(), env->dot_string()) .ToLocalChecked(); - return ResolveExportsTarget(env, pjson_url, target, "", "", base, - true); + return ResolveExportsTarget(env, pjson_url, target, "", "", base); } } + std::string msg = "No \"exports\" main resolved in " + + pjson_url.ToFilePath(); + node::THROW_ERR_PKG_PATH_NOT_EXPORTED(env, msg.c_str()); } if (pcfg.has_main == HasMain::Yes) { URL resolved(pcfg.main, pjson_url); @@ -1204,39 +1238,6 @@ Maybe PackageExportsResolve(Environment* env, return Nothing(); } -Maybe ResolveSelf(Environment* env, - const std::string& pkg_name, - const std::string& pkg_subpath, - const URL& base) { - 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 (auto it = env->package_json_cache.begin(); - it != env->package_json_cache.end(); - ++it) { - if (&it->second == pcfg) { - pjson_url = URL::FromFilePath(it->first); - found_pjson = true; - } - } - if (!found_pjson || pcfg->name != pkg_name) return Nothing(); - if (pcfg->exports.IsEmpty()) return Nothing(); - 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); - } - } - - return Nothing(); -} - Maybe PackageResolve(Environment* env, const std::string& specifier, const URL& base) { @@ -1277,10 +1278,32 @@ Maybe PackageResolve(Environment* env, pkg_subpath = "." + specifier.substr(sep_index); } - Maybe self_url = ResolveSelf(env, pkg_name, pkg_subpath, base); - if (self_url.IsJust()) { - ProcessEmitExperimentalWarning(env, "Package name self resolution"); - return self_url; + // 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 (auto it = env->package_json_cache.begin(); + it != env->package_json_cache.end(); + ++it) { + 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); diff --git a/src/node_errors.h b/src/node_errors.h index d56bf7ef5a5a53..ad163e90d5fa2c 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -43,7 +43,8 @@ void OnFatalError(const char* location, const char* message); V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \ V(ERR_INVALID_ARG_TYPE, TypeError) \ V(ERR_INVALID_MODULE_SPECIFIER, TypeError) \ - V(ERR_INVALID_PACKAGE_CONFIG, SyntaxError) \ + V(ERR_INVALID_PACKAGE_CONFIG, Error) \ + V(ERR_INVALID_PACKAGE_TARGET, Error) \ V(ERR_INVALID_TRANSFER_OBJECT, TypeError) \ V(ERR_MEMORY_ALLOCATION_FAILED, Error) \ V(ERR_MISSING_ARGS, TypeError) \ @@ -53,6 +54,7 @@ void OnFatalError(const char* location, const char* message); V(ERR_NON_CONTEXT_AWARE_DISABLED, Error) \ V(ERR_MODULE_NOT_FOUND, Error) \ V(ERR_OUT_OF_RANGE, RangeError) \ + V(ERR_PKG_PATH_NOT_EXPORTED, Error) \ V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \ V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \ V(ERR_STRING_TOO_LONG, Error) \ diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs index bdd4a975cf748e..3891663c06c92e 100644 --- a/test/es-module/test-esm-exports.mjs +++ b/test/es-module/test-esm-exports.mjs @@ -32,7 +32,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; ['pkgexports/resolve-self', isRequire ? { default: 'self-cjs' } : { default: 'self-mjs' }], // Resolve self sugar - ['pkgexports-sugar', { default: 'main' }] + ['pkgexports-sugar', { default: 'main' }], ]); for (const [validSpecifier, expected] of validSpecifiers) { @@ -53,48 +53,59 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; // Sugar cases still encapsulate ['pkgexports-sugar/not-exported.js', './not-exported.js'], ['pkgexports-sugar2/not-exported.js', './not-exported.js'], + // Conditional exports with no match are "not exported" errors + ['pkgexports/invalid1', './invalid1'], + ['pkgexports/invalid4', './invalid4'], ]); const invalidExports = new Map([ - // Even though 'pkgexports/sub/asdf.js' works, alternate "path-like" - // variants do not to prevent confusion and accidental loopholes. - ['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'], + // Directory mappings require a trailing / to work + ['pkgexports/missingtrailer/x', './missingtrailer/'], // This path steps back inside the package but goes through an exports // target that escapes the package, so we still catch that as invalid - ['pkgexports/belowdir/pkgexports/asdf.js', './belowdir/pkgexports/asdf.js'], + ['pkgexports/belowdir/pkgexports/asdf.js', './belowdir/'], // This target file steps below the package ['pkgexports/belowfile', './belowfile'], - // Directory mappings require a trailing / to work - ['pkgexports/missingtrailer/x', './missingtrailer/x'], // Invalid target handling ['pkgexports/null', './null'], - ['pkgexports/invalid1', './invalid1'], ['pkgexports/invalid2', './invalid2'], ['pkgexports/invalid3', './invalid3'], - ['pkgexports/invalid4', './invalid4'], // Missing / invalid fallbacks ['pkgexports/nofallback1', './nofallback1'], ['pkgexports/nofallback2', './nofallback2'], // Reaching into nested node_modules ['pkgexports/nodemodules', './nodemodules'], + // Self resolve invalid + ['pkgexports/resolve-self-invalid', './invalid2'], + ]); + + const invalidSpecifiers = new Map([ + // Even though 'pkgexports/sub/asdf.js' works, alternate "path-like" + // variants do not to prevent confusion and accidental loopholes. + ['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'], ]); for (const [specifier, subpath] of undefinedExports) { loadFixture(specifier).catch(mustCall((err) => { - strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND'); - assertStartsWith(err.message, 'Package exports'); - assertIncludes(err.message, `do not define a '${subpath}' subpath`); + strictEqual(err.code, 'ERR_PKG_PATH_NOT_EXPORTED'); + assertStartsWith(err.message, 'Package subpath '); + assertIncludes(err.message, subpath); })); } for (const [specifier, subpath] of invalidExports) { loadFixture(specifier).catch(mustCall((err) => { - strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND'); - assertStartsWith(err.message, (isRequire ? 'Package exports' : - 'Cannot resolve')); - assertIncludes(err.message, isRequire ? - `do not define a valid '${subpath}' target` : - `matched for '${subpath}'`); + strictEqual(err.code, 'ERR_INVALID_PACKAGE_TARGET'); + assertStartsWith(err.message, 'Invalid "exports"'); + assertIncludes(err.message, subpath); + })); + } + + for (const [specifier, subpath] of invalidSpecifiers) { + loadFixture(specifier).catch(mustCall((err) => { + strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER'); + assertStartsWith(err.message, 'Package subpath '); + assertIncludes(err.message, subpath); })); } @@ -102,8 +113,8 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; // of falling back to main if (isRequire) { loadFixture('pkgexports-main').catch(mustCall((err) => { - strictEqual(err.code, 'MODULE_NOT_FOUND'); - assertStartsWith(err.message, 'No valid export'); + strictEqual(err.code, 'ERR_PKG_PATH_NOT_EXPORTED'); + assertStartsWith(err.message, 'No "exports" main '); })); } @@ -130,8 +141,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; // Sugar conditional exports main mixed failure case loadFixture('pkgexports-sugar-fail').catch(mustCall((err) => { strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG'); - assertStartsWith(err.message, (isRequire ? 'Invalid package' : - 'Cannot resolve')); + assertStartsWith(err.message, 'Invalid package'); assertIncludes(err.message, '"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 ' + diff --git a/test/fixtures/node_modules/pkgexports/package.json b/test/fixtures/node_modules/pkgexports/package.json index 02e06f0ebe5f3c..7f417ad5457bfc 100644 --- a/test/fixtures/node_modules/pkgexports/package.json +++ b/test/fixtures/node_modules/pkgexports/package.json @@ -35,6 +35,10 @@ "./resolve-self": { "require": "./resolve-self.js", "import": "./resolve-self.mjs" + }, + "./resolve-self-invalid": { + "require": "./resolve-self-invalid.js", + "import": "./resolve-self-invalid.mjs" } } } diff --git a/test/fixtures/node_modules/pkgexports/resolve-self-invalid.js b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.js new file mode 100644 index 00000000000000..c3ebf76fc1b2f3 --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.js @@ -0,0 +1 @@ +require('pkg-exports/invalid2'); diff --git a/test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs new file mode 100644 index 00000000000000..1edbf62c4b0114 --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/resolve-self-invalid.mjs @@ -0,0 +1 @@ +import 'pkg-exports/invalid2'; From e59a16ebce194f26b190b120780e5a969d403e1b Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 4 Feb 2020 10:17:17 +0200 Subject: [PATCH 2/9] Apply suggestions from code review Co-Authored-By: Jordan Harband --- lib/internal/errors.js | 2 +- lib/internal/modules/cjs/loader.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index c0522de32da0b0..f64135602e02fc 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1094,7 +1094,7 @@ E('ERR_INVALID_PACKAGE_TARGET', (pkgPath, key, subpath, target) => { `in the package config ${pkgPath}${sep}package.json`; } else { return `Invalid "exports" target ${JSON.stringify(target)} defined for '${ - key.slice(0, -subpath.length || key.length)}' in the package config ${ + StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the package config ${ pkgPath}${sep}package.json`; } }, Error); diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index f685090c896f65..2e84a88770ed82 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -563,7 +563,7 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { if (typeof target === 'string') { let resolvedTarget, resolvedTargetPath; const pkgPathPath = baseUrl.pathname; - if (target.startsWith('./')) { + if (StringPrototypeStartsWith(target, './')) { resolvedTarget = new URL(target, baseUrl); resolvedTargetPath = resolvedTarget.pathname; if (!StringPrototypeStartsWith(resolvedTargetPath, pkgPathPath) || @@ -571,10 +571,10 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { pkgPathPath.length - 1) !== -1) resolvedTarget = undefined; } - if (subpath.length > 0 && !target.endsWith('/')) + if (subpath.length > 0 && ! StringPrototypeEndsWith(target, '/')) resolvedTarget = undefined; if (resolvedTarget === undefined) - throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname.slice(0, -1), + throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target); const resolved = new URL(subpath, resolvedTarget); const resolvedPath = resolved.pathname; @@ -583,11 +583,11 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { pkgPathPath.length - 1) === -1) { return fileURLToPath(resolved); } - throw new ERR_INVALID_MODULE_SPECIFIER(baseUrl.pathname.slice(0, -1), + throw new ERR_INVALID_MODULE_SPECIFIER(StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey); } else if (ArrayIsArray(target)) { if (target.length === 0) - throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname.slice(0, -1), + throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target); for (const targetValue of target) { try { @@ -629,10 +629,10 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { } } } - throw new ERR_PKG_PATH_NOT_EXPORTED(baseUrl.pathname.slice(0, -1), + throw new ERR_PKG_PATH_NOT_EXPORTED(StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath); } - throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname.slice(0, -1), + throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target); } From 56269a6f79ae7257413b3db7a39cff8d346b4d4d Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 4 Feb 2020 10:28:51 +0200 Subject: [PATCH 3/9] linting fixest status --- lib/internal/errors.js | 5 +++-- lib/internal/modules/cjs/loader.js | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index f64135602e02fc..f7128e0b4faba8 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -19,6 +19,7 @@ const { NumberIsInteger, ObjectDefineProperty, ObjectKeys, + StringPrototypeSlice, Symbol, SymbolFor, WeakMap, @@ -1094,8 +1095,8 @@ E('ERR_INVALID_PACKAGE_TARGET', (pkgPath, key, subpath, target) => { `in the package config ${pkgPath}${sep}package.json`; } else { return `Invalid "exports" target ${JSON.stringify(target)} defined for '${ - StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the package config ${ - pkgPath}${sep}package.json`; + StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the ` + + `package config ${pkgPath}${sep}package.json`; } }, Error); E('ERR_INVALID_PERFORMANCE_MARK', diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 2e84a88770ed82..63d1dbd5641806 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -571,11 +571,11 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { pkgPathPath.length - 1) !== -1) resolvedTarget = undefined; } - if (subpath.length > 0 && ! StringPrototypeEndsWith(target, '/')) + if (subpath.length > 0 && target[target.length - 1] !== '/') resolvedTarget = undefined; if (resolvedTarget === undefined) - throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname, 0, -1), - mappingKey, subpath, target); + throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname + , 0, -1), mappingKey, subpath, target); const resolved = new URL(subpath, resolvedTarget); const resolvedPath = resolved.pathname; if (StringPrototypeStartsWith(resolvedPath, resolvedTargetPath) && @@ -583,12 +583,12 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { pkgPathPath.length - 1) === -1) { return fileURLToPath(resolved); } - throw new ERR_INVALID_MODULE_SPECIFIER(StringPrototypeSlice(baseUrl.pathname, 0, -1), - mappingKey); + throw new ERR_INVALID_MODULE_SPECIFIER(StringPrototypeSlice(baseUrl.pathname + , 0, -1), mappingKey); } else if (ArrayIsArray(target)) { if (target.length === 0) - throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname, 0, -1), - mappingKey, subpath, target); + throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname + , 0, -1), mappingKey, subpath, target); for (const targetValue of target) { try { return resolveExportsTarget(baseUrl, targetValue, subpath, mappingKey); @@ -629,11 +629,11 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { } } } - throw new ERR_PKG_PATH_NOT_EXPORTED(StringPrototypeSlice(baseUrl.pathname, 0, -1), - mappingKey + subpath); + throw new ERR_PKG_PATH_NOT_EXPORTED( + StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath); } - throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname, 0, -1), - mappingKey, subpath, target); + throw new ERR_INVALID_PACKAGE_TARGET( + StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target); } Module._findPath = function(request, paths, isMain) { From fb97d9738839515e2ddf3072f83e48e767a5384c Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 4 Feb 2020 10:21:25 +0200 Subject: [PATCH 4/9] unabbreviate ERR_PACKAGE_PATH_NOT_EXPORTED --- doc/api/errors.md | 4 ++-- lib/internal/errors.js | 4 ++-- lib/internal/modules/cjs/loader.js | 12 ++++++------ src/module_wrap.cc | 8 ++++---- src/node_errors.h | 2 +- test/es-module/test-esm-exports.mjs | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 434f42d9863c4b..8a1af870b1d6d3 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1652,8 +1652,8 @@ A non-context-aware native addon was loaded in a process that disallows them. A given value is out of the accepted range. - -### `ERR_PKG_PATH_NOT_EXPORTED` + +### `ERR_PACKAGE_PATH_NOT_EXPORTED` The `package.json` [exports][] field does not export the requested subpath. Because exports are encapsulated, private internal modules that are not exported diff --git a/lib/internal/errors.js b/lib/internal/errors.js index f7128e0b4faba8..ee93c5cabcd7bf 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -24,7 +24,7 @@ const { SymbolFor, WeakMap, } = primordials; -const sep = process.platform === 'win32' ? '\\' : '/'; +const { sep } = require('path'); const messages = new Map(); const codes = {}; @@ -1243,7 +1243,7 @@ E('ERR_OUT_OF_RANGE', msg += ` It must be ${range}. Received ${received}`; return msg; }, RangeError); -E('ERR_PKG_PATH_NOT_EXPORTED', (pkgPath, subpath) => { +E('ERR_PACKAGE_PATH_NOT_EXPORTED', (pkgPath, subpath) => { if (subpath === '.') { return `No "exports" main resolved in ${pkgPath}${sep}package.json`; } else { diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 63d1dbd5641806..7d6d01194f0841 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -87,7 +87,7 @@ const { ERR_INVALID_PACKAGE_CONFIG, ERR_INVALID_PACKAGE_TARGET, ERR_INVALID_MODULE_SPECIFIER, - ERR_PKG_PATH_NOT_EXPORTED, + ERR_PACKAGE_PATH_NOT_EXPORTED, ERR_REQUIRE_ESM } = require('internal/errors').codes; const { validateString } = require('internal/validators'); @@ -524,7 +524,7 @@ function applyExports(basePath, expansion) { } } - throw new ERR_PKG_PATH_NOT_EXPORTED(basePath, mappingKey); + throw new ERR_PACKAGE_PATH_NOT_EXPORTED(basePath, mappingKey); } // This only applies to requests of a specific form: @@ -593,7 +593,7 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { try { return resolveExportsTarget(baseUrl, targetValue, subpath, mappingKey); } catch (e) { - if (e.code !== 'ERR_PKG_PATH_NOT_EXPORTED' && + if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && e.code !== 'ERR_INVALID_PACKAGE_TARGET') throw e; } @@ -617,7 +617,7 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { return resolveExportsTarget(baseUrl, target[p], subpath, mappingKey); } catch (e) { - if (e.code !== 'ERR_PKG_PATH_NOT_EXPORTED') throw e; + if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e; } break; case 'default': @@ -625,11 +625,11 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { return resolveExportsTarget(baseUrl, target.default, subpath, mappingKey); } catch (e) { - if (e.code !== 'ERR_PKG_PATH_NOT_EXPORTED') throw e; + if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e; } } } - throw new ERR_PKG_PATH_NOT_EXPORTED( + throw new ERR_PACKAGE_PATH_NOT_EXPORTED( StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath); } throw new ERR_INVALID_PACKAGE_TARGET( diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 50c554e28d799a..c604ef5df7e465 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -859,7 +859,7 @@ void ThrowExportsNotFound(Environment* env, const std::string msg = "Package subpath '" + subpath + "' is not defined" + " by \"exports\" in " + pjson_url.ToFilePath() + " imported from " + base.ToFilePath(); - node::THROW_ERR_PKG_PATH_NOT_EXPORTED(env, msg.c_str()); + node::THROW_ERR_PACKAGE_PATH_NOT_EXPORTED(env, msg.c_str()); } void ThrowSubpathInvalid(Environment* env, @@ -1001,7 +1001,7 @@ Maybe ResolveExportsTarget(Environment* env, Utf8Value code_utf8(env->isolate(), code->ToString(context).ToLocalChecked()); std::string code_str(*code_utf8, code_utf8.length()); - if (code_str == "ERR_PKG_PATH_NOT_EXPORTED" || + if (code_str == "ERR_PACKAGE_PATH_NOT_EXPORTED" || code_str == "ERR_INVALID_PACKAGE_TARGET") { continue; } @@ -1051,7 +1051,7 @@ Maybe ResolveExportsTarget(Environment* env, Utf8Value code_utf8(env->isolate(), code->ToString(context).ToLocalChecked()); std::string code_str(*code_utf8, code_utf8.length()); - if (code_str == "ERR_PKG_PATH_NOT_EXPORTED") continue; + if (code_str == "ERR_PACKAGE_PATH_NOT_EXPORTED") continue; try_catch.ReThrow(); return Nothing(); } @@ -1146,7 +1146,7 @@ Maybe PackageMainResolve(Environment* env, } std::string msg = "No \"exports\" main resolved in " + pjson_url.ToFilePath(); - node::THROW_ERR_PKG_PATH_NOT_EXPORTED(env, msg.c_str()); + node::THROW_ERR_PACKAGE_PATH_NOT_EXPORTED(env, msg.c_str()); } if (pcfg.has_main == HasMain::Yes) { URL resolved(pcfg.main, pjson_url); diff --git a/src/node_errors.h b/src/node_errors.h index ad163e90d5fa2c..a05ce8f6bfb1bd 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -54,7 +54,7 @@ void OnFatalError(const char* location, const char* message); V(ERR_NON_CONTEXT_AWARE_DISABLED, Error) \ V(ERR_MODULE_NOT_FOUND, Error) \ V(ERR_OUT_OF_RANGE, RangeError) \ - V(ERR_PKG_PATH_NOT_EXPORTED, Error) \ + V(ERR_PACKAGE_PATH_NOT_EXPORTED, Error) \ V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \ V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \ V(ERR_STRING_TOO_LONG, Error) \ diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs index 3891663c06c92e..7dbc963502824e 100644 --- a/test/es-module/test-esm-exports.mjs +++ b/test/es-module/test-esm-exports.mjs @@ -87,7 +87,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; for (const [specifier, subpath] of undefinedExports) { loadFixture(specifier).catch(mustCall((err) => { - strictEqual(err.code, 'ERR_PKG_PATH_NOT_EXPORTED'); + strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED'); assertStartsWith(err.message, 'Package subpath '); assertIncludes(err.message, subpath); })); @@ -113,7 +113,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; // of falling back to main if (isRequire) { loadFixture('pkgexports-main').catch(mustCall((err) => { - strictEqual(err.code, 'ERR_PKG_PATH_NOT_EXPORTED'); + strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED'); assertStartsWith(err.message, 'No "exports" main '); })); } From baec562a4372a38d16085dc21bc66098af372621 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 4 Feb 2020 10:35:28 +0200 Subject: [PATCH 5/9] fixup json stringify --- lib/internal/errors.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index ee93c5cabcd7bf..303271ce1e1110 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -13,7 +13,7 @@ const { ArrayIsArray, Error, - JSON, + JSONStringify, Map, MathAbs, NumberIsInteger, @@ -1091,10 +1091,10 @@ E('ERR_INVALID_PACKAGE_CONFIG', `Invalid package config %s${sep}package.json, %s`, Error); E('ERR_INVALID_PACKAGE_TARGET', (pkgPath, key, subpath, target) => { if (key === '.') { - return `Invalid "exports" main target ${JSON.stringify(target)} defined ` + + return `Invalid "exports" main target ${JSONStringify(target)} defined ` + `in the package config ${pkgPath}${sep}package.json`; } else { - return `Invalid "exports" target ${JSON.stringify(target)} defined for '${ + return `Invalid "exports" target ${JSONStringify(target)} defined for '${ StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the ` + `package config ${pkgPath}${sep}package.json`; } From 368546881e9f250ec56caebd7ee606658bc7cf63 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 4 Feb 2020 11:18:49 +0200 Subject: [PATCH 6/9] fixup circular ref --- lib/internal/errors.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 303271ce1e1110..bb2629d65d2873 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -24,7 +24,6 @@ const { SymbolFor, WeakMap, } = primordials; -const { sep } = require('path'); const messages = new Map(); const codes = {}; @@ -713,6 +712,8 @@ module.exports = { fatalExceptionStackEnhancers }; +const { sep } = require('path'); + // To declare an error message, use the E(sym, val, def) function above. The sym // must be an upper case string. The val can be either a function or a string. // The def must be an error class. From cac7c603189e3242b2a39524fe88489aa670947b Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 4 Feb 2020 12:23:06 +0200 Subject: [PATCH 7/9] reinline sep --- lib/internal/errors.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index bb2629d65d2873..d9add5353e017a 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -25,6 +25,8 @@ const { WeakMap, } = primordials; +const sep = process.platform === 'win32' ? '\\' : '/'; + const messages = new Map(); const codes = {}; @@ -712,8 +714,6 @@ module.exports = { fatalExceptionStackEnhancers }; -const { sep } = require('path'); - // To declare an error message, use the E(sym, val, def) function above. The sym // must be an upper case string. The val can be either a function or a string. // The def must be an error class. From 61c5de0077ffb01968b1da0b1b0d18ee09f3b574 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sat, 8 Feb 2020 12:20:02 +0200 Subject: [PATCH 8/9] pr feedback --- src/env.h | 1 - src/module_wrap.cc | 25 ++++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/env.h b/src/env.h index 6d268e5779b7cd..3b636dafd2c7d7 100644 --- a/src/env.h +++ b/src/env.h @@ -221,7 +221,6 @@ constexpr size_t kFsStatsBufferLength = V(duration_string, "duration") \ V(emit_warning_string, "emitWarning") \ V(empty_object_string, "{}") \ - V(empty_string, "") \ V(encoding_string, "encoding") \ V(entries_string, "entries") \ V(entry_type_string, "entryType") \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index c604ef5df7e465..6e8453d4390fd8 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -897,10 +897,12 @@ void ThrowExportsInvalid(Environment* env, const URL& base) { Local target_string; if (target->IsObject()) { - target_string = v8::JSON::Stringify(env->context(), target.As(), - env->empty_string()).ToLocalChecked(); + if (!v8::JSON::Stringify(env->context(), target.As(), + v8::String::Empty(env->isolate())).ToLocal(&target_string)) + return; } else { - target_string = target->ToString(env->context()).ToLocalChecked(); + 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()); @@ -1000,9 +1002,8 @@ Maybe ResolveExportsTarget(Environment* env, 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" || - code_str == "ERR_INVALID_PACKAGE_TARGET") { + if (strcmp(*code_utf8, "ERR_PACKAGE_PATH_NOT_EXPORTED") == 0 || + strcmp(*code_utf8, "ERR_INVALID_PACKAGE_TARGET") == 0) { continue; } try_catch.ReThrow(); @@ -1050,8 +1051,8 @@ Maybe ResolveExportsTarget(Environment* env, 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; + if (strcmp(*code_utf8, "ERR_PACKAGE_PATH_NOT_EXPORTED") == 0) + continue; try_catch.ReThrow(); return Nothing(); } @@ -1286,11 +1287,9 @@ Maybe PackageResolve(Environment* env, // while executing GetPackageScopeConfig URL pjson_url(""); bool found_pjson = false; - for (auto it = env->package_json_cache.begin(); - it != env->package_json_cache.end(); - ++it) { - if (&it->second == pcfg) { - pjson_url = URL::FromFilePath(it->first); + for (const auto& it : env->package_json_cache) { + if (&it.second == pcfg) { + pjson_url = URL::FromFilePath(it.first); found_pjson = true; } } From f6d4dac015756397aeaf21117dc2a03752b5db74 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 10 Feb 2020 15:11:45 +0200 Subject: [PATCH 9/9] fixup local checks --- src/module_wrap.cc | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 6e8453d4390fd8..51abd649bcc225 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -997,11 +997,18 @@ Maybe ResolveExportsTarget(Environment* env, Maybe resolved = ResolveExportsTarget(env, pjson_url, target_item, 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()); + CHECK(try_catch.HasCaught()); + if (try_catch.Exception().IsEmpty()) return Nothing(); + Local e; + if (!try_catch.Exception()->ToObject(context).ToLocal(&e)) + return Nothing(); + Local code; + if (!e->Get(context, env->code_string()).ToLocal(&code)) + return Nothing(); + Local code_string; + if (!code->ToString(context).ToLocal(&code_string)) + return Nothing(); + 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; @@ -1046,11 +1053,18 @@ Maybe ResolveExportsTarget(Environment* env, Maybe 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()); + CHECK(try_catch.HasCaught()); + if (try_catch.Exception().IsEmpty()) return Nothing(); + Local e; + if (!try_catch.Exception()->ToObject(context).ToLocal(&e)) + return Nothing(); + Local code; + if (!e->Get(context, env->code_string()).ToLocal(&code)) + return Nothing(); + Local code_string; + if (!code->ToString(context).ToLocal(&code_string)) + return Nothing(); + Utf8Value code_utf8(env->isolate(), code_string); if (strcmp(*code_utf8, "ERR_PACKAGE_PATH_NOT_EXPORTED") == 0) continue; try_catch.ReThrow();