From 0f489bce13de4b4a90c9c999dfdccd3bc8646087 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 11 Jun 2022 14:45:31 +0100 Subject: [PATCH 1/4] tools: add `avoid-prototype-pollution` lint rule PR-URL: https://github.com/nodejs/node/pull/43308 Reviewed-By: Rich Trott --- lib/.eslintrc.yaml | 1 + lib/crypto.js | 3 + lib/internal/bootstrap/node.js | 4 + lib/internal/bootstrap/pre_execution.js | 3 + lib/internal/per_context/domexception.js | 2 + lib/internal/per_context/primordials.js | 10 +- lib/readline.js | 1 + .../test-eslint-avoid-prototype-pollution.js | 147 ++++++++++++++++++ .../eslint-rules/avoid-prototype-pollution.js | 92 +++++++++++ 9 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 test/parallel/test-eslint-avoid-prototype-pollution.js create mode 100644 tools/eslint-rules/avoid-prototype-pollution.js diff --git a/lib/.eslintrc.yaml b/lib/.eslintrc.yaml index 9714c9c4f64df2..e7b27e5cff8482 100644 --- a/lib/.eslintrc.yaml +++ b/lib/.eslintrc.yaml @@ -116,6 +116,7 @@ rules: - name: SubtleCrypto message: Use `const { SubtleCrypto } = require('internal/crypto/webcrypto');` instead of the global. # Custom rules in tools/eslint-rules + node-core/avoid-prototype-pollution: error node-core/lowercase-name-for-primitive: error node-core/non-ascii-character: error node-core/no-array-destructuring: error diff --git a/lib/crypto.js b/lib/crypto.js index be1cf7a99cf3a5..3ec03cbe73154f 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -297,6 +297,7 @@ ObjectDefineProperties(module.exports, { // Aliases for randomBytes are deprecated. // The ecosystem needs those to exist for backwards compatibility. prng: { + __proto__: null, enumerable: false, configurable: true, writable: true, @@ -305,6 +306,7 @@ ObjectDefineProperties(module.exports, { randomBytes }, pseudoRandomBytes: { + __proto__: null, enumerable: false, configurable: true, writable: true, @@ -314,6 +316,7 @@ ObjectDefineProperties(module.exports, { randomBytes }, rng: { + __proto__: null, enumerable: false, configurable: true, writable: true, diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 75c81c04575a0b..67cbdb9db09ca7 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -496,6 +496,7 @@ function createGlobalConsole(consoleFromVM) { // https://heycam.github.io/webidl/#es-namespaces function exposeNamespace(target, name, namespaceObject) { ObjectDefineProperty(target, name, { + __proto__: null, writable: true, enumerable: false, configurable: true, @@ -506,6 +507,7 @@ function exposeNamespace(target, name, namespaceObject) { // https://heycam.github.io/webidl/#es-interfaces function exposeInterface(target, name, interfaceObject) { ObjectDefineProperty(target, name, { + __proto__: null, writable: true, enumerable: false, configurable: true, @@ -516,6 +518,7 @@ function exposeInterface(target, name, interfaceObject) { // https://heycam.github.io/webidl/#define-the-operations function defineOperation(target, name, method) { ObjectDefineProperty(target, name, { + __proto__: null, writable: true, enumerable: true, configurable: true, @@ -526,6 +529,7 @@ function defineOperation(target, name, method) { // https://heycam.github.io/webidl/#Replaceable function defineReplacableAttribute(target, name, value) { ObjectDefineProperty(target, name, { + __proto__: null, writable: true, enumerable: true, configurable: true, diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 19d81549b4fd92..2f6af18887f33d 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -209,18 +209,21 @@ function setupWebCrypto() { if (internalBinding('config').hasOpenSSL) { webcrypto ??= require('internal/crypto/webcrypto'); ObjectDefineProperty(globalThis, 'Crypto', { + __proto__: null, writable: true, enumerable: false, configurable: true, value: webcrypto.Crypto }); ObjectDefineProperty(globalThis, 'CryptoKey', { + __proto__: null, writable: true, enumerable: false, configurable: true, value: webcrypto.CryptoKey }); ObjectDefineProperty(globalThis, 'SubtleCrypto', { + __proto__: null, writable: true, enumerable: false, configurable: true, diff --git a/lib/internal/per_context/domexception.js b/lib/internal/per_context/domexception.js index 6c3ed1437e5f9d..444e055dc86f74 100644 --- a/lib/internal/per_context/domexception.js +++ b/lib/internal/per_context/domexception.js @@ -18,12 +18,14 @@ function throwInvalidThisError(Base, type) { const key = 'ERR_INVALID_THIS'; ObjectDefineProperties(err, { message: { + __proto__: null, value: `Value of "this" must be of ${type}`, enumerable: false, writable: true, configurable: true, }, toString: { + __proto__: null, value() { return `${this.name} [${key}]: ${this.message}`; }, diff --git a/lib/internal/per_context/primordials.js b/lib/internal/per_context/primordials.js index f26193ab0505e0..7c6f513d9ccebd 100644 --- a/lib/internal/per_context/primordials.js +++ b/lib/internal/per_context/primordials.js @@ -78,7 +78,7 @@ function copyPropsRenamed(src, dest, prefix) { copyAccessor(dest, prefix, newKey, desc); } else { const name = `${prefix}${newKey}`; - ReflectDefineProperty(dest, name, desc); + ReflectDefineProperty(dest, name, { __proto__: null, ...desc }); if (varargsMethods.includes(name)) { ReflectDefineProperty(dest, `${name}Apply`, { __proto__: null, @@ -105,7 +105,7 @@ function copyPropsRenamedBound(src, dest, prefix) { } const name = `${prefix}${newKey}`; - ReflectDefineProperty(dest, name, desc); + ReflectDefineProperty(dest, name, { __proto__: null, ...desc }); if (varargsMethods.includes(name)) { ReflectDefineProperty(dest, `${name}Apply`, { __proto__: null, @@ -129,7 +129,7 @@ function copyPrototype(src, dest, prefix) { } const name = `${prefix}${newKey}`; - ReflectDefineProperty(dest, name, desc); + ReflectDefineProperty(dest, name, { __proto__: null, ...desc }); if (varargsMethods.includes(name)) { ReflectDefineProperty(dest, `${name}Apply`, { __proto__: null, @@ -313,7 +313,7 @@ const copyProps = (src, dest) => { ReflectDefineProperty( dest, key, - ReflectGetOwnPropertyDescriptor(src, key)); + { __proto__: null, ...ReflectGetOwnPropertyDescriptor(src, key) }); } }); }; @@ -341,7 +341,7 @@ const makeSafe = (unsafe, safe) => { return new SafeIterator(this); }; } - ReflectDefineProperty(safe.prototype, key, desc); + ReflectDefineProperty(safe.prototype, key, { __proto__: null, ...desc }); } }); } else { diff --git a/lib/readline.js b/lib/readline.js index 7e55c99e65be23..f80f383170814b 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -368,6 +368,7 @@ ObjectSetPrototypeOf(Interface.prototype, EventEmitter.prototype); ObjectSetPrototypeOf(Interface, EventEmitter); ObjectDefineProperty(Interface.prototype, 'columns', { + __proto__: null, configurable: true, enumerable: true, get: function() { diff --git a/test/parallel/test-eslint-avoid-prototype-pollution.js b/test/parallel/test-eslint-avoid-prototype-pollution.js new file mode 100644 index 00000000000000..047def545e9a90 --- /dev/null +++ b/test/parallel/test-eslint-avoid-prototype-pollution.js @@ -0,0 +1,147 @@ +'use strict'; + +const common = require('../common'); +if ((!common.hasCrypto) || (!common.hasIntl)) { + common.skip('ESLint tests require crypto and Intl'); +} + +common.skipIfEslintMissing(); + +const RuleTester = require('../../tools/node_modules/eslint').RuleTester; +const rule = require('../../tools/eslint-rules/avoid-prototype-pollution'); + +new RuleTester({ + parserOptions: { ecmaVersion: 2022 }, +}) + .run('property-descriptor-no-prototype-pollution', rule, { + valid: [ + 'ObjectDefineProperties({}, {})', + 'ObjectCreate(null, {})', + 'ObjectDefineProperties({}, { key })', + 'ObjectCreate(null, { key })', + 'ObjectDefineProperties({}, { ...spread })', + 'ObjectCreate(null, { ...spread })', + 'ObjectDefineProperties({}, { key: valueDescriptor })', + 'ObjectCreate(null, { key: valueDescriptor })', + 'ObjectDefineProperties({}, { key: { ...{}, __proto__: null } })', + 'ObjectCreate(null, { key: { ...{}, __proto__: null } })', + 'ObjectDefineProperties({}, { key: { __proto__: null } })', + 'ObjectCreate(null, { key: { __proto__: null } })', + 'ObjectDefineProperties({}, { key: { __proto__: null, enumerable: true } })', + 'ObjectCreate(null, { key: { __proto__: null, enumerable: true } })', + 'ObjectDefineProperties({}, { key: { "__proto__": null } })', + 'ObjectCreate(null, { key: { "__proto__": null } })', + 'ObjectDefineProperties({}, { key: { \'__proto__\': null } })', + 'ObjectCreate(null, { key: { \'__proto__\': null } })', + 'ObjectDefineProperty({}, "key", ObjectCreate(null))', + 'ReflectDefineProperty({}, "key", ObjectCreate(null))', + 'ObjectDefineProperty({}, "key", valueDescriptor)', + 'ReflectDefineProperty({}, "key", valueDescriptor)', + 'ObjectDefineProperty({}, "key", { __proto__: null })', + 'ReflectDefineProperty({}, "key", { __proto__: null })', + 'ObjectDefineProperty({}, "key", { __proto__: null, enumerable: true })', + 'ReflectDefineProperty({}, "key", { __proto__: null, enumerable: true })', + 'ObjectDefineProperty({}, "key", { "__proto__": null })', + 'ReflectDefineProperty({}, "key", { "__proto__": null })', + 'ObjectDefineProperty({}, "key", { \'__proto__\': null })', + 'ReflectDefineProperty({}, "key", { \'__proto__\': null })', + ], + invalid: [ + { + code: 'ObjectDefineProperties({}, ObjectGetOwnPropertyDescriptors({}))', + errors: [{ message: /prototype pollution/ }], + }, + { + code: 'ObjectCreate(null, ObjectGetOwnPropertyDescriptors({}))', + errors: [{ message: /prototype pollution/ }], + }, + { + code: 'ObjectDefineProperties({}, { key: {} })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectCreate(null, { key: {} })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperties({}, { key: { [void 0]: { ...{ __proto__: null } } } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectCreate(null, { key: { [void 0]: { ...{ __proto__: null } } } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperties({}, { key: { __proto__: Object.prototype } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectCreate(null, { key: { __proto__: Object.prototype } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperties({}, { key: { [`__proto__`]: null } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectCreate(null, { key: { [`__proto__`]: null } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperties({}, { key: { enumerable: true } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectCreate(null, { key: { enumerable: true } })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperty({}, "key", {})', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ReflectDefineProperty({}, "key", {})', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperty({}, "key", ObjectGetOwnPropertyDescriptor({}, "key"))', + errors: [{ message: /prototype pollution/ }], + }, + { + code: 'ReflectDefineProperty({}, "key", ObjectGetOwnPropertyDescriptor({}, "key"))', + errors: [{ message: /prototype pollution/ }], + }, + { + code: 'ObjectDefineProperty({}, "key", ReflectGetOwnPropertyDescriptor({}, "key"))', + errors: [{ message: /prototype pollution/ }], + }, + { + code: 'ReflectDefineProperty({}, "key", ReflectGetOwnPropertyDescriptor({}, "key"))', + errors: [{ message: /prototype pollution/ }], + }, + { + code: 'ObjectDefineProperty({}, "key", { __proto__: Object.prototype })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ReflectDefineProperty({}, "key", { __proto__: Object.prototype })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperty({}, "key", { [`__proto__`]: null })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ReflectDefineProperty({}, "key", { [`__proto__`]: null })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ObjectDefineProperty({}, "key", { enumerable: true })', + errors: [{ message: /null-prototype/ }], + }, + { + code: 'ReflectDefineProperty({}, "key", { enumerable: true })', + errors: [{ message: /null-prototype/ }], + }, + ] + }); diff --git a/tools/eslint-rules/avoid-prototype-pollution.js b/tools/eslint-rules/avoid-prototype-pollution.js new file mode 100644 index 00000000000000..bf6bd1e0a81825 --- /dev/null +++ b/tools/eslint-rules/avoid-prototype-pollution.js @@ -0,0 +1,92 @@ +'use strict'; + +function checkProperties(context, node) { + if ( + node.type === 'CallExpression' && + node.callee.name === 'ObjectGetOwnPropertyDescriptors' + ) { + context.report({ + node, + message: + 'Property descriptors inherits from the Object prototype, therefore are subject to prototype pollution', + }); + } + if (node.type !== 'ObjectExpression') return; + for (const { key, value } of node.properties) { + if ( + key != null && value != null && + !(key.type === 'Identifier' && key.name === '__proto__') && + !(key.type === 'Literal' && key.value === '__proto__') + ) { + checkPropertyDescriptor(context, value); + } + } +} + +function checkPropertyDescriptor(context, node) { + if ( + node.type === 'CallExpression' && + (node.callee.name === 'ObjectGetOwnPropertyDescriptor' || + node.callee.name === 'ReflectGetOwnPropertyDescriptor') + ) { + context.report({ + node, + message: + 'Property descriptors inherits from the Object prototype, therefore are subject to prototype pollution', + suggest: [{ + desc: 'Wrap the property descriptor in a null-prototype object', + fix(fixer) { + return [ + fixer.insertTextBefore(node, '{ __proto__: null,...'), + fixer.insertTextAfter(node, ' }'), + ]; + }, + }], + }); + } + if (node.type !== 'ObjectExpression') return; + + for (const { key, value } of node.properties) { + if ( + key != null && value != null && + ((key.type === 'Identifier' && key.name === '__proto__') || + (key.type === 'Literal' && key.value === '__proto__')) && + value.type === 'Literal' && value.value === null + ) { + return true; + } + } + + context.report({ + node, + message: 'Must use null-prototype object for property descriptors', + }); +} + +const CallExpression = 'ExpressionStatement[expression.type="CallExpression"]'; +module.exports = { + meta: { hasSuggestions: true }, + create(context) { + return { + [`${CallExpression}[expression.callee.name=${/^(Object|Reflect)DefinePropert(ies|y)$/}]`]( + node + ) { + switch (node.expression.callee.name) { + case 'ObjectDefineProperties': + checkProperties(context, node.expression.arguments[1]); + break; + case 'ReflectDefineProperty': + case 'ObjectDefineProperty': + checkPropertyDescriptor(context, node.expression.arguments[2]); + break; + default: + throw new Error('Unreachable'); + } + }, + + [`${CallExpression}[expression.callee.name="ObjectCreate"][expression.arguments.length=2]`](node) { + checkProperties(context, node.expression.arguments[1]); + }, + }; + }, +}; From e0b41a5b7379ad13f3417abfac789f33cb24e104 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 15 Jun 2022 23:21:57 +0100 Subject: [PATCH 2/4] tools: report unsafe string and regex primordials as lint errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | The string method | looks up the property | | ----------------------------- | --------------------- | | `String.prototype.match` | `Symbol.match` | | `String.prototype.matchAll` | `Symbol.matchAll` | | `String.prototype.replace` | `Symbol.replace` | | `String.prototype.replaceAll` | `Symbol.replace` | | `String.prototype.search` | `Symbol.search` | | `String.prototype.split` | `Symbol.split` | Functions that lookup the `exec` property on the prototype chain: * `RegExp.prototype[Symbol.match]` * `RegExp.prototype[Symbol.matchAll]` * `RegExp.prototype[Symbol.replace]` * `RegExp.prototype[Symbol.search]` * `RegExp.prototype[Symbol.split]` * `RegExp.prototype.test` `RegExp.prototype[Symbol.replace]` and `RegExp.prototype[Symbol.split]` are still allowed for a lack of a better solution. PR-URL: https://github.com/nodejs/node/pull/43393 Reviewed-By: Tobias Nießen Reviewed-By: James M Snell --- lib/_tls_common.js | 32 +++++++-------- lib/repl.js | 2 +- .../test-eslint-avoid-prototype-pollution.js | 40 ++++++++++++++++++ .../eslint-rules/avoid-prototype-pollution.js | 41 +++++++++++++++++++ 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/lib/_tls_common.js b/lib/_tls_common.js index 2b5b17436bfc0d..eac85c16b5e9ec 100644 --- a/lib/_tls_common.js +++ b/lib/_tls_common.js @@ -27,7 +27,7 @@ const { ArrayPrototypePush, JSONParse, ObjectCreate, - StringPrototypeReplace, + RegExpPrototypeSymbolReplace, } = primordials; const { @@ -142,21 +142,21 @@ function translatePeerCertificate(c) { c.infoAccess = ObjectCreate(null); // XXX: More key validation? - StringPrototypeReplace(info, /([^\n:]*):([^\n]*)(?:\n|$)/g, - (all, key, val) => { - if (val.charCodeAt(0) === 0x22) { - // The translatePeerCertificate function is only - // used on internally created legacy certificate - // objects, and any value that contains a quote - // will always be a valid JSON string literal, - // so this should never throw. - val = JSONParse(val); - } - if (key in c.infoAccess) - ArrayPrototypePush(c.infoAccess[key], val); - else - c.infoAccess[key] = [val]; - }); + RegExpPrototypeSymbolReplace(/([^\n:]*):([^\n]*)(?:\n|$)/g, info, + (all, key, val) => { + if (val.charCodeAt(0) === 0x22) { + // The translatePeerCertificate function is only + // used on internally created legacy certificate + // objects, and any value that contains a quote + // will always be a valid JSON string literal, + // so this should never throw. + val = JSONParse(val); + } + if (key in c.infoAccess) + ArrayPrototypePush(c.infoAccess[key], val); + else + c.infoAccess[key] = [val]; + }); } return c; } diff --git a/lib/repl.js b/lib/repl.js index 20ea65768d2304..3d0980dd25d4e2 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -535,7 +535,7 @@ function REPLServer(prompt, // This will set the values from `savedRegExMatches` to corresponding // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9` - RegExpPrototypeTest(regExMatcher, + RegExpPrototypeExec(regExMatcher, ArrayPrototypeJoin(savedRegExMatches, sep)); let finished = false; diff --git a/test/parallel/test-eslint-avoid-prototype-pollution.js b/test/parallel/test-eslint-avoid-prototype-pollution.js index 047def545e9a90..0af4a0a07a2457 100644 --- a/test/parallel/test-eslint-avoid-prototype-pollution.js +++ b/test/parallel/test-eslint-avoid-prototype-pollution.js @@ -143,5 +143,45 @@ new RuleTester({ code: 'ReflectDefineProperty({}, "key", { enumerable: true })', errors: [{ message: /null-prototype/ }], }, + { + code: 'RegExpPrototypeTest(/some regex/, "some string")', + errors: [{ message: /looks up the "exec" property/ }], + }, + { + code: 'RegExpPrototypeSymbolMatch(/some regex/, "some string")', + errors: [{ message: /looks up the "exec" property/ }], + }, + { + code: 'RegExpPrototypeSymbolMatchAll(/some regex/, "some string")', + errors: [{ message: /looks up the "exec" property/ }], + }, + { + code: 'RegExpPrototypeSymbolSearch(/some regex/, "some string")', + errors: [{ message: /looks up the "exec" property/ }], + }, + { + code: 'StringPrototypeMatch("some string", /some regex/)', + errors: [{ message: /looks up the Symbol\.match property/ }], + }, + { + code: 'StringPrototypeMatchAll("some string", /some regex/)', + errors: [{ message: /looks up the Symbol\.matchAll property/ }], + }, + { + code: 'StringPrototypeReplace("some string", /some regex/, "some replacement")', + errors: [{ message: /looks up the Symbol\.replace property/ }], + }, + { + code: 'StringPrototypeReplaceAll("some string", /some regex/, "some replacement")', + errors: [{ message: /looks up the Symbol\.replace property/ }], + }, + { + code: 'StringPrototypeSearch("some string", /some regex/)', + errors: [{ message: /looks up the Symbol\.search property/ }], + }, + { + code: 'StringPrototypeSplit("some string", /some regex/)', + errors: [{ message: /looks up the Symbol\.split property/ }], + }, ] }); diff --git a/tools/eslint-rules/avoid-prototype-pollution.js b/tools/eslint-rules/avoid-prototype-pollution.js index bf6bd1e0a81825..0759960349a9fe 100644 --- a/tools/eslint-rules/avoid-prototype-pollution.js +++ b/tools/eslint-rules/avoid-prototype-pollution.js @@ -63,6 +63,17 @@ function checkPropertyDescriptor(context, node) { }); } +function createUnsafeStringMethodReport(context, name, lookedUpProperty) { + return { + [`${CallExpression}[expression.callee.name=${JSON.stringify(name)}]`](node) { + context.report({ + node, + message: `${name} looks up the ${lookedUpProperty} property on the first argument`, + }); + } + }; +} + const CallExpression = 'ExpressionStatement[expression.type="CallExpression"]'; module.exports = { meta: { hasSuggestions: true }, @@ -87,6 +98,36 @@ module.exports = { [`${CallExpression}[expression.callee.name="ObjectCreate"][expression.arguments.length=2]`](node) { checkProperties(context, node.expression.arguments[1]); }, + [`${CallExpression}[expression.callee.name="RegExpPrototypeTest"]`](node) { + context.report({ + node, + message: '%RegExp.prototype.test% looks up the "exec" property of `this` value', + suggest: [{ + desc: 'Use RegexpPrototypeExec instead', + fix(fixer) { + const testRange = { ...node.range }; + testRange.start = testRange.start + 'RegexpPrototype'.length; + testRange.end = testRange.start + 'Test'.length; + return [ + fixer.replaceTextRange(node.range, 'Exec'), + fixer.insertTextAfter(node, ' !== null'), + ]; + } + }] + }); + }, + [`${CallExpression}[expression.callee.name=${/^RegExpPrototypeSymbol(Match|MatchAll|Search)$/}]`](node) { + context.report({ + node, + message: node.expression.callee.name + ' looks up the "exec" property of `this` value', + }); + }, + ...createUnsafeStringMethodReport(context, 'StringPrototypeMatch', 'Symbol.match'), + ...createUnsafeStringMethodReport(context, 'StringPrototypeMatchAll', 'Symbol.matchAll'), + ...createUnsafeStringMethodReport(context, 'StringPrototypeReplace', 'Symbol.replace'), + ...createUnsafeStringMethodReport(context, 'StringPrototypeReplaceAll', 'Symbol.replace'), + ...createUnsafeStringMethodReport(context, 'StringPrototypeSearch', 'Symbol.search'), + ...createUnsafeStringMethodReport(context, 'StringPrototypeSplit', 'Symbol.split'), }; }, }; From 4327ff8a6c6c439121286a1a9e95e86099e3b337 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 12 Jun 2022 13:52:43 +0200 Subject: [PATCH 3/4] tools,doc: add guards against prototype pollution when creating proxies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/43391 Reviewed-By: James M Snell Reviewed-By: LiviaMedeiros Reviewed-By: Сковорода Никита Андреевич --- doc/contributing/primordials.md | 24 +++++++++++++++++++ lib/internal/debugger/inspect.js | 1 + lib/internal/http2/core.js | 1 + lib/internal/modules/cjs/loader.js | 4 ++++ .../test-eslint-avoid-prototype-pollution.js | 20 ++++++++++++++++ .../eslint-rules/avoid-prototype-pollution.js | 17 +++++++++++++ 6 files changed, 67 insertions(+) diff --git a/doc/contributing/primordials.md b/doc/contributing/primordials.md index 8be3f8770768f9..7980bd79b3b272 100644 --- a/doc/contributing/primordials.md +++ b/doc/contributing/primordials.md @@ -738,3 +738,27 @@ class SomeClass { ObjectDefineProperty(SomeClass.prototype, 'readOnlyProperty', kEnumerableProperty); console.log(new SomeClass().readOnlyProperty); // genuine data ``` + +### Defining a `Proxy` handler + +When defining a `Proxy`, the handler object could be at risk of prototype +pollution when using a plain object literal: + +```js +// User-land +Object.prototype.get = () => 'Unrelated user-provided data'; + +// Core +const objectToProxy = { someProperty: 'genuine value' }; + +const proxyWithPlainObjectLiteral = new Proxy(objectToProxy, { + has() { return false; }, +}); +console.log(proxyWithPlainObjectLiteral.someProperty); // Unrelated user-provided data + +const proxyWithNullPrototypeObject = new Proxy(objectToProxy, { + __proto__: null, + has() { return false; }, +}); +console.log(proxyWithNullPrototypeObject.someProperty); // genuine value +``` diff --git a/lib/internal/debugger/inspect.js b/lib/internal/debugger/inspect.js index 7f1017886f6d6e..cb5ffa8cef5720 100644 --- a/lib/internal/debugger/inspect.js +++ b/lib/internal/debugger/inspect.js @@ -117,6 +117,7 @@ function createAgentProxy(domain, client) { }; return new Proxy(agent, { + __proto__: null, get(target, name) { if (name in target) return target[name]; return function callVirtualMethod(params) { diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 337fbd7877a496..5a1bbe17de2135 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -982,6 +982,7 @@ function trackAssignmentsTypedArray(typedArray) { } return new Proxy(typedArray, { + __proto__: null, get(obj, prop, receiver) { if (prop === 'copyAssigned') { return copyAssigned; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 0cd2f5c2bae188..163fd00bd57995 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -217,6 +217,8 @@ const wrapper = [ ]; let wrapperProxy = new Proxy(wrapper, { + __proto__: null, + set(target, property, value, receiver) { patched = true; return ReflectSet(target, property, value, receiver); @@ -725,6 +727,8 @@ function emitCircularRequireWarning(prop) { // A Proxy that can be used as the prototype of a module.exports object and // warns when non-existent properties are accessed. const CircularRequirePrototypeWarningProxy = new Proxy({}, { + __proto__: null, + get(target, prop) { // Allow __esModule access in any case because it is used in the output // of transpiled code to determine whether something comes from an diff --git a/test/parallel/test-eslint-avoid-prototype-pollution.js b/test/parallel/test-eslint-avoid-prototype-pollution.js index 0af4a0a07a2457..26b0852c0c24ee 100644 --- a/test/parallel/test-eslint-avoid-prototype-pollution.js +++ b/test/parallel/test-eslint-avoid-prototype-pollution.js @@ -45,6 +45,10 @@ new RuleTester({ 'ReflectDefineProperty({}, "key", { "__proto__": null })', 'ObjectDefineProperty({}, "key", { \'__proto__\': null })', 'ReflectDefineProperty({}, "key", { \'__proto__\': null })', + 'new Proxy({}, otherObject)', + 'new Proxy({}, someFactory())', + 'new Proxy({}, { __proto__: null })', + 'new Proxy({}, { __proto__: null, ...{} })', ], invalid: [ { @@ -183,5 +187,21 @@ new RuleTester({ code: 'StringPrototypeSplit("some string", /some regex/)', errors: [{ message: /looks up the Symbol\.split property/ }], }, + { + code: 'new Proxy({}, {})', + errors: [{ message: /null-prototype/ }] + }, + { + code: 'new Proxy({}, { [`__proto__`]: null })', + errors: [{ message: /null-prototype/ }] + }, + { + code: 'new Proxy({}, { __proto__: Object.prototype })', + errors: [{ message: /null-prototype/ }] + }, + { + code: 'new Proxy({}, { ...{ __proto__: null } })', + errors: [{ message: /null-prototype/ }] + }, ] }); diff --git a/tools/eslint-rules/avoid-prototype-pollution.js b/tools/eslint-rules/avoid-prototype-pollution.js index 0759960349a9fe..1f71272bd7d0b3 100644 --- a/tools/eslint-rules/avoid-prototype-pollution.js +++ b/tools/eslint-rules/avoid-prototype-pollution.js @@ -128,6 +128,23 @@ module.exports = { ...createUnsafeStringMethodReport(context, 'StringPrototypeReplaceAll', 'Symbol.replace'), ...createUnsafeStringMethodReport(context, 'StringPrototypeSearch', 'Symbol.search'), ...createUnsafeStringMethodReport(context, 'StringPrototypeSplit', 'Symbol.split'), + + 'NewExpression[callee.name="Proxy"][arguments.1.type="ObjectExpression"]'(node) { + for (const { key, value } of node.arguments[1].properties) { + if ( + key != null && value != null && + ((key.type === 'Identifier' && key.name === '__proto__') || + (key.type === 'Literal' && key.value === '__proto__')) && + value.type === 'Literal' && value.value === null + ) { + return; + } + } + context.report({ + node, + message: 'Proxy handler must be a null-prototype object' + }); + } }; }, }; From 9994a1b002c2d3cce94bcb724b2db59dceb799a2 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 27 Jun 2022 17:16:06 +0200 Subject: [PATCH 4/4] lib: refactor to avoid unsafe regex primordials PR-URL: https://github.com/nodejs/node/pull/43475 Reviewed-By: Geoffrey Booth Reviewed-By: Stephen Belanger --- lib/_http_client.js | 4 +- lib/_http_common.js | 6 +- lib/_http_outgoing.js | 6 +- lib/_http_server.js | 8 +- lib/_tls_wrap.js | 17 ++-- lib/assert.js | 15 ++-- lib/buffer.js | 6 +- lib/child_process.js | 4 +- lib/internal/blob.js | 6 +- lib/internal/cluster/primary.js | 6 +- lib/internal/console/constructor.js | 4 +- lib/internal/debugger/inspect.js | 11 ++- lib/internal/debugger/inspect_repl.js | 8 +- lib/internal/dns/utils.js | 10 +-- lib/internal/errors.js | 10 +-- lib/internal/fs/utils.js | 4 +- lib/internal/http2/core.js | 4 +- lib/internal/main/print_help.js | 13 +-- lib/internal/modules/cjs/loader.js | 10 +-- lib/internal/modules/esm/formats.js | 6 +- lib/internal/modules/esm/module_job.js | 21 +++-- lib/internal/modules/esm/resolve.js | 13 +-- lib/internal/policy/manifest.js | 17 ++-- lib/internal/policy/sri.js | 3 +- lib/internal/process/per_thread.js | 4 +- lib/internal/readline/interface.js | 20 ++--- lib/internal/readline/utils.js | 11 ++- lib/internal/repl/history.js | 4 +- lib/internal/repl/utils.js | 14 ++-- .../source_map/prepare_stack_trace.js | 4 +- lib/internal/source_map/source_map_cache.js | 8 +- lib/internal/tty.js | 10 +-- lib/internal/util.js | 5 +- lib/internal/util/inspect.js | 26 +++--- lib/internal/validators.js | 4 +- lib/internal/worker.js | 4 +- lib/repl.js | 79 ++++++++++--------- lib/tls.js | 9 +-- test/parallel/test-errors-systemerror.js | 2 +- 39 files changed, 209 insertions(+), 207 deletions(-) diff --git a/lib/_http_client.js b/lib/_http_client.js index 003f08a774cb0b..9c50af5a57e7d1 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -31,7 +31,7 @@ const { ObjectKeys, ObjectSetPrototypeOf, ReflectApply, - RegExpPrototypeTest, + RegExpPrototypeExec, String, StringPrototypeCharCodeAt, StringPrototypeIncludes, @@ -169,7 +169,7 @@ function ClientRequest(input, options, cb) { if (options.path) { const path = String(options.path); - if (RegExpPrototypeTest(INVALID_PATH_REGEX, path)) + if (RegExpPrototypeExec(INVALID_PATH_REGEX, path) !== null) throw new ERR_UNESCAPED_CHARACTERS('Request path'); } diff --git a/lib/_http_common.js b/lib/_http_common.js index d2819492aa6f23..0b324ae231e6ff 100644 --- a/lib/_http_common.js +++ b/lib/_http_common.js @@ -24,7 +24,7 @@ const { MathMin, Symbol, - RegExpPrototypeTest, + RegExpPrototypeExec, } = primordials; const { setImmediate } = require('timers'); @@ -218,7 +218,7 @@ const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; * See https://tools.ietf.org/html/rfc7230#section-3.2.6 */ function checkIsHttpToken(val) { - return RegExpPrototypeTest(tokenRegExp, val); + return RegExpPrototypeExec(tokenRegExp, val) !== null; } const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; @@ -229,7 +229,7 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; * field-vchar = VCHAR / obs-text */ function checkInvalidHeaderChar(val) { - return RegExpPrototypeTest(headerCharRegex, val); + return RegExpPrototypeExec(headerCharRegex, val) !== null; } function cleanParser(parser) { diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index e02b86e8a84929..006ac437a14938 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -33,7 +33,7 @@ const { ObjectValues, ObjectPrototypeHasOwnProperty, ObjectSetPrototypeOf, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeSet, StringPrototypeToLowerCase, Symbol, @@ -542,7 +542,7 @@ function matchHeader(self, state, field, value) { case 'connection': state.connection = true; self._removedConnection = false; - if (RegExpPrototypeTest(RE_CONN_CLOSE, value)) + if (RegExpPrototypeExec(RE_CONN_CLOSE, value) !== null) self._last = true; else self.shouldKeepAlive = true; @@ -550,7 +550,7 @@ function matchHeader(self, state, field, value) { case 'transfer-encoding': state.te = true; self._removedTE = false; - if (RegExpPrototypeTest(RE_TE_CHUNKED, value)) + if (RegExpPrototypeExec(RE_TE_CHUNKED, value) !== null) self.chunkedEncoding = true; break; case 'content-length': diff --git a/lib/_http_server.js b/lib/_http_server.js index 17f0fb7010b2a3..80039b3e7a26b6 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -26,7 +26,7 @@ const { Error, ObjectKeys, ObjectSetPrototypeOf, - RegExpPrototypeTest, + RegExpPrototypeExec, Symbol, SymbolFor, } = primordials; @@ -191,8 +191,8 @@ function ServerResponse(req) { this._expect_continue = false; if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { - this.useChunkedEncodingByDefault = RegExpPrototypeTest(chunkExpression, - req.headers.te); + this.useChunkedEncodingByDefault = RegExpPrototypeExec(chunkExpression, + req.headers.te) !== null; this.shouldKeepAlive = false; } @@ -957,7 +957,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { } else if (req.headers.expect !== undefined) { handled = true; - if (RegExpPrototypeTest(continueExpression, req.headers.expect)) { + if (RegExpPrototypeExec(continueExpression, req.headers.expect) !== null) { res._expect_continue = true; if (server.listenerCount('checkContinue') > 0) { diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index be2c82698715e7..eda0c0987f9446 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -31,8 +31,9 @@ const { ObjectSetPrototypeOf, ReflectApply, RegExp, - RegExpPrototypeTest, - StringPrototypeReplace, + RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, + StringPrototypeReplaceAll, StringPrototypeSlice, Symbol, SymbolFor, @@ -426,8 +427,8 @@ function onerror(err) { owner.destroy(err); } else if (owner._tlsOptions?.isServer && owner._rejectUnauthorized && - RegExpPrototypeTest(/peer did not return a certificate/, - err.message)) { + RegExpPrototypeExec(/peer did not return a certificate/, + err.message) !== null) { // Ignore server's authorization errors owner.destroy(); } else { @@ -1448,9 +1449,9 @@ Server.prototype.addContext = function(servername, context) { throw new ERR_TLS_REQUIRED_SERVER_NAME(); } - const re = new RegExp('^' + StringPrototypeReplace( - StringPrototypeReplace(servername, /([.^$+?\-\\[\]{}])/g, '\\$1'), - /\*/g, '[^.]*' + const re = new RegExp('^' + StringPrototypeReplaceAll( + RegExpPrototypeSymbolReplace(/([.^$+?\-\\[\]{}])/g, servername, '\\$1'), + '*', '[^.]*' ) + '$'); ArrayPrototypePush(this._contexts, [re, tls.createSecureContext(context).context]); @@ -1474,7 +1475,7 @@ function SNICallback(servername, callback) { for (let i = contexts.length - 1; i >= 0; --i) { const elem = contexts[i]; - if (RegExpPrototypeTest(elem[0], servername)) { + if (RegExpPrototypeExec(elem[0], servername) !== null) { callback(null, elem[1]); return; } diff --git a/lib/assert.js b/lib/assert.js index 2c7cf369a87af2..fc3127f3bf4214 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -35,7 +35,8 @@ const { ObjectKeys, ObjectPrototypeIsPrototypeOf, ReflectApply, - RegExpPrototypeTest, + RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, SafeMap, String, StringPrototypeCharCodeAt, @@ -345,7 +346,7 @@ function getErrMessage(message, fn) { // Always normalize indentation, otherwise the message could look weird. if (StringPrototypeIncludes(message, '\n')) { if (EOL === '\r\n') { - message = StringPrototypeReplace(message, /\r\n/g, '\n'); + message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n'); } const frames = StringPrototypeSplit(message, '\n'); message = ArrayPrototypeShift(frames); @@ -606,7 +607,7 @@ class Comparison { if (actual !== undefined && typeof actual[key] === 'string' && isRegExp(obj[key]) && - RegExpPrototypeTest(obj[key], actual[key])) { + RegExpPrototypeExec(obj[key], actual[key]) !== null) { this[key] = actual[key]; } else { this[key] = obj[key]; @@ -652,7 +653,7 @@ function expectedException(actual, expected, message, fn) { // Handle regular expressions. if (isRegExp(expected)) { const str = String(actual); - if (RegExpPrototypeTest(expected, str)) + if (RegExpPrototypeExec(expected, str) !== null) return; if (!message) { @@ -687,7 +688,7 @@ function expectedException(actual, expected, message, fn) { for (const key of keys) { if (typeof actual[key] === 'string' && isRegExp(expected[key]) && - RegExpPrototypeTest(expected[key], actual[key])) { + RegExpPrototypeExec(expected[key], actual[key]) !== null) { continue; } compareExceptionKey(actual, expected, key, message, keys, fn); @@ -851,7 +852,7 @@ function hasMatchingError(actual, expected) { if (typeof expected !== 'function') { if (isRegExp(expected)) { const str = String(actual); - return RegExpPrototypeTest(expected, str); + return RegExpPrototypeExec(expected, str) !== null; } throw new ERR_INVALID_ARG_TYPE( 'expected', ['Function', 'RegExp'], expected @@ -1000,7 +1001,7 @@ function internalMatch(string, regexp, message, fn) { } const match = fn === assert.match; if (typeof string !== 'string' || - RegExpPrototypeTest(regexp, string) !== match) { + RegExpPrototypeExec(regexp, string) !== null !== match) { if (message instanceof Error) { throw message; } diff --git a/lib/buffer.js b/lib/buffer.js index 96d303d3b2f588..874b9b5595985c 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -37,8 +37,8 @@ const { ObjectDefineProperties, ObjectDefineProperty, ObjectSetPrototypeOf, + RegExpPrototypeSymbolReplace, StringPrototypeCharCodeAt, - StringPrototypeReplace, StringPrototypeSlice, StringPrototypeToLowerCase, StringPrototypeTrim, @@ -840,8 +840,8 @@ Buffer.prototype[customInspectSymbol] = function inspect(recurseTimes, ctx) { const max = INSPECT_MAX_BYTES; const actualMax = MathMin(max, this.length); const remaining = this.length - max; - let str = StringPrototypeTrim(StringPrototypeReplace( - this.hexSlice(0, actualMax), /(.{2})/g, '$1 ')); + let str = StringPrototypeTrim(RegExpPrototypeSymbolReplace( + /(.{2})/g, this.hexSlice(0, actualMax), '$1 ')); if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; // Inspect special properties as well, if possible. diff --git a/lib/child_process.js b/lib/child_process.js index 388cbc68dbd0b0..77b9ff35ff6ea7 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -36,7 +36,7 @@ const { ObjectAssign, ObjectDefineProperty, ObjectPrototypeHasOwnProperty, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeSet, StringPrototypeSlice, StringPrototypeToUpperCase, @@ -592,7 +592,7 @@ function normalizeSpawnArguments(file, args, options) { else file = process.env.comspec || 'cmd.exe'; // '/d /s /c' is used only for cmd.exe. - if (RegExpPrototypeTest(/^(?:.*\\)?cmd(?:\.exe)?$/i, file)) { + if (RegExpPrototypeExec(/^(?:.*\\)?cmd(?:\.exe)?$/i, file) !== null) { args = ['/d', '/s', '/c', `"${command}"`]; windowsVerbatimArguments = true; } else { diff --git a/lib/internal/blob.js b/lib/internal/blob.js index 7c1da74beea9d3..7070524c6c4659 100644 --- a/lib/internal/blob.js +++ b/lib/internal/blob.js @@ -9,8 +9,8 @@ const { PromiseReject, SafePromisePrototypeFinally, ReflectConstruct, + RegExpPrototypeExec, RegExpPrototypeSymbolReplace, - RegExpPrototypeTest, StringPrototypeToLowerCase, StringPrototypeSplit, Symbol, @@ -164,7 +164,7 @@ class Blob { this[kLength] = length; type = `${type}`; - this[kType] = RegExpPrototypeTest(disallowedTypeCharacters, type) ? + this[kType] = RegExpPrototypeExec(disallowedTypeCharacters, type) !== null ? '' : StringPrototypeToLowerCase(type); // eslint-disable-next-line no-constructor-return @@ -246,7 +246,7 @@ class Blob { end |= 0; contentType = `${contentType}`; - if (RegExpPrototypeTest(disallowedTypeCharacters, contentType)) { + if (RegExpPrototypeExec(disallowedTypeCharacters, contentType) !== null) { contentType = ''; } else { contentType = StringPrototypeToLowerCase(contentType); diff --git a/lib/internal/cluster/primary.js b/lib/internal/cluster/primary.js index 69e97eb8a6837f..3940d094e7b76d 100644 --- a/lib/internal/cluster/primary.js +++ b/lib/internal/cluster/primary.js @@ -6,7 +6,7 @@ const { ArrayPrototypeSome, ObjectKeys, ObjectValues, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeMap, StringPrototypeStartsWith, } = primordials; @@ -121,8 +121,8 @@ function createWorkerProcess(id, env) { const nodeOptions = process.env.NODE_OPTIONS || ''; if (ArrayPrototypeSome(execArgv, - (arg) => RegExpPrototypeTest(debugArgRegex, arg)) || - RegExpPrototypeTest(debugArgRegex, nodeOptions)) { + (arg) => RegExpPrototypeExec(debugArgRegex, arg) !== null) || + RegExpPrototypeExec(debugArgRegex, nodeOptions) !== null) { let inspectPort; if ('inspectPort' in cluster.settings) { if (typeof cluster.settings.inspectPort === 'function') diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index bba2784bb92d44..d8434f2311a375 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -24,13 +24,13 @@ const { ReflectApply, ReflectConstruct, ReflectOwnKeys, + RegExpPrototypeSymbolReplace, SafeArrayIterator, SafeMap, SafeWeakMap, StringPrototypeIncludes, StringPrototypePadStart, StringPrototypeRepeat, - StringPrototypeReplace, StringPrototypeSlice, StringPrototypeSplit, Symbol, @@ -279,7 +279,7 @@ ObjectDefineProperties(Console.prototype, { if (groupIndent.length !== 0) { if (StringPrototypeIncludes(string, '\n')) { - string = StringPrototypeReplace(string, /\n/g, `\n${groupIndent}`); + string = RegExpPrototypeSymbolReplace(/\n/g, string, `\n${groupIndent}`); } string = groupIndent + string; } diff --git a/lib/internal/debugger/inspect.js b/lib/internal/debugger/inspect.js index cb5ffa8cef5720..e5211c285bd5eb 100644 --- a/lib/internal/debugger/inspect.js +++ b/lib/internal/debugger/inspect.js @@ -15,9 +15,8 @@ const { PromisePrototypeThen, PromiseResolve, Proxy, - RegExpPrototypeSymbolMatch, + RegExpPrototypeExec, RegExpPrototypeSymbolSplit, - RegExpPrototypeTest, StringPrototypeEndsWith, StringPrototypeSplit, } = primordials; @@ -91,7 +90,7 @@ async function runScript(script, scriptArgs, inspectHost, inspectPort, return new Promise((resolve) => { function waitForListenHint(text) { output += text; - const debug = RegExpPrototypeSymbolMatch(debugRegex, output); + const debug = RegExpPrototypeExec(debugRegex, output); if (debug) { const host = debug[1]; const port = Number(debug[2]); @@ -282,8 +281,8 @@ function parseArgv(args) { let script = target; let scriptArgs = args; - const hostMatch = RegExpPrototypeSymbolMatch(/^([^:]+):(\d+)$/, target); - const portMatch = RegExpPrototypeSymbolMatch(/^--port=(\d+)$/, target); + const hostMatch = RegExpPrototypeExec(/^([^:]+):(\d+)$/, target); + const portMatch = RegExpPrototypeExec(/^--port=(\d+)$/, target); if (hostMatch) { // Connecting to remote debugger @@ -296,7 +295,7 @@ function parseArgv(args) { port = Number(portMatch[1]); script = args[0]; scriptArgs = ArrayPrototypeSlice(args, 1); - } else if (args.length === 1 && RegExpPrototypeTest(/^\d+$/, args[0]) && + } else if (args.length === 1 && RegExpPrototypeExec(/^\d+$/, args[0]) !== null && target === '-p') { // Start debugger against a given pid const pid = Number(args[0]); diff --git a/lib/internal/debugger/inspect_repl.js b/lib/internal/debugger/inspect_repl.js index 1a40b1757524e0..bfc11990e0ddf0 100644 --- a/lib/internal/debugger/inspect_repl.js +++ b/lib/internal/debugger/inspect_repl.js @@ -28,7 +28,7 @@ const { PromiseResolve, ReflectGetOwnPropertyDescriptor, ReflectOwnKeys, - RegExpPrototypeSymbolMatch, + RegExpPrototypeExec, RegExpPrototypeSymbolReplace, SafeMap, SafePromiseAll, @@ -113,7 +113,7 @@ takeHeapSnapshot(filepath = 'node.heapsnapshot') const FUNCTION_NAME_PATTERN = /^(?:function\*? )?([^(\s]+)\(/; function extractFunctionName(description) { const fnNameMatch = - RegExpPrototypeSymbolMatch(FUNCTION_NAME_PATTERN, description); + RegExpPrototypeExec(FUNCTION_NAME_PATTERN, description); return fnNameMatch ? `: ${fnNameMatch[1]}` : ''; } @@ -170,7 +170,7 @@ function markSourceColumn(sourceText, position, useColors) { function extractErrorMessage(stack) { if (!stack) return ''; - const m = RegExpPrototypeSymbolMatch(/^\w+: ([^\n]+)/, stack); + const m = RegExpPrototypeExec(/^\w+: ([^\n]+)/, stack); return m?.[1] ?? stack; } @@ -574,7 +574,7 @@ function createRepl(inspector) { function prepareControlCode(input) { if (input === '\n') return lastCommand; // Add parentheses: exec process.title => exec("process.title"); - const match = RegExpPrototypeSymbolMatch(/^\s*(?:exec|p)\s+([^\n]*)/, input); + const match = RegExpPrototypeExec(/^\s*(?:exec|p)\s+([^\n]*)/, input); if (match) { lastCommand = `exec(${JSONStringify(match[1])})`; } else { diff --git a/lib/internal/dns/utils.js b/lib/internal/dns/utils.js index 511be2bb457b34..06de00174b709e 100644 --- a/lib/internal/dns/utils.js +++ b/lib/internal/dns/utils.js @@ -7,8 +7,8 @@ const { ArrayPrototypePush, FunctionPrototypeBind, NumberParseInt, - StringPrototypeMatch, - StringPrototypeReplace, + RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, } = primordials; const errors = require('internal/errors'); @@ -86,7 +86,7 @@ class Resolver { if (ipVersion !== 0) return ArrayPrototypePush(newSet, [ipVersion, serv, IANA_DNS_PORT]); - const match = StringPrototypeMatch(serv, IPv6RE); + const match = RegExpPrototypeExec(IPv6RE, serv); // Check for an IPv6 in brackets. if (match) { @@ -94,13 +94,13 @@ class Resolver { if (ipVersion !== 0) { const port = NumberParseInt( - StringPrototypeReplace(serv, addrSplitRE, '$2')) || IANA_DNS_PORT; + RegExpPrototypeSymbolReplace(addrSplitRE, serv, '$2')) || IANA_DNS_PORT; return ArrayPrototypePush(newSet, [ipVersion, match[1], port]); } } // addr::port - const addrSplitMatch = StringPrototypeMatch(serv, addrSplitRE); + const addrSplitMatch = RegExpPrototypeExec(addrSplitRE, serv); if (addrSplitMatch) { const hostIP = addrSplitMatch[1]; diff --git a/lib/internal/errors.js b/lib/internal/errors.js index eb039db859b568..b3a505aae8d0de 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -42,14 +42,13 @@ const { ObjectPrototypeHasOwnProperty, RangeError, ReflectApply, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeArrayIterator, SafeMap, SafeWeakMap, String, StringPrototypeEndsWith, StringPrototypeIncludes, - StringPrototypeMatch, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, @@ -438,8 +437,9 @@ function getMessage(key, args, self) { return ReflectApply(msg, self, args); } - const expectedLength = - (StringPrototypeMatch(msg, /%[dfijoOs]/g) || []).length; + const regex = /%[dfijoOs]/g; + let expectedLength = 0; + while (RegExpPrototypeExec(regex, msg) !== null) expectedLength++; assert( expectedLength === args.length, `Code: ${key}; The provided arguments length (${args.length}) does not ` + @@ -1203,7 +1203,7 @@ E('ERR_INVALID_ARG_TYPE', 'All expected entries have to be of type string'); if (ArrayPrototypeIncludes(kTypes, value)) { ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); - } else if (RegExpPrototypeTest(classRegExp, value)) { + } else if (RegExpPrototypeExec(classRegExp, value) !== null) { ArrayPrototypePush(instances, value); } else { assert(value !== 'object', diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 9280fcfef7018e..cba646942785fe 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -17,9 +17,9 @@ const { ObjectSetPrototypeOf, ReflectApply, ReflectOwnKeys, + RegExpPrototypeSymbolReplace, StringPrototypeEndsWith, StringPrototypeIncludes, - StringPrototypeReplace, Symbol, TypedArrayPrototypeIncludes, } = primordials; @@ -396,7 +396,7 @@ function preprocessSymlinkDestination(path, type, linkPath) { return pathModule.toNamespacedPath(path); } // Windows symlinks don't tolerate forward slashes. - return StringPrototypeReplace(path, /\//g, '\\'); + return RegExpPrototypeSymbolReplace(/\//g, path, '\\'); } // Constructor for file stats. diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 5a1bbe17de2135..0f4dc9bf9865f5 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -23,7 +23,7 @@ const { ReflectGet, ReflectGetPrototypeOf, ReflectSet, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeArrayIterator, SafeMap, SafeSet, @@ -1660,7 +1660,7 @@ class ServerHttp2Session extends Http2Session { } validateString(alt, 'alt'); - if (!RegExpPrototypeTest(kQuotedString, alt)) + if (RegExpPrototypeExec(kQuotedString, alt) === null) throw new ERR_INVALID_CHAR('alt'); // Max length permitted for ALTSVC diff --git a/lib/internal/main/print_help.js b/lib/internal/main/print_help.js index 6aa422c657b2d0..a2cee4b0567649 100644 --- a/lib/internal/main/print_help.js +++ b/lib/internal/main/print_help.js @@ -8,11 +8,11 @@ const { MathMax, ObjectKeys, RegExp, + RegExpPrototypeSymbolReplace, StringPrototypeLocaleCompare, StringPrototypeSlice, StringPrototypeTrimLeft, StringPrototypeRepeat, - StringPrototypeReplace, SafeMap, } = primordials; @@ -77,14 +77,15 @@ const envVars = new SafeMap(ArrayPrototypeConcat([ function indent(text, depth) { - return StringPrototypeReplace(text, /^/gm, StringPrototypeRepeat(' ', depth)); + return RegExpPrototypeSymbolReplace(/^/gm, text, StringPrototypeRepeat(' ', depth)); } function fold(text, width) { - return StringPrototypeReplace(text, - new RegExp(`([^\n]{0,${width}})( |$)`, 'g'), - (_, newLine, end) => - newLine + (end === ' ' ? '\n' : '')); + return RegExpPrototypeSymbolReplace( + new RegExp(`([^\n]{0,${width}})( |$)`, 'g'), + text, + (_, newLine, end) => newLine + (end === ' ' ? '\n' : '') + ); } function getArgDescription(type) { diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 163fd00bd57995..f1971c40a447b2 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -50,7 +50,6 @@ const { ReflectApply, ReflectSet, RegExpPrototypeExec, - RegExpPrototypeTest, SafeMap, SafeSet, SafeWeakMap, @@ -60,7 +59,6 @@ const { StringPrototypeEndsWith, StringPrototypeLastIndexOf, StringPrototypeIndexOf, - StringPrototypeMatch, StringPrototypeRepeat, StringPrototypeSlice, StringPrototypeSplit, @@ -81,7 +79,7 @@ const { maybeCacheSourceMap, } = require('internal/source_map/source_map_cache'); const { pathToFileURL, fileURLToPath, isURLInstance } = require('internal/url'); -const { deprecate } = require('internal/util'); +const { deprecate, kEmptyObject } = require('internal/util'); const vm = require('vm'); const assert = require('internal/assert'); const fs = require('fs'); @@ -485,7 +483,7 @@ const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; function resolveExports(nmPath, request) { // The implementation's behavior is meant to mirror resolution in ESM. const { 1: name, 2: expansion = '' } = - StringPrototypeMatch(request, EXPORTS_PATTERN) || []; + RegExpPrototypeExec(EXPORTS_PATTERN, request) || kEmptyObject; if (!name) return; const pkgPath = path.resolve(nmPath, name); @@ -522,7 +520,7 @@ Module._findPath = function(request, paths, isMain) { StringPrototypeCharCodeAt(request, request.length - 1) === CHAR_FORWARD_SLASH; if (!trailingSlash) { - trailingSlash = RegExpPrototypeTest(trailingSlashRegex, request); + trailingSlash = RegExpPrototypeExec(trailingSlashRegex, request) !== null; } // For each path @@ -963,7 +961,7 @@ Module._resolveFilename = function(request, parent, isMain, options) { function finalizeEsmResolution(match, request, parentPath, pkgPath) { const { resolved, exact } = match; - if (RegExpPrototypeTest(encodedSepRegEx, resolved)) + if (RegExpPrototypeExec(encodedSepRegEx, resolved) !== null) throw new ERR_INVALID_MODULE_SPECIFIER( resolved, 'must not include encoded "/" or "\\" characters', parentPath); const filename = fileURLToPath(resolved); diff --git a/lib/internal/modules/esm/formats.js b/lib/internal/modules/esm/formats.js index f9da01402e7f62..f71e6ae689c277 100644 --- a/lib/internal/modules/esm/formats.js +++ b/lib/internal/modules/esm/formats.js @@ -1,7 +1,7 @@ 'use strict'; const { - RegExpPrototypeTest, + RegExpPrototypeExec, } = primordials; const { getOptionValue } = require('internal/options'); @@ -35,10 +35,10 @@ if (experimentalWasmModules) { */ function mimeToFormat(mime) { if ( - RegExpPrototypeTest( + RegExpPrototypeExec( /\s*(text|application)\/javascript\s*(;\s*charset=utf-?8\s*)?/i, mime - ) + ) !== null ) return 'module'; if (mime === 'application/json') return 'json'; if (experimentalWasmModules && mime === 'application/wasm') return 'wasm'; diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 2df2c40495e7bc..3a2ec7343a1d53 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -10,12 +10,11 @@ const { PromiseResolve, PromisePrototypeCatch, ReflectApply, - RegExpPrototypeTest, + RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, SafePromiseAll, SafeSet, StringPrototypeIncludes, - StringPrototypeMatch, - StringPrototypeReplace, StringPrototypeSplit, StringPrototypeStartsWith, } = primordials; @@ -133,14 +132,14 @@ class ModuleJob { StringPrototypeIncludes(e.message, ' does not provide an export named')) { const splitStack = StringPrototypeSplit(e.stack, '\n'); - const parentFileUrl = StringPrototypeReplace( - splitStack[0], + const parentFileUrl = RegExpPrototypeSymbolReplace( /:\d+$/, + splitStack[0], '' ); - const { 1: childSpecifier, 2: name } = StringPrototypeMatch( - e.message, - /module '(.*)' does not provide an export named '(.+)'/); + const { 1: childSpecifier, 2: name } = RegExpPrototypeExec( + /module '(.*)' does not provide an export named '(.+)'/, + e.message); const { url: childFileURL } = await this.loader.resolve( childSpecifier, parentFileUrl, ); @@ -161,9 +160,9 @@ class ModuleJob { // line which causes the error. For multi-line import statements we // cannot generate an equivalent object destructuring assignment by // just parsing the error stack. - const oneLineNamedImports = StringPrototypeMatch(importStatement, /{.*}/); + const oneLineNamedImports = RegExpPrototypeExec(/{.*}/, importStatement); const destructuringAssignment = oneLineNamedImports && - StringPrototypeReplace(oneLineNamedImports, /\s+as\s+/g, ': '); + RegExpPrototypeSymbolReplace(/\s+as\s+/g, oneLineNamedImports, ': '); e.message = `Named export '${name}' not found. The requested module` + ` '${childSpecifier}' is a CommonJS module, which may not support` + ' all module.exports as named exports.\nCommonJS modules can ' + @@ -203,7 +202,7 @@ class ModuleJob { const packageConfig = StringPrototypeStartsWith(this.module.url, 'file://') && - RegExpPrototypeTest(/\.js(\?[^#]*)?(#.*)?$/, this.module.url) && + RegExpPrototypeExec(/\.js(\?[^#]*)?(#.*)?$/, this.module.url) !== null && require('internal/modules/esm/resolve') .getPackageScopeConfig(this.module.url); if (packageConfig.type === 'module') { diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index e9147aa83de800..fc5dcd6863dc35 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -13,7 +13,6 @@ const { RegExp, RegExpPrototypeExec, RegExpPrototypeSymbolReplace, - RegExpPrototypeTest, SafeMap, SafeSet, String, @@ -21,6 +20,7 @@ const { StringPrototypeIncludes, StringPrototypeIndexOf, StringPrototypeLastIndexOf, + StringPrototypeReplace, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, @@ -394,7 +394,7 @@ const encodedSepRegEx = /%2F|%5C/i; * @returns {URL | undefined} */ function finalizeResolution(resolved, base, preserveSymlinks) { - if (RegExpPrototypeTest(encodedSepRegEx, resolved.pathname)) + if (RegExpPrototypeExec(encodedSepRegEx, resolved.pathname) !== null) throw new ERR_INVALID_MODULE_SPECIFIER( resolved.pathname, 'must not include encoded "/" or "\\" characters', fileURLToPath(base)); @@ -523,7 +523,7 @@ function resolvePackageTargetString( throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); } - if (RegExpPrototypeTest(invalidSegmentRegEx, StringPrototypeSlice(target, 2))) + if (RegExpPrototypeExec(invalidSegmentRegEx, StringPrototypeSlice(target, 2)) !== null) throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); const resolved = new URL(target, packageJSONUrl); @@ -535,8 +535,11 @@ function resolvePackageTargetString( if (subpath === '') return resolved; - if (RegExpPrototypeTest(invalidSegmentRegEx, subpath)) - throwInvalidSubpath(match + subpath, packageJSONUrl, internal, base); + if (RegExpPrototypeExec(invalidSegmentRegEx, subpath) !== null) { + const request = pattern ? + StringPrototypeReplace(match, '*', () => subpath) : match + subpath; + throwInvalidSubpath(request, packageJSONUrl, internal, base); + } if (pattern) { return new URL( diff --git a/lib/internal/policy/manifest.js b/lib/internal/policy/manifest.js index a0cd9707a2c7d5..062e1ba219bbe8 100644 --- a/lib/internal/policy/manifest.js +++ b/lib/internal/policy/manifest.js @@ -10,12 +10,11 @@ const { ObjectKeys, ObjectSetPrototypeOf, RegExpPrototypeExec, - RegExpPrototypeTest, SafeMap, SafeSet, + RegExpPrototypeSymbolReplace, StringPrototypeEndsWith, StringPrototypeStartsWith, - StringPrototypeReplace, Symbol, uncurryThis, } = primordials; @@ -202,7 +201,7 @@ class DependencyMapperInstance { let ret; if (parsedURLs && parsedURLs.has(to)) { ret = parsedURLs.get(to); - } else if (RegExpPrototypeTest(kRelativeURLStringPattern, to)) { + } else if (RegExpPrototypeExec(kRelativeURLStringPattern, to) !== null) { ret = resolve(to, manifest.href); } else { ret = resolve(to); @@ -301,7 +300,7 @@ function findScopeHREF(href, scopeStore, allowSame) { if (href !== '') { // default URL parser does some stuff to special urls... skip if this is // just the protocol - if (RegExpPrototypeTest(/^[^:]*[:]$/, href)) { + if (RegExpPrototypeExec(/^[^:]*[:]$/, href) !== null) { protocol = href; } else { let currentURL = new URL(href); @@ -669,7 +668,7 @@ module.exports = ObjectFreeze({ Manifest }); */ function canonicalizeSpecifier(specifier, base) { try { - if (RegExpPrototypeTest(kRelativeURLStringPattern, specifier)) { + if (RegExpPrototypeExec(kRelativeURLStringPattern, specifier) !== null) { return resolve(specifier, base).href; } return resolve(specifier).href; @@ -690,13 +689,13 @@ const emptyOrProtocolOrResolve = (resourceHREF, base) => { if (resourceHREF === '') return ''; if (StringPrototypeEndsWith(resourceHREF, ':')) { // URL parse will trim these anyway, save the compute - resourceHREF = StringPrototypeReplace( - resourceHREF, + resourceHREF = RegExpPrototypeSymbolReplace( // eslint-disable-next-line /^[\x00-\x1F\x20]|\x09\x0A\x0D|[\x00-\x1F\x20]$/g, + resourceHREF, '' ); - if (RegExpPrototypeTest(/^[a-zA-Z][a-zA-Z+\-.]*:$/, resourceHREF)) { + if (RegExpPrototypeExec(/^[a-zA-Z][a-zA-Z+\-.]*:$/, resourceHREF) !== null) { return resourceHREF; } } @@ -718,7 +717,7 @@ const resolve = (originalHREF, base) => { parsedURLs = parsedURLs ?? new SafeMap(); if (parsedURLs.has(originalHREF)) { return parsedURLs.get(originalHREF); - } else if (RegExpPrototypeTest(kRelativeURLStringPattern, originalHREF)) { + } else if (RegExpPrototypeExec(kRelativeURLStringPattern, originalHREF) !== null) { const resourceURL = new URL(originalHREF, base); parsedURLs.set(resourceURL.href, resourceURL); return resourceURL; diff --git a/lib/internal/policy/sri.js b/lib/internal/policy/sri.js index 0d8c24e89ae8aa..72f7798ce4f333 100644 --- a/lib/internal/policy/sri.js +++ b/lib/internal/policy/sri.js @@ -10,7 +10,6 @@ const { ObjectSetPrototypeOf, RegExp, RegExpPrototypeExec, - RegExpPrototypeTest, StringPrototypeSlice, } = primordials; @@ -62,7 +61,7 @@ const parse = (str) => { } if (prevIndex !== str.length) { - if (!RegExpPrototypeTest(kAllWSP, StringPrototypeSlice(str, prevIndex))) { + if (RegExpPrototypeExec(kAllWSP, StringPrototypeSlice(str, prevIndex)) === null) { throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex); } } diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index f80d078444047c..c2be274659e6c7 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -17,7 +17,7 @@ const { ObjectFreeze, ObjectDefineProperty, ReflectApply, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeArrayIterator, Set, SetPrototypeEntries, @@ -340,7 +340,7 @@ function buildAllowedFlags() { // not. if (typeof key === 'string') { key = StringPrototypeReplace(key, replaceUnderscoresRegex, '-'); - if (RegExpPrototypeTest(leadingDashesRegex, key)) { + if (RegExpPrototypeExec(leadingDashesRegex, key) !== null) { key = StringPrototypeReplace(key, trailingValuesRegex, ''); return ArrayPrototypeIncludes(this[kInternal].array, key); } diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 63383658879095..1431ae792825cf 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -19,12 +19,12 @@ const { NumberIsFinite, NumberIsNaN, ObjectSetPrototypeOf, - RegExpPrototypeTest, + RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, + RegExpPrototypeSymbolSplit, StringPrototypeCodePointAt, StringPrototypeEndsWith, - StringPrototypeMatch, StringPrototypeRepeat, - StringPrototypeReplace, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, @@ -551,12 +551,12 @@ class Interface extends InterfaceConstructor { this[kSawReturnAt] && DateNow() - this[kSawReturnAt] <= this.crlfDelay ) { - string = StringPrototypeReplace(string, /^\n/, ''); + string = RegExpPrototypeSymbolReplace(/^\n/, string, ''); this[kSawReturnAt] = 0; } // Run test() on the new string chunk, not on the entire line buffer. - const newPartContainsEnding = RegExpPrototypeTest(lineEnding, string); + const newPartContainsEnding = RegExpPrototypeExec(lineEnding, string) !== null; if (this[kLine_buffer]) { string = this[kLine_buffer] + string; @@ -694,7 +694,7 @@ class Interface extends InterfaceConstructor { ArrayPrototypeReverse(ArrayFrom(leading)), '' ); - const match = StringPrototypeMatch(reversed, /^\s*(?:[^\w\s]+|\w+)?/); + const match = RegExpPrototypeExec(/^\s*(?:[^\w\s]+|\w+)?/, reversed); this[kMoveCursor](-match[0].length); } } @@ -702,7 +702,7 @@ class Interface extends InterfaceConstructor { [kWordRight]() { if (this.cursor < this.line.length) { const trailing = StringPrototypeSlice(this.line, this.cursor); - const match = StringPrototypeMatch(trailing, /^(?:\s+|[^\w\s]+|\w+)\s*/); + const match = RegExpPrototypeExec(/^(?:\s+|[^\w\s]+|\w+)\s*/, trailing); this[kMoveCursor](match[0].length); } } @@ -744,7 +744,7 @@ class Interface extends InterfaceConstructor { ArrayPrototypeReverse(ArrayFrom(leading)), '' ); - const match = StringPrototypeMatch(reversed, /^\s*(?:[^\w\s]+|\w+)?/); + const match = RegExpPrototypeExec(/^\s*(?:[^\w\s]+|\w+)?/, reversed); leading = StringPrototypeSlice( leading, 0, @@ -761,7 +761,7 @@ class Interface extends InterfaceConstructor { [kDeleteWordRight]() { if (this.cursor < this.line.length) { const trailing = StringPrototypeSlice(this.line, this.cursor); - const match = StringPrototypeMatch(trailing, /^(?:\s+|\W+|\w+)\s*/); + const match = RegExpPrototypeExec(/^(?:\s+|\W+|\w+)\s*/, trailing); this.line = StringPrototypeSlice(this.line, 0, this.cursor) + StringPrototypeSlice(trailing, match[0].length); @@ -1164,7 +1164,7 @@ class Interface extends InterfaceConstructor { // falls through default: if (typeof s === 'string' && s) { - const lines = StringPrototypeSplit(s, /\r\n|\n|\r/); + const lines = RegExpPrototypeSymbolSplit(/\r\n|\n|\r/, s); for (let i = 0, len = lines.length; i < len; i++) { if (i > 0) { this[kLine](); diff --git a/lib/internal/readline/utils.js b/lib/internal/readline/utils.js index 98679494a3e64b..83609b71fb5974 100644 --- a/lib/internal/readline/utils.js +++ b/lib/internal/readline/utils.js @@ -3,11 +3,10 @@ const { ArrayPrototypeSlice, ArrayPrototypeSort, - RegExpPrototypeTest, + RegExpPrototypeExec, StringFromCharCode, StringPrototypeCharCodeAt, StringPrototypeCodePointAt, - StringPrototypeMatch, StringPrototypeSlice, StringPrototypeToLowerCase, Symbol, @@ -190,11 +189,11 @@ function* emitKeys(stream) { const cmd = StringPrototypeSlice(s, cmdStart); let match; - if ((match = StringPrototypeMatch(cmd, /^(\d\d?)(;(\d))?([~^$])$/))) { + if ((match = RegExpPrototypeExec(/^(\d\d?)(;(\d))?([~^$])$/, cmd))) { code += match[1] + match[4]; modifier = (match[3] || 1) - 1; } else if ( - (match = StringPrototypeMatch(cmd, /^((\d;)?(\d))?([A-Za-z])$/)) + (match = RegExpPrototypeExec(/^((\d;)?(\d))?([A-Za-z])$/, cmd)) ) { code += match[4]; modifier = (match[3] || 1) - 1; @@ -340,10 +339,10 @@ function* emitKeys(stream) { StringPrototypeCharCodeAt(ch) + StringPrototypeCharCodeAt('a') - 1 ); key.ctrl = true; - } else if (RegExpPrototypeTest(/^[0-9A-Za-z]$/, ch)) { + } else if (RegExpPrototypeExec(/^[0-9A-Za-z]$/, ch) !== null) { // Letter, number, shift+letter key.name = StringPrototypeToLowerCase(ch); - key.shift = RegExpPrototypeTest(/^[A-Z]$/, ch); + key.shift = RegExpPrototypeExec(/^[A-Z]$/, ch) !== null; key.meta = escaped; } else if (escaped) { // Escape sequence timeout diff --git a/lib/internal/repl/history.js b/lib/internal/repl/history.js index 74ef94e81070dc..9300283e0015cc 100644 --- a/lib/internal/repl/history.js +++ b/lib/internal/repl/history.js @@ -4,7 +4,7 @@ const { ArrayPrototypeJoin, Boolean, FunctionPrototype, - StringPrototypeSplit, + RegExpPrototypeSymbolSplit, StringPrototypeTrim, } = primordials; @@ -90,7 +90,7 @@ function setupHistory(repl, historyPath, ready) { } if (data) { - repl.history = StringPrototypeSplit(data, /[\n\r]+/, repl.historySize); + repl.history = RegExpPrototypeSymbolSplit(/[\n\r]+/, data, repl.historySize); } else { repl.history = []; } diff --git a/lib/internal/repl/utils.js b/lib/internal/repl/utils.js index ee8c54be5bb5c3..9e55b79fc7e86c 100644 --- a/lib/internal/repl/utils.js +++ b/lib/internal/repl/utils.js @@ -7,13 +7,13 @@ const { Boolean, FunctionPrototypeBind, MathMin, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeSet, SafeStringIterator, StringPrototypeEndsWith, StringPrototypeIndexOf, StringPrototypeLastIndexOf, - StringPrototypeReplace, + StringPrototypeReplaceAll, StringPrototypeSlice, StringPrototypeStartsWith, StringPrototypeToLowerCase, @@ -68,7 +68,7 @@ function isRecoverableError(e, code) { // curly brace with parenthesis. Note: only the open parenthesis is added // here as the point is to test for potentially valid but incomplete // expressions. - if (RegExpPrototypeTest(/^\s*\{/, code) && + if (RegExpPrototypeExec(/^\s*\{/, code) !== null && isRecoverableError(e, `(${code}`)) return true; @@ -114,8 +114,8 @@ function isRecoverableError(e, code) { const token = StringPrototypeSlice(this.input, this.lastTokStart, this.pos); // See https://www.ecma-international.org/ecma-262/#sec-line-terminators - if (RegExpPrototypeTest(/\\(?:\r\n?|\n|\u2028|\u2029)$/, - token)) { + if (RegExpPrototypeExec(/\\(?:\r\n?|\n|\u2028|\u2029)$/, + token) !== null) { recoverable = true; } } @@ -288,9 +288,9 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) { function isInStrictMode(repl) { return repl.replMode === REPL_MODE_STRICT || ArrayPrototypeIncludes( ArrayPrototypeMap(process.execArgv, - (e) => StringPrototypeReplace( + (e) => StringPrototypeReplaceAll( StringPrototypeToLowerCase(e), - /_/g, + '_', '-' )), '--use-strict'); diff --git a/lib/internal/source_map/prepare_stack_trace.js b/lib/internal/source_map/prepare_stack_trace.js index 8420bd230fa2e9..55dc8b344f8be6 100644 --- a/lib/internal/source_map/prepare_stack_trace.js +++ b/lib/internal/source_map/prepare_stack_trace.js @@ -5,9 +5,9 @@ const { ArrayPrototypeJoin, ArrayPrototypeMap, ErrorPrototypeToString, + RegExpPrototypeSymbolSplit, StringPrototypeRepeat, StringPrototypeSlice, - StringPrototypeSplit, StringPrototypeStartsWith, SafeStringIterator, } = primordials; @@ -157,7 +157,7 @@ function getErrorSource( sourceMap.payload, originalSourcePathNoScheme ); - const lines = StringPrototypeSplit(source, /\r?\n/, originalLine + 1); + const lines = RegExpPrototypeSymbolSplit(/\r?\n/, source, originalLine + 1); const line = lines[originalLine]; if (!line) return exceptionLine; diff --git a/lib/internal/source_map/source_map_cache.js b/lib/internal/source_map/source_map_cache.js index bc212b27ff8670..c7770e7a6c733b 100644 --- a/lib/internal/source_map/source_map_cache.js +++ b/lib/internal/source_map/source_map_cache.js @@ -8,8 +8,8 @@ const { ObjectGetOwnPropertyDescriptor, ObjectPrototypeHasOwnProperty, RegExpPrototypeExec, + RegExpPrototypeSymbolSplit, SafeMap, - StringPrototypeMatch, StringPrototypeSplit, } = primordials; @@ -87,9 +87,9 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSo debug(err); return; } - const match = StringPrototypeMatch( + const match = RegExpPrototypeExec( + /\/[*/]#\s+sourceMappingURL=(?[^\s]+)/, content, - /\/[*/]#\s+sourceMappingURL=(?[^\s]+)/ ); if (match) { const data = dataFromUrl(filename, match.groups.sourceMappingURL); @@ -169,7 +169,7 @@ function lineLengths(content) { // We purposefully keep \r as part of the line-length calculation, in // cases where there is a \r\n separator, so that this can be taken into // account in coverage calculations. - return ArrayPrototypeMap(StringPrototypeSplit(content, /\n|\u2028|\u2029/), (line) => { + return ArrayPrototypeMap(RegExpPrototypeSymbolSplit(/\n|\u2028|\u2029/, content), (line) => { return line.length; }); } diff --git a/lib/internal/tty.js b/lib/internal/tty.js index 5abf0b908b02cc..4f285e92829637 100644 --- a/lib/internal/tty.js +++ b/lib/internal/tty.js @@ -24,7 +24,7 @@ const { ArrayPrototypeSome, - RegExpPrototypeTest, + RegExpPrototypeExec, StringPrototypeSplit, StringPrototypeToLowerCase, } = primordials; @@ -174,14 +174,14 @@ function getColorDepth(env = process.env) { } if ('TEAMCITY_VERSION' in env) { - return RegExpPrototypeTest(/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/, env.TEAMCITY_VERSION) ? + return RegExpPrototypeExec(/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/, env.TEAMCITY_VERSION) !== null ? COLORS_16 : COLORS_2; } switch (env.TERM_PROGRAM) { case 'iTerm.app': if (!env.TERM_PROGRAM_VERSION || - RegExpPrototypeTest(/^[0-2]\./, env.TERM_PROGRAM_VERSION) + RegExpPrototypeExec(/^[0-2]\./, env.TERM_PROGRAM_VERSION) !== null ) { return COLORS_256; } @@ -198,7 +198,7 @@ function getColorDepth(env = process.env) { } if (env.TERM) { - if (RegExpPrototypeTest(/^xterm-256/, env.TERM)) { + if (RegExpPrototypeExec(/^xterm-256/, env.TERM) !== null) { return COLORS_256; } @@ -208,7 +208,7 @@ function getColorDepth(env = process.env) { return TERM_ENVS[termEnv]; } if (ArrayPrototypeSome(TERM_ENVS_REG_EXP, - (term) => RegExpPrototypeTest(term, termEnv))) { + (term) => RegExpPrototypeExec(term, termEnv) !== null)) { return COLORS_16; } } diff --git a/lib/internal/util.js b/lib/internal/util.js index b90a769f4828ff..ea6d19683fed45 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -19,7 +19,6 @@ const { ReflectApply, ReflectConstruct, RegExpPrototypeExec, - RegExpPrototypeTest, SafeMap, SafeSet, StringPrototypeReplace, @@ -430,9 +429,9 @@ function isInsideNodeModules() { const filename = frame.getFileName(); // If a filename does not start with / or contain \, // it's likely from Node.js core. - if (!RegExpPrototypeTest(/^\/|\\/, filename)) + if (RegExpPrototypeExec(/^\/|\\/, filename) === null) continue; - return RegExpPrototypeTest(kNodeModulesRE, filename); + return RegExpPrototypeExec(kNodeModulesRE, filename) !== null; } } return false; diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 17d567eb9db368..f38df74ed6af15 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -49,7 +49,8 @@ const { ObjectSetPrototypeOf, ReflectOwnKeys, RegExp, - RegExpPrototypeTest, + RegExpPrototypeExec, + RegExpPrototypeSymbolReplace, RegExpPrototypeToString, SafeStringIterator, SafeMap, @@ -64,7 +65,6 @@ const { StringPrototypePadEnd, StringPrototypePadStart, StringPrototypeRepeat, - StringPrototypeReplace, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeToLowerCase, @@ -153,7 +153,7 @@ function pathToFileUrlHref(filepath) { const builtInObjects = new SafeSet( ArrayPrototypeFilter( ObjectGetOwnPropertyNames(globalThis), - (e) => RegExpPrototypeTest(/^[A-Z][a-zA-Z0-9]+$/, e) + (e) => RegExpPrototypeExec(/^[A-Z][a-zA-Z0-9]+$/, e) !== null ) ); @@ -502,10 +502,10 @@ function strEscape(str) { } // Some magic numbers that worked out fine while benchmarking with v8 6.0 - if (str.length < 5000 && !RegExpPrototypeTest(escapeTest, str)) + if (str.length < 5000 && RegExpPrototypeExec(escapeTest, str) === null) return addQuotes(str, singleQuote); if (str.length > 100) { - str = StringPrototypeReplace(str, escapeReplace, escapeFn); + str = RegExpPrototypeSymbolReplace(escapeReplace, str, escapeFn); return addQuotes(str, singleQuote); } @@ -1694,9 +1694,10 @@ function formatArrayBuffer(ctx, value) { } if (hexSlice === undefined) hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice); - let str = StringPrototypeTrim(StringPrototypeReplace( + let str = StringPrototypeTrim(RegExpPrototypeSymbolReplace( + /(.{2})/g, hexSlice(buffer, 0, MathMin(ctx.maxArrayLength, buffer.length)), - /(.{2})/g, '$1 ')); + '$1 ')); const remaining = buffer.length - ctx.maxArrayLength; if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; @@ -1928,18 +1929,19 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc, return str; } if (typeof key === 'symbol') { - const tmp = StringPrototypeReplace( + const tmp = RegExpPrototypeSymbolReplace( + strEscapeSequencesReplacer, SymbolPrototypeToString(key), - strEscapeSequencesReplacer, escapeFn + escapeFn ); name = `[${ctx.stylize(tmp, 'symbol')}]`; } else if (key === '__proto__') { name = "['__proto__']"; } else if (desc.enumerable === false) { - const tmp = StringPrototypeReplace(key, - strEscapeSequencesReplacer, escapeFn); + const tmp = RegExpPrototypeSymbolReplace( + strEscapeSequencesReplacer, key, escapeFn); name = `[${tmp}]`; - } else if (RegExpPrototypeTest(keyStrRegExp, key)) { + } else if (RegExpPrototypeExec(keyStrRegExp, key) !== null) { name = ctx.stylize(key, 'name'); } else { name = ctx.stylize(strEscape(key), 'string'); diff --git a/lib/internal/validators.js b/lib/internal/validators.js index fbf09d2d352240..11cccf2040cd36 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -10,7 +10,7 @@ const { NumberMAX_SAFE_INTEGER, NumberMIN_SAFE_INTEGER, NumberParseInt, - RegExpPrototypeTest, + RegExpPrototypeExec, String, StringPrototypeToUpperCase, StringPrototypeTrim, @@ -60,7 +60,7 @@ const modeDesc = 'must be a 32-bit unsigned integer or an octal string'; function parseFileMode(value, name, def) { value ??= def; if (typeof value === 'string') { - if (!RegExpPrototypeTest(octalReg, value)) { + if (RegExpPrototypeExec(octalReg, value) === null) { throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); } value = NumberParseInt(value, 8); diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 0cede10ca4bf9b..8e396195209b83 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -13,7 +13,7 @@ const { Promise, PromiseResolve, ReflectApply, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeArrayIterator, SafeMap, String, @@ -158,7 +158,7 @@ class Worker extends EventEmitter { filename ); } else if (path.isAbsolute(filename) || - RegExpPrototypeTest(/^\.\.?[\\/]/, filename)) { + RegExpPrototypeExec(/^\.\.?[\\/]/, filename) !== null) { filename = path.resolve(filename); url = pathToFileURL(filename); } else { diff --git a/lib/repl.js b/lib/repl.js index 3d0980dd25d4e2..8a0533191c3970 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -78,17 +78,15 @@ const { RegExp, RegExpPrototypeExec, RegExpPrototypeSymbolReplace, - RegExpPrototypeTest, SafePromiseRace, + RegExpPrototypeSymbolSplit, SafeSet, SafeWeakSet, StringPrototypeCharAt, StringPrototypeCodePointAt, StringPrototypeEndsWith, StringPrototypeIncludes, - StringPrototypeMatch, StringPrototypeRepeat, - StringPrototypeReplace, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, @@ -423,8 +421,8 @@ function REPLServer(prompt, // to wrap it in parentheses, so that it will be interpreted as // an expression. Note that if the above condition changes, // lib/internal/repl/utils.js needs to be changed to match. - if (RegExpPrototypeTest(/^\s*{/, code) && - !RegExpPrototypeTest(/;\s*$/, code)) { + if (RegExpPrototypeExec(/^\s*{/, code) !== null && + RegExpPrototypeExec(/;\s*$/, code) === null) { code = `(${StringPrototypeTrim(code)})\n`; wrappedCmd = true; } @@ -499,7 +497,7 @@ function REPLServer(prompt, while (true) { try { if (self.replMode === module.exports.REPL_MODE_STRICT && - !RegExpPrototypeTest(/^\s*$/, code)) { + RegExpPrototypeExec(/^\s*$/, code) === null) { // "void 0" keeps the repl from returning "use strict" as the result // value for statements and declarations that don't return a value. code = `'use strict'; void 0;\n${code}`; @@ -683,22 +681,24 @@ function REPLServer(prompt, if (e.stack) { if (e.name === 'SyntaxError') { // Remove stack trace. - e.stack = StringPrototypeReplace(StringPrototypeReplace(e.stack, - /^REPL\d+:\d+\r?\n/, ''), - /^\s+at\s.*\n?/gm, ''); + e.stack = RegExpPrototypeSymbolReplace( + /^\s+at\s.*\n?/gm, + RegExpPrototypeSymbolReplace(/^REPL\d+:\d+\r?\n/, e.stack, ''), + ''); const importErrorStr = 'Cannot use import statement outside a ' + 'module'; if (StringPrototypeIncludes(e.message, importErrorStr)) { e.message = 'Cannot use import statement inside the Node.js ' + 'REPL, alternatively use dynamic import'; - e.stack = StringPrototypeReplace(e.stack, - /SyntaxError:.*\n/, - `SyntaxError: ${e.message}\n`); + e.stack = RegExpPrototypeSymbolReplace( + /SyntaxError:.*\n/, + e.stack, + `SyntaxError: ${e.message}\n`); } } else if (self.replMode === module.exports.REPL_MODE_STRICT) { - e.stack = StringPrototypeReplace( - e.stack, + e.stack = RegExpPrototypeSymbolReplace( /(\s+at\s+REPL\d+:)(\d+)/, + e.stack, (_, pre, line) => pre + (line - 1) ); } @@ -728,13 +728,13 @@ function REPLServer(prompt, if (errStack === '') { errStack = self.writer(e); } - const lines = StringPrototypeSplit(errStack, /(?<=\n)/); + const lines = RegExpPrototypeSymbolSplit(/(?<=\n)/, errStack); let matched = false; errStack = ''; ArrayPrototypeForEach(lines, (line) => { if (!matched && - RegExpPrototypeTest(/^\[?([A-Z][a-z0-9_]*)*Error/, line)) { + RegExpPrototypeExec(/^\[?([A-Z][a-z0-9_]*)*Error/, line) !== null) { errStack += writer.options.breakLength >= line.length ? `Uncaught ${line}` : `Uncaught:\n${line}`; @@ -862,7 +862,7 @@ function REPLServer(prompt, // code alignment const matches = self._sawKeyPress ? - StringPrototypeMatch(cmd, /^\s+/) : null; + RegExpPrototypeExec(/^\s+/, cmd) : null; if (matches) { const prefix = matches[0]; self.write(prefix); @@ -882,7 +882,7 @@ function REPLServer(prompt, if (StringPrototypeCharAt(trimmedCmd, 0) === '.' && StringPrototypeCharAt(trimmedCmd, 1) !== '.' && NumberIsNaN(NumberParseFloat(trimmedCmd))) { - const matches = StringPrototypeMatch(trimmedCmd, /^\.([^\s]+)\s*(.*)$/); + const matches = RegExpPrototypeExec(/^\.([^\s]+)\s*(.*)$/, trimmedCmd); const keyword = matches && matches[1]; const rest = matches && matches[2]; if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) { @@ -1262,9 +1262,9 @@ function gracefulReaddir(...args) { } } -function completeFSFunctions(line) { +function completeFSFunctions(match) { let baseName = ''; - let filePath = StringPrototypeMatch(line, fsAutoCompleteRE)[1]; + let filePath = match[1]; let fileList = gracefulReaddir(filePath, { withFileTypes: true }); if (!fileList) { @@ -1303,16 +1303,17 @@ function complete(line, callback) { line = StringPrototypeTrimLeft(line); let filter = ''; + + let match; // REPL commands (e.g. ".break"). - if (RegExpPrototypeTest(/^\s*\.(\w*)$/, line)) { + if ((match = RegExpPrototypeExec(/^\s*\.(\w*)$/, line)) !== null) { ArrayPrototypePush(completionGroups, ObjectKeys(this.commands)); - completeOn = StringPrototypeMatch(line, /^\s*\.(\w*)$/)[1]; + completeOn = match[1]; if (completeOn.length) { filter = completeOn; } - } else if (RegExpPrototypeTest(requireRE, line)) { + } else if ((match = RegExpPrototypeExec(requireRE, line)) !== null) { // require('...') - const match = StringPrototypeMatch(line, requireRE); completeOn = match[1]; filter = completeOn; if (this.allowBlockingCompletions) { @@ -1329,7 +1330,7 @@ function complete(line, callback) { group = ['./', '../']; } else if (completeOn === '..') { group = ['../']; - } else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) { + } else if (RegExpPrototypeExec(/^\.\.?\//, completeOn) !== null) { paths = [process.cwd()]; } else { paths = ArrayPrototypeConcat(module.paths, CJSModule.globalPaths); @@ -1339,7 +1340,7 @@ function complete(line, callback) { dir = path.resolve(dir, subdir); const dirents = gracefulReaddir(dir, { withFileTypes: true }) || []; ArrayPrototypeForEach(dirents, (dirent) => { - if (RegExpPrototypeTest(versionedFileNamesRe, dirent.name) || + if (RegExpPrototypeExec(versionedFileNamesRe, dirent.name) !== null || dirent.name === '.npm') { // Exclude versioned names that 'npm' installs. return; @@ -1369,9 +1370,8 @@ function complete(line, callback) { } ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs); - } else if (RegExpPrototypeTest(importRE, line)) { + } else if ((match = RegExpPrototypeExec(importRE, line)) !== null) { // import('...') - const match = StringPrototypeMatch(line, importRE); completeOn = match[1]; filter = completeOn; if (this.allowBlockingCompletions) { @@ -1392,7 +1392,7 @@ function complete(line, callback) { group = ['./', '../']; } else if (completeOn === '..') { group = ['../']; - } else if (RegExpPrototypeTest(/^\.\.?\//, completeOn)) { + } else if (RegExpPrototypeExec(/^\.\.?\//, completeOn) !== null) { paths = [process.cwd()]; } else { paths = ArrayPrototypeSlice(module.paths); @@ -1404,7 +1404,7 @@ function complete(line, callback) { const dirents = gracefulReaddir(dir, { withFileTypes: true }) || []; ArrayPrototypeForEach(dirents, (dirent) => { const { name } = dirent; - if (RegExpPrototypeTest(versionedFileNamesRe, name) || + if (RegExpPrototypeExec(versionedFileNamesRe, name) !== null || name === '.npm') { // Exclude versioned names that 'npm' installs. return; @@ -1437,9 +1437,9 @@ function complete(line, callback) { } ArrayPrototypePush(completionGroups, _builtinLibs, nodeSchemeBuiltinLibs); - } else if (RegExpPrototypeTest(fsAutoCompleteRE, line) && + } else if ((match = RegExpPrototypeExec(fsAutoCompleteRE, line)) !== null && this.allowBlockingCompletions) { - ({ 0: completionGroups, 1: completeOn } = completeFSFunctions(line)); + ({ 0: completionGroups, 1: completeOn } = completeFSFunctions(match)); // Handle variable member lookup. // We support simple chained expressions like the following (no function // calls, etc.). That is for simplicity and also because we *eval* that @@ -1451,7 +1451,7 @@ function complete(line, callback) { // foo<|> # all scope vars with filter 'foo' // foo.<|> # completions for 'foo' with filter '' } else if (line.length === 0 || - RegExpPrototypeTest(/\w|\.|\$/, line[line.length - 1])) { + RegExpPrototypeExec(/\w|\.|\$/, line[line.length - 1]) !== null) { const { 0: match } = RegExpPrototypeExec(simpleExpressionRE, line) || ['']; if (line.length !== 0 && !match) { completionGroupsLoaded(); @@ -1640,14 +1640,17 @@ function _memory(cmd) { // I need to know "depth." // Because I can not tell the difference between a } that // closes an object literal and a } that closes a function + const countMatches = (regex, str) => { + let count = 0; + while (RegExpPrototypeExec(regex, str) !== null) count++; + return count; + }; // Going down is { and ( e.g. function() { // going up is } and ) - let dw = StringPrototypeMatch(cmd, /[{(]/g); - let up = StringPrototypeMatch(cmd, /[})]/g); - up = up ? up.length : 0; - dw = dw ? dw.length : 0; - let depth = dw - up; + const dw = countMatches(/[{(]/g, cmd); + const up = countMatches(/[})]/g, cmd); + let depth = dw.length - up.length; if (depth) { (function workIt() { diff --git a/lib/tls.js b/lib/tls.js index 78e62091657b1a..1ada006635d4d3 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -34,13 +34,12 @@ const { ObjectDefineProperty, ObjectFreeze, RegExpPrototypeExec, - RegExpPrototypeTest, + RegExpPrototypeSymbolReplace, StringFromCharCode, StringPrototypeCharCodeAt, StringPrototypeEndsWith, StringPrototypeIncludes, StringPrototypeIndexOf, - StringPrototypeReplace, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, @@ -161,7 +160,7 @@ exports.convertALPNProtocols = function convertALPNProtocols(protocols, out) { }; function unfqdn(host) { - return StringPrototypeReplace(host, /[.]$/, ''); + return RegExpPrototypeSymbolReplace(/[.]$/, host, ''); } // String#toLowerCase() is locale-sensitive so we use @@ -172,7 +171,7 @@ function toLowerCase(c) { function splitHost(host) { return StringPrototypeSplit( - StringPrototypeReplace(unfqdn(host), /[A-Z]/g, toLowerCase), + RegExpPrototypeSymbolReplace(/[A-Z]/g, unfqdn(host), toLowerCase), '.' ); } @@ -195,7 +194,7 @@ function check(hostParts, pattern, wildcards) { // good way to detect their encoding or normalize them so we simply // reject them. Control characters and blanks are rejected as well // because nothing good can come from accepting them. - const isBad = (s) => RegExpPrototypeTest(/[^\u0021-\u007F]/u, s); + const isBad = (s) => RegExpPrototypeExec(/[^\u0021-\u007F]/u, s) !== null; if (ArrayPrototypeSome(patternParts, isBad)) return false; diff --git a/test/parallel/test-errors-systemerror.js b/test/parallel/test-errors-systemerror.js index 2a20588e75b386..38afbd4aa7d164 100644 --- a/test/parallel/test-errors-systemerror.js +++ b/test/parallel/test-errors-systemerror.js @@ -9,7 +9,7 @@ assert.throws( () => { new SystemError(); }, { name: 'TypeError', - message: 'String.prototype.match called on null or undefined' + message: "Cannot read properties of undefined (reading 'syscall')", } );