diff --git a/lib/assert.js b/lib/assert.js index 61d6bda6b37632..b1e16ecff787bd 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -35,16 +35,18 @@ const { ObjectKeys, ObjectPrototypeIsPrototypeOf, ReflectApply, - RegExpPrototypeTest, + RegExpPrototypeExec, SafeMap, String, StringPrototypeCharCodeAt, StringPrototypeIncludes, StringPrototypeIndexOf, - StringPrototypeReplace, StringPrototypeSlice, StringPrototypeSplit, StringPrototypeStartsWith, + SafeRegExpPrototypeSymbolReplace, + SafeRegExpPrototypeTest, + makeSafeStringObject, } = primordials; const { Buffer } = require('buffer'); @@ -283,9 +285,11 @@ function parseCode(code, offset) { return [ node.node.start, - StringPrototypeReplace(StringPrototypeSlice(code, - node.node.start, node.node.end), - escapeSequencesRegExp, escapeFn), + SafeRegExpPrototypeSymbolReplace( + escapeSequencesRegExp, + StringPrototypeSlice(code, node.node.start, node.node.end), + escapeFn + ), ]; } @@ -357,17 +361,21 @@ 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'); - } - const frames = StringPrototypeSplit(message, '\n'); - message = ArrayPrototypeShift(frames); - for (const frame of frames) { - let pos = 0; - while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) { + const splitter = EOL === '\r\n' ? /\r\n/g : /\n/g; + const splitterLength = EOL === '\r\n' ? 2 : 1; + const str = message; + let previousIndex = RegExpPrototypeExec(splitter, str).index; + message = StringPrototypeSlice(message, 0, previousIndex); + while (previousIndex != null) { + const lineEndIndex = RegExpPrototypeExec(splitter, str)?.index; + previousIndex += splitterLength; + let pos = previousIndex; + while (pos < previousIndex + column && + (str[pos] === ' ' || str[pos] === '\t')) { pos++; } - message += `\n ${StringPrototypeSlice(frame, pos)}`; + message += `\n ${StringPrototypeSlice(str, pos, lineEndIndex)}`; + previousIndex = lineEndIndex; } } message = `The expression evaluated to a falsy value:\n\n ${message}\n`; @@ -619,7 +627,7 @@ class Comparison { if (actual !== undefined && typeof actual[key] === 'string' && isRegExp(obj[key]) && - RegExpPrototypeTest(obj[key], actual[key])) { + SafeRegExpPrototypeTest(obj[key], actual[key])) { this[key] = actual[key]; } else { this[key] = obj[key]; @@ -665,7 +673,7 @@ function expectedException(actual, expected, message, fn) { // Handle regular expressions. if (isRegExp(expected)) { const str = String(actual); - if (RegExpPrototypeTest(expected, str)) + if (SafeRegExpPrototypeTest(expected, str)) return; if (!message) { @@ -700,7 +708,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])) { + SafeRegExpPrototypeTest(expected[key], actual[key])) { continue; } compareExceptionKey(actual, expected, key, message, keys, fn); @@ -864,7 +872,7 @@ function hasMatchingError(actual, expected) { if (typeof expected !== 'function') { if (isRegExp(expected)) { const str = String(actual); - return RegExpPrototypeTest(expected, str); + return SafeRegExpPrototypeTest(expected, str); } throw new ERR_INVALID_ARG_TYPE( 'expected', ['Function', 'RegExp'], expected @@ -978,10 +986,11 @@ assert.ifError = function ifError(err) { // This will remove any duplicated frames from the error frames taken // from within `ifError` and add the original error frames to the newly // created ones. - const tmp2 = StringPrototypeSplit(origStack, '\n'); + const LINE_RETURN = makeSafeStringObject('\n'); + const tmp2 = StringPrototypeSplit(origStack, LINE_RETURN); ArrayPrototypeShift(tmp2); // Filter all frames existing in err.stack. - let tmp1 = StringPrototypeSplit(newErr.stack, '\n'); + let tmp1 = StringPrototypeSplit(newErr.stack, LINE_RETURN); for (const errFrame of tmp2) { // Find the first occurrence of the frame. const pos = ArrayPrototypeIndexOf(tmp1, errFrame); @@ -1007,7 +1016,7 @@ function internalMatch(string, regexp, message, fn) { } const match = fn.name === 'match'; if (typeof string !== 'string' || - RegExpPrototypeTest(regexp, string) !== match) { + SafeRegExpPrototypeTest(regexp, string) !== match) { if (message instanceof Error) { throw message; } diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index 837c37b1221540..49f21702f13601 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -15,6 +15,7 @@ const { StringPrototypeRepeat, StringPrototypeSlice, StringPrototypeSplit, + makeSafeStringObject, } = primordials; const { inspect } = require('internal/util/inspect'); @@ -80,14 +81,16 @@ function inspectValue(val) { ); } +const SAFE_LINE_RETURN = makeSafeStringObject('\n'); function createErrDiff(actual, expected, operator) { let other = ''; let res = ''; let end = ''; let skipped = false; const actualInspected = inspectValue(actual); - const actualLines = StringPrototypeSplit(actualInspected, '\n'); - const expectedLines = StringPrototypeSplit(inspectValue(expected), '\n'); + const actualLines = StringPrototypeSplit(actualInspected, SAFE_LINE_RETURN); + const expectedLines = StringPrototypeSplit(inspectValue(expected), + SAFE_LINE_RETURN); let i = 0; let indicator = ''; @@ -164,7 +167,7 @@ function createErrDiff(actual, expected, operator) { // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() }) if (maxLines === 0) { // We have to get the result again. The lines were all removed before. - const actualLines = StringPrototypeSplit(actualInspected, '\n'); + const actualLines = StringPrototypeSplit(actualInspected, SAFE_LINE_RETURN); // Only remove lines in case it makes sense to collapse those. // TODO: Accept env to always show the full error. @@ -315,7 +318,7 @@ function createErrDiff(actual, expected, operator) { } function addEllipsis(string) { - const lines = StringPrototypeSplit(string, '\n', 11); + const lines = StringPrototypeSplit(string, SAFE_LINE_RETURN, 11); if (lines.length > 10) { lines.length = 10; return `${ArrayPrototypeJoin(lines, '\n')}\n...`; @@ -380,7 +383,8 @@ class AssertionError extends Error { // In case the objects are equal but the operator requires unequal, show // the first object and say A equals B let base = kReadableOperator[operator]; - const res = StringPrototypeSplit(inspectValue(actual), '\n'); + const res = StringPrototypeSplit(inspectValue(actual), + SAFE_LINE_RETURN); // In case "actual" is an object or a function, it should not be // reference equal. diff --git a/lib/internal/per_context/primordials.js b/lib/internal/per_context/primordials.js index 42250ffb422d6e..5cb64c620da3b8 100644 --- a/lib/internal/per_context/primordials.js +++ b/lib/internal/per_context/primordials.js @@ -225,6 +225,8 @@ function copyPrototype(src, dest, prefix) { copyPrototype(original.prototype, primordials, `${name}Prototype`); }); +const { String } = primordials; + // Create copies of abstract intrinsic objects that are not directly exposed // on the global object. // Refs: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object @@ -248,20 +250,208 @@ function copyPrototype(src, dest, prefix) { const { ArrayPrototypeForEach, + Error, FinalizationRegistry, FunctionPrototypeCall, Map, + MathMax, + MathMin, + ObjectCreate, + ObjectDefineProperties, ObjectFreeze, ObjectSetPrototypeOf, Promise, PromisePrototypeThen, + RegExp, + RegExpPrototype, + RegExpPrototypeExec, + RegExpPrototypeGetUnicode, + RegExpPrototypeGetSticky, + RegExpPrototypeSymbolMatch, + RegExpPrototypeSymbolReplace, + RegExpPrototypeSymbolSearch, Set, + StringPrototypeReplace, + StringPrototypeReplaceAll, + StringPrototypeSlice, + StringPrototypeSplit, SymbolIterator, + SymbolMatch, + SymbolMatchAll, + SymbolReplace, + SymbolSearch, + SymbolSplit, WeakMap, WeakRef, WeakSet, } = primordials; +{ + const undefinedDesc = ObjectCreate(null); + undefinedDesc.value = undefined; + const safeTextTransformProperties = { + [SymbolMatch]: undefinedDesc, + [SymbolMatchAll]: undefinedDesc, + [SymbolReplace]: undefinedDesc, + [SymbolSearch]: undefinedDesc, + [SymbolSplit]: undefinedDesc, + exec: undefinedDesc, + }; + const defineRegExpGetter = (name) => { + safeTextTransformProperties[name] = + ReflectGetOwnPropertyDescriptor(RegExpPrototype, name); + }; + defineRegExpGetter('flags'); + defineRegExpGetter('global'); + defineRegExpGetter('ignoreCase'); + defineRegExpGetter('multiline'); + defineRegExpGetter('dotAll'); + defineRegExpGetter('unicode'); + defineRegExpGetter('sticky'); + ObjectFreeze(safeTextTransformProperties); + + const toSafeRegex = (stringOrRegex) => + ObjectDefineProperties(stringOrRegex, safeTextTransformProperties); + const toSafeString = (string) => toSafeRegex(new String(string)); + primordials.makeSafeStringObject = toSafeString; + + /** + * @param {string} thisString + * @param {string} needle + * @returns {string[]} + */ + primordials.SafeStringPrototypeMatch = (thisString, needle) => + RegExpPrototypeSymbolMatch(toSafeRegex(new RegExp(needle)), thisString); + + /** + * @param {string} thisStr + * @param {string} needle + * @returns {string[]} + */ + primordials.SafeStringPrototypeMatchAll = (thisStr, needle) => + RegExpPrototypeSymbolMatch(toSafeRegex(new RegExp(needle, 'g')), thisStr); + + /** + * @param {string} thisStr + * @param {string} searchVal + * @param {string | (substring: string, ...args: any[]) => string} replaceVal + * @returns {string} + */ + primordials.SafeStringPrototypeReplace = (thisStr, searchVal, replaceVal) => + StringPrototypeReplace(thisStr, toSafeString(searchVal), replaceVal); + + /** + * @param {string} thisStr + * @param {string} searchVal + * @param {string | (substring: string, ...args: any[]) => string} replaceVal + * @returns {string} + */ + primordials.SafeStringPrototypeReplaceAll = + (thisStr, searchVal, replaceVal) => + StringPrototypeReplaceAll(thisStr, toSafeString(searchVal), replaceVal); + + /** + * @param {string} thisString + * @param {string} needle + * @returns {number} + */ + primordials.SafeStringPrototypeSearch = (thisString, needle) => + RegExpPrototypeSymbolSearch(toSafeRegex(new RegExp(needle)), thisString); + + /** + * @param {string} thisString + * @param {string} splitter + * @param {number} [limit] + * @returns {string} + */ + primordials.SafeStringPrototypeSplit = (thisString, splitter, limit) => + StringPrototypeSplit(thisString, toSafeString(splitter), limit); + + /** + * @param {RegExp} thisRegex + * @param {string} string + * @returns {string[]} + */ + primordials.SafeRegExpPrototypeSymbolMatch = (thisRegex, string) => + RegExpPrototypeSymbolMatch(toSafeRegex(thisRegex), string); + + /** + * @param {RegExp} thisRegex + * @param {string} string + * @param {string | (substring: string, ...args: any[]) => string} replaceVal + * @returns {string} + */ + primordials.SafeRegExpPrototypeSymbolReplace = + (thisRegex, string, replaceVal) => + RegExpPrototypeSymbolReplace(toSafeRegex(thisRegex), string, replaceVal); + + /** + * @param {RegExp} thisRegex + * @param {string} string + * @returns {number} + */ + primordials.SafeRegExpPrototypeSymbolSearch = (thisRegex, string) => + RegExpPrototypeSymbolSearch(toSafeRegex(thisRegex), string); + + /** + * Re-implementation of `RegExp.prototype [ @@split ]` that do not rely on + * user-mutable methods. + * @ref https://tc39.es/ecma262/#sec-regexp.prototype-@@split + * @param {RegExp} splitter + * @param {string} string + * @param {number} [limit] + * @returns {string[]} + */ + primordials.SafeRegExpPrototypeSymbolSplit = (splitter, string, limit) => { + if (!RegExpPrototypeGetSticky(splitter)) + // eslint-disable-next-line no-restricted-syntax + throw new Error('You must use sticky flag (y).'); + if (RegExpPrototypeGetUnicode(splitter)) + // eslint-disable-next-line no-restricted-syntax + throw new Error('Unicode matching regex not supported'); + const S = string; + const A = []; + let lengthA = 0; + if (limit === 0) return A; + const size = S.length; + if (size === 0) { + const z = RegExpPrototypeExec(splitter, S); + if (z === null) A[0] = S; + return A; + } + + let p = 0; + let q = p; + while (q < size) { + splitter.lastIndex = q; + const z = RegExpPrototypeExec(splitter, S); + if (z === null) q++; + else { + const e = MathMin(splitter.lastIndex, size); + if (e === p) q++; + else { + const T = StringPrototypeSlice(S, p, q); + A[lengthA++] = T; + if (lengthA === limit) return A; + p = e; + const numberOfCaptures = MathMax(z.length - 1, 0); + for (let i = 1; i <= numberOfCaptures; i++) { + const nextCapture = z[i]; + A[lengthA++] = nextCapture; + if (lengthA === limit) return A; + } + q = p; + } + } + } + A[lengthA] = StringPrototypeSlice(S, p, size); + return A; + }; + + primordials.SafeRegExpPrototypeTest = (thisRegex, string) => + RegExpPrototypeExec(thisRegex, string) !== null; +} + // Because these functions are used by `makeSafe`, which is exposed // on the `primordials` object, it's important to use const references // to the primordials that they use: diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 6f8eb710a9255f..aa32544c5ebc2f 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -5,10 +5,14 @@ const { ArrayIsArray, ArrayPrototypeFilter, ArrayPrototypeForEach, + ArrayPrototypeIndexOf, + ArrayPrototypeJoin, + ArrayPrototypeMap, ArrayPrototypePop, ArrayPrototypePush, ArrayPrototypePushApply, ArrayPrototypeSort, + ArrayPrototypeSplice, ArrayPrototypeUnshift, BigIntPrototypeValueOf, BooleanPrototypeValueOf, @@ -47,24 +51,30 @@ const { ObjectSetPrototypeOf, ReflectOwnKeys, RegExp, - RegExpPrototypeTest, + RegExpPrototypeExec, RegExpPrototypeToString, SafeStringIterator, SafeMap, + SafeRegExpPrototypeSymbolSplit, + SafeRegExpPrototypeTest, SafeSet, + SafeRegExpPrototypeSymbolReplace, + SafeStringPrototypeReplaceAll, + SafeStringPrototypeSplit, SetPrototypeGetSize, SetPrototypeValues, String, StringPrototypeCharCodeAt, StringPrototypeCodePointAt, + StringPrototypeEndsWith, StringPrototypeIncludes, + StringPrototypeIndexOf, StringPrototypeNormalize, StringPrototypePadEnd, StringPrototypePadStart, StringPrototypeRepeat, - StringPrototypeReplace, StringPrototypeSlice, - StringPrototypeSplit, + StringPrototypeStartsWith, StringPrototypeToLowerCase, StringPrototypeTrim, StringPrototypeValueOf, @@ -147,7 +157,7 @@ let hexSlice; const builtInObjects = new SafeSet( ArrayPrototypeFilter( ObjectGetOwnPropertyNames(globalThis), - (e) => RegExpPrototypeTest(/^[A-Z][a-zA-Z0-9]+$/, e) + (e) => SafeRegExpPrototypeTest(/^[A-Z][a-zA-Z0-9]+$/, e) ) ); @@ -485,10 +495,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 && !SafeRegExpPrototypeTest(escapeTest, str)) return addQuotes(str, singleQuote); if (str.length > 100) { - str = StringPrototypeReplace(str, escapeReplace, escapeFn); + str = SafeRegExpPrototypeSymbolReplace(escapeReplace, str, escapeFn); return addQuotes(str, singleQuote); } @@ -1168,56 +1178,68 @@ function formatError(err, constructor, tag, ctx, keys) { // Do not "duplicate" error properties that are already included in the output // otherwise. if (!ctx.showHidden && keys.length !== 0) { - for (const name of ['name', 'message', 'stack']) { - const index = keys.indexOf(name); + ArrayPrototypeForEach(['name', 'message', 'stack'], (name) => { + const index = ArrayPrototypeIndexOf(keys, name); // Only hide the property in case it's part of the original stack - if (index !== -1 && stack.includes(err[name])) { - keys.splice(index, 1); + if (index !== -1 && StringPrototypeIncludes(stack, err[name])) { + ArrayPrototypeSplice(keys, index, 1); } - } + }); } // A stack trace may contain arbitrary data. Only manipulate the output // for "regular errors" (errors that "look normal") for now. if (constructor === null || - (name.endsWith('Error') && - stack.startsWith(name) && + (StringPrototypeEndsWith(name, 'Error') && + StringPrototypeStartsWith(stack, name) && (stack.length === len || stack[len] === ':' || stack[len] === '\n'))) { let fallback = 'Error'; if (constructor === null) { - const start = stack.match(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/) || - stack.match(/^([a-z_A-Z0-9-]*Error)$/); - fallback = (start && start[1]) || ''; - len = fallback.length; - fallback = fallback || 'Error'; + const start = + RegExpPrototypeExec(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/, + stack) || + RegExpPrototypeExec(/^([a-z_A-Z0-9-]*Error)$/, stack); + if (start === null) { + len = 0; + } else { + fallback = start[1]; + len = fallback.length; + } } - const prefix = getPrefix(constructor, tag, fallback).slice(0, -1); + const prefix = StringPrototypeSlice( + getPrefix(constructor, tag, fallback), 0, -1); if (name !== prefix) { - if (prefix.includes(name)) { + if (StringPrototypeIncludes(prefix, name)) { if (len === 0) { stack = `${prefix}: ${stack}`; } else { - stack = `${prefix}${stack.slice(len)}`; + stack = `${prefix}${StringPrototypeSlice(stack, len)}`; } } else { - stack = `${prefix} [${name}]${stack.slice(len)}`; + stack = `${prefix} [${name}]${StringPrototypeSlice(stack, len)}`; } } } // Ignore the error message if it's contained in the stack. - let pos = (err.message && stack.indexOf(err.message)) || -1; + let pos = (err.message && StringPrototypeIndexOf(stack, err.message)) || -1; if (pos !== -1) pos += err.message.length; // Wrap the error in brackets in case it has no stack trace. - const stackStart = stack.indexOf('\n at', pos); + const stackStart = StringPrototypeIndexOf(stack, '\n at', pos); if (stackStart === -1) { stack = `[${stack}]`; } else if (ctx.colors) { // Highlight userland code and node modules. - let newStack = stack.slice(0, stackStart); - const lines = stack.slice(stackStart + 1).split('\n'); - for (const line of lines) { - const core = line.match(coreModuleRegExp); + let newStack = StringPrototypeSlice(stack, 0, stackStart); + let previousIndex = stackStart + 1; + let index; + do { + index = StringPrototypeIndexOf(stack, '\n', previousIndex); + if (index === -1) index = undefined; + const line = StringPrototypeSlice(stack, previousIndex, index); + previousIndex = index + 1; + + const core = RegExpPrototypeExec(coreModuleRegExp, line); if (core !== null && NativeModule.exists(core[1])) { newStack += `\n${ctx.stylize(line, 'undefined')}`; } else { @@ -1225,21 +1247,21 @@ function formatError(err, constructor, tag, ctx, keys) { let nodeModule; newStack += '\n'; let pos = 0; - while (nodeModule = nodeModulesRegExp.exec(line)) { + while (nodeModule = RegExpPrototypeExec(nodeModulesRegExp, line)) { // '/node_modules/'.length === 14 - newStack += line.slice(pos, nodeModule.index + 14); + newStack += StringPrototypeSlice(line, pos, nodeModule.index + 14); newStack += ctx.stylize(nodeModule[1], 'module'); pos = nodeModule.index + nodeModule[0].length; } - newStack += pos === 0 ? line : line.slice(pos); + newStack += pos === 0 ? line : StringPrototypeSlice(line, pos); } - } + } while (index !== undefined); stack = newStack; } // The message and the stack have to be indented as well! if (ctx.indentationLvl !== 0) { - const indentation = ' '.repeat(ctx.indentationLvl); - stack = stack.replace(/\n/g, `\n${indentation}`); + const indentation = StringPrototypeRepeat(' ', ctx.indentationLvl); + stack = SafeStringPrototypeReplaceAll(stack, '\n', `\n${indentation}`); } return stack; } @@ -1390,10 +1412,13 @@ function formatPrimitive(fn, value, ctx) { // function. value.length > kMinLineLength && value.length > ctx.breakLength - ctx.indentationLvl - 4) { - return value - .split(/(?<=\n)/) - .map((line) => fn(strEscape(line), 'string')) - .join(` +\n${' '.repeat(ctx.indentationLvl + 2)}`) + trailer; + return ArrayPrototypeJoin( + ArrayPrototypeMap( + SafeRegExpPrototypeSymbolSplit(/(?<=\n)/y, value), + (line) => fn(strEscape(line), 'string') + ), + ` +\n${' '.repeat(ctx.indentationLvl + 2)}` + ) + trailer; } return fn(strEscape(value), 'string') + trailer; } @@ -1483,9 +1508,10 @@ function formatArrayBuffer(ctx, value) { } if (hexSlice === undefined) hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice); - let str = StringPrototypeTrim(StringPrototypeReplace( + let str = StringPrototypeTrim(SafeRegExpPrototypeSymbolReplace( + /(.{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' : ''}`; @@ -1716,18 +1742,19 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc, return str; } if (typeof key === 'symbol') { - const tmp = StringPrototypeReplace( + const tmp = SafeRegExpPrototypeSymbolReplace( + 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 = SafeRegExpPrototypeSymbolReplace(strEscapeSequencesReplacer, + key, escapeFn); name = `[${tmp}]`; - } else if (RegExpPrototypeTest(keyStrRegExp, key)) { + } else if (SafeRegExpPrototypeTest(keyStrRegExp, key)) { name = ctx.stylize(key, 'name'); } else { name = ctx.stylize(strEscape(key), 'string'); @@ -1852,7 +1879,7 @@ function hasBuiltInToString(value) { } const firstErrorLine = (error) => - StringPrototypeSplit(error.message, '\n', 1)[0]; + SafeStringPrototypeSplit(error.message, '\n', 1)[0]; let CIRCULAR_ERROR_MESSAGE; function tryStringify(arg) { try { diff --git a/test/parallel/test-primordials-textTransform.js b/test/parallel/test-primordials-textTransform.js new file mode 100644 index 00000000000000..9400fb4c58d6e5 --- /dev/null +++ b/test/parallel/test-primordials-textTransform.js @@ -0,0 +1,95 @@ +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const { + SafeRegExpPrototypeSymbolMatch, + SafeRegExpPrototypeSymbolReplace, + SafeRegExpPrototypeSymbolSearch, + SafeRegExpPrototypeTest, + SafeRegExpPrototypeSymbolSplit, + SafeStringPrototypeMatch, + SafeStringPrototypeMatchAll, + SafeStringPrototypeSearch, + SafeStringPrototypeReplace, + SafeStringPrototypeReplaceAll, + SafeStringPrototypeSplit, +} = require('internal/test/binding').primordials; + +const defineRegExpGetter = (name) => + Reflect.defineProperty(RegExp.prototype, name, { + get: common.mustNotCall(`get RegExp.prototype.${name}`), + }); + +RegExp.prototype[Symbol.match] = common.mustNotCall( + 'RegExp.prototype[@@match]' +); +RegExp.prototype[Symbol.matchAll] = common.mustNotCall( + 'RegExp.prototype[@@matchAll]' +); +RegExp.prototype[Symbol.replace] = common.mustNotCall( + 'RegExp.prototype[@@replace]' +); +RegExp.prototype[Symbol.search] = common.mustNotCall( + 'RegExp.prototype[@@search]' +); +RegExp.prototype[Symbol.split] = common.mustNotCall( + 'RegExp.prototype[@@split]' +); +RegExp.prototype.exec = common.mustNotCall('RegExp.prototype.exec'); +[ + 'flags', + 'global', + 'ignoreCase', + 'multiline', + 'dotAll', + 'unicode', + 'sticky', +].forEach(defineRegExpGetter); + +String.prototype[Symbol.match] = common.mustNotCall( + 'String.prototype[@@match]' +); +String.prototype[Symbol.matchAll] = common.mustNotCall( + 'String.prototype[@@matchAll]' +); +String.prototype[Symbol.replace] = common.mustNotCall( + 'String.prototype[@@replace]' +); +String.prototype[Symbol.search] = common.mustNotCall( + 'String.prototype[@@search]' +); +String.prototype[Symbol.split] = common.mustNotCall( + 'String.prototype[@@split]' +); + +{ + const expected = ['o']; + expected.groups = undefined; + expected.input = 'foo'; + expected.index = 1; + assert.deepStrictEqual(SafeStringPrototypeMatch('foo', 'o'), expected); +} + +assert.deepStrictEqual(SafeStringPrototypeMatchAll('foo', 'o'), + ['o', 'o']); + +assert.strictEqual(SafeStringPrototypeReplace('foo', 'o', 'a'), 'fao'); +assert.strictEqual(SafeStringPrototypeReplaceAll('foo', 'o', 'a'), 'faa'); +assert.strictEqual(SafeStringPrototypeSearch('foo', 'o'), 1); +assert.deepStrictEqual(SafeStringPrototypeSplit('foo', 'o'), ['f', '', '']); + +{ + const expected = ['o']; + expected.groups = undefined; + expected.input = 'foo'; + expected.index = 1; + assert.deepStrictEqual(SafeRegExpPrototypeSymbolMatch(/o/, 'foo'), expected); +} + +assert.strictEqual(SafeRegExpPrototypeSymbolReplace(/o/, 'foo', 'a'), 'fao'); +assert.strictEqual(SafeRegExpPrototypeSymbolSearch(/o/, 'foo'), 1); +assert.deepStrictEqual(SafeRegExpPrototypeSymbolSplit(/o/y, 'foo'), ['f', '', '']); +assert.strictEqual(SafeRegExpPrototypeTest(/o/, 'foo'), true);