Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix RegExp methods in old browsers #453

Merged
merged 9 commits into from
Nov 17, 2018
1 change: 1 addition & 0 deletions packages/core-js-builder/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ module.exports = {
'es.string.sub',
'es.string.sup',
'es.regexp.constructor',
'es.regexp.exec',
'es.regexp.flags',
'es.regexp.to-string',
'es.parse-int',
Expand Down
1 change: 1 addition & 0 deletions packages/core-js/es/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ require('../modules/es.string.strike');
require('../modules/es.string.sub');
require('../modules/es.string.sup');
require('../modules/es.regexp.constructor');
require('../modules/es.regexp.exec');
require('../modules/es.regexp.flags');
require('../modules/es.regexp.to-string');
require('../modules/es.parse-int');
Expand Down
1 change: 1 addition & 0 deletions packages/core-js/es/regexp/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require('../../modules/es.regexp.constructor');
require('../../modules/es.regexp.to-string');
require('../../modules/es.regexp.exec');
require('../../modules/es.regexp.flags');
require('../../modules/es.string.match');
require('../../modules/es.string.replace');
Expand Down
1 change: 1 addition & 0 deletions packages/core-js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ require('./modules/es.string.strike');
require('./modules/es.string.sub');
require('./modules/es.string.sup');
require('./modules/es.regexp.constructor');
require('./modules/es.regexp.exec');
require('./modules/es.regexp.flags');
require('./modules/es.regexp.to-string');
require('./modules/es.parse-int');
Expand Down
47 changes: 39 additions & 8 deletions packages/core-js/internals/fix-regexp-well-known-symbol-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ var redefine = require('../internals/redefine');
var fails = require('../internals/fails');
var requireObjectCoercible = require('../internals/require-object-coercible');
var wellKnownSymbol = require('../internals/well-known-symbol');
var regexpExec = require('../internals/regexp-exec');

var SPECIES = wellKnownSymbol('species');

module.exports = function (KEY, length, exec, sham) {
var SYMBOL = wellKnownSymbol(KEY);

var delegates = !fails(function () {
var delegatesToSymbol = !fails(function () {
// String methods call symbol-named RegEp methods
var O = {};
O[SYMBOL] = function () { return 7; };
return ''[KEY](O) != 7;
}) && !fails(function () {
});

var delegatesToExec = delegatesToSymbol ? !fails(function () {
// Symbol-named RegExp methods call .exec
var execCalled = false;
var re = /a/;
Expand All @@ -30,7 +33,7 @@ module.exports = function (KEY, length, exec, sham) {

re[SYMBOL]('');
return !execCalled;
});
}) : undefined;

var replaceSupportsNamedGroups = KEY === 'replace' && !fails(function () {
// #replace needs built-in support for named groups.
Expand All @@ -45,11 +48,39 @@ module.exports = function (KEY, length, exec, sham) {
return ''.replace(re, '$<a>') !== '7';
});

if (!delegates || (KEY === 'replace' && !replaceSupportsNamedGroups)) {
var methods = exec(requireObjectCoercible, SYMBOL, ''[KEY], /./[SYMBOL], {
delegates: delegates,
replaceSupportsNamedGroups: replaceSupportsNamedGroups
});
var splitWorksWithOverwrittenExec = KEY === 'split' && (function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the fix for Chrome 51.

// Chrome 51 has a buggy "split" implementation when RegExp#exec !== nativeExec
var re = /(?:)/;
var originalExec = re.exec;
re.exec = function () { return originalExec.apply(this, arguments); };
var result = 'ab'.split(re);
return result.length === 2 && result[0] === 'a' && result[1] === 'b';
})();

if (
!delegatesToSymbol ||
!delegatesToExec ||
(KEY === 'replace' && !replaceSupportsNamedGroups) ||
(KEY === 'split' && !splitWorksWithOverwrittenExec)
) {
var nativeRegExpMethod = /./[SYMBOL];
var methods = exec(
requireObjectCoercible,
SYMBOL,
''[KEY],
function maybeCallNative(nativeMethod, regexp, str, arg2, forceStringMethod) {
if (regexp.exec === regexpExec.impl) {
if (delegatesToSymbol && !forceStringMethod) {
// The native String method already delegates to @@method (this
// polyfilled function), leasing to infinite recursion.
// We avoid it by directly calling the native @@method method.
return { done: true, value: nativeRegExpMethod.call(regexp, str, arg2) };
}
return { done: true, value: nativeMethod.call(str, regexp, arg2) };
}
return { done: false };
}
);
var stringMethod = methods[0];
var regexMethod = methods[1];

Expand Down
22 changes: 22 additions & 0 deletions packages/core-js/internals/regexp-exec-abstract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
var classof = require('./classof-raw');
var regexpExec = require('./regexp-exec');

// `RegExpExec` abstract operation
// https://tc39.github.io/ecma262/#sec-regexpexec
module.exports = function (R, S) {
var exec = R.exec;
if (typeof exec === 'function') {
var result = exec.call(R, S);
if (typeof result !== 'object') {
throw new TypeError('RegExp exec method returned something other than an Object or null');
}
return result;
}

if (classof(R) !== 'RegExp') {
throw new TypeError('RegExp#exec called on incompatible receiver');
}

return regexpExec.impl.call(R, S);
};

77 changes: 59 additions & 18 deletions packages/core-js/internals/regexp-exec.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,63 @@
var classof = require('../internals/classof-raw');
var builtinExec = RegExp.prototype.exec;

// `RegExpExec` abstract operation
// https://tc39.github.io/ecma262/#sec-regexpexec
module.exports = function (R, S) {
var exec = R.exec;
if (typeof exec === 'function') {
var result = exec.call(R, S);
if (typeof result !== 'object') {
throw new TypeError('RegExp exec method returned something other than an Object or null');
'use strict';

var regexpFlags = require('./regexp-flags');

var nativeExec = RegExp.prototype.exec;
// This always refers to the native implementation, because the
// String#replace polyfill uses ./fix-regexp-well-known-symbol-logic.js,
// which loads this file before patching the method.
var nativeReplace = String.prototype.replace;

var patchedExec = nativeExec;

var LAST_INDEX = 'lastIndex';
var LENGTH = 'length';

var UPDATES_LAST_INDEX_WRONG = (function () {
var re1 = /a/,
re2 = /b*/g;
nativeExec.call(re1, 'a');
nativeExec.call(re2, 'a');
return re1[LAST_INDEX] !== 0 || re2[LAST_INDEX] !== 0;
})();

// nonparticipating capturing group, copied from es5-shim's String#split patch.
var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;

var patch = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED;

if (patch) {
patchedExec = function exec(str) {
var re = this;
var lastIndex, reCopy, match, i;

if (NPCG_INCLUDED) {
reCopy = new RegExp('^' + re.source + '$(?!\\s)', regexpFlags.call(re));
}
return result;
}
if (UPDATES_LAST_INDEX_WRONG) lastIndex = re[LAST_INDEX];

if (classof(R) !== 'RegExp') {
throw new TypeError('RegExp#exec called on incompatible receiver');
}
match = nativeExec.call(re, str);

return builtinExec.call(R, S);
};
if (UPDATES_LAST_INDEX_WRONG && match) {
re[LAST_INDEX] = re.global ? match.index + match[0][LENGTH] : lastIndex;
}
if (NPCG_INCLUDED && match && match[LENGTH] > 1) {
// Fix browsers whose `exec` methods don't consistently return `undefined`
// for NPCG, like IE8. NOTE: This doesn' work for /(.?)?/
// eslint-disable-next-line no-loop-func
nativeReplace.call(match[0], reCopy, function () {
for (i = 1; i < arguments[LENGTH] - 2; i++) {
if (arguments[i] === undefined) match[i] = undefined;
}
});
}

return match;
};
}

module.exports = {
orig: nativeExec,
impl: patchedExec,
patched: patch
};
11 changes: 11 additions & 0 deletions packages/core-js/modules/es.regexp.exec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

var regexpExec = require('../internals/regexp-exec');

require('../internals/export')({
target: 'RegExp',
proto: true,
forced: regexpExec.patched
}, {
exec: regexpExec.impl
});
68 changes: 36 additions & 32 deletions packages/core-js/modules/es.string.match.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,45 @@
var anObject = require('../internals/an-object');
var toLength = require('../internals/to-length');
var advanceStringIndex = require('../internals/advance-string-index');
var regExpExec = require('../internals/regexp-exec');
var nativeExec = RegExp.prototype.exec;
var regExpExec = require('../internals/regexp-exec-abstract');

// @@match logic
require('../internals/fix-regexp-well-known-symbol-logic')('match', 1, function (defined, MATCH, nativeMatch) {
return [
// `String.prototype.match` method
// https://tc39.github.io/ecma262/#sec-string.prototype.match
function match(regexp) {
var O = defined(this);
var matcher = regexp == undefined ? undefined : regexp[MATCH];
return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
},
// `RegExp.prototype[@@match]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@match
function (regexp) {
if (regexp.exec === nativeExec) return nativeMatch.call(this, regexp);
require('../internals/fix-regexp-well-known-symbol-logic')(
'match',
1,
function (defined, MATCH, nativeMatch, maybeCallNative) {
return [
// `String.prototype.match` method
// https://tc39.github.io/ecma262/#sec-string.prototype.match
function match(regexp) {
var O = defined(this);
var matcher = regexp == undefined ? undefined : regexp[MATCH];
return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
},
// `RegExp.prototype[@@match]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@match
function (regexp) {
var res = maybeCallNative(nativeMatch, regexp, this);
Copy link
Contributor Author

@nicolo-ribaudo nicolo-ribaudo Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All those "maybeCallNative" calls are to fix the infinite recursion in Chrome 50

if (res.done) return res.value;

var rx = anObject(regexp);
var S = String(this);
var rx = anObject(regexp);
var S = String(this);

if (!rx.global) return regExpExec(rx, S);
if (!rx.global) return regExpExec(rx, S);

var fullUnicode = rx.unicode;
rx.lastIndex = 0;
var A = [];
var n = 0;
var result;
while ((result = regExpExec(rx, S)) !== null) {
var matchStr = String(result[0]);
A[n] = matchStr;
if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
n++;
var fullUnicode = rx.unicode;
rx.lastIndex = 0;
var A = [];
var n = 0;
var result;
while ((result = regExpExec(rx, S)) !== null) {
var matchStr = String(result[0]);
A[n] = matchStr;
if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
n++;
}
return n === 0 ? null : A;
}
return n === 0 ? null : A;
}
];
});
];
}
);
28 changes: 13 additions & 15 deletions packages/core-js/modules/es.string.replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ var toObject = require('../internals/to-object');
var toLength = require('../internals/to-length');
var toInteger = require('../internals/to-integer');
var advanceStringIndex = require('../internals/advance-string-index');
var regExpExec = require('../internals/regexp-exec');
var nativeExec = RegExp.prototype.exec;
var regExpExec = require('../internals/regexp-exec-abstract');
var max = Math.max;
var min = Math.min;
var floor = Math.floor;
Expand All @@ -21,7 +20,7 @@ var maybeToString = function (it) {
require('../internals/fix-regexp-well-known-symbol-logic')(
'replace',
2,
function (defined, REPLACE, nativeReplace, nativeRegExpReplace, reason) {
function (defined, REPLACE, nativeReplace, maybeCallNative) {
return [
// `String.prototype.replace` method
// https://tc39.github.io/ecma262/#sec-string.prototype.replace
Expand All @@ -35,15 +34,8 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
// `RegExp.prototype[@@replace]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace
function (regexp, replaceValue) {
if (regexp.exec === nativeExec) {
if (reason.delegates) {
// The native #replaceMethod already delegates to @@replace (this
// polyfilled function, leasing to infinite recursion).
// We avoid it by directly calling the native @@replace method.
return nativeRegExpReplace.call(regexp, this, replaceValue);
}
return nativeReplace.call(this, regexp, replaceValue);
}
var res = maybeCallNative(nativeReplace, regexp, this, replaceValue);
if (res.done) return res.value;

var rx = anObject(regexp);
var S = String(this);
Expand Down Expand Up @@ -75,7 +67,13 @@ require('../internals/fix-regexp-well-known-symbol-logic')(

var matched = String(result[0]);
var position = max(min(toInteger(result.index), S.length), 0);
var captures = result.slice(1).map(maybeToString);
var captures = [];
// NOTE: This is equivalent to
// captures = result.slice(1).map(maybeToString)
// but for some reason `nativeSlice.call(result, 1, result.length)` (called in
// the slice polyfill when slicing native arrays) "doesn't work" in safari 9 and
// causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.
for (var j = 1; j < result.length; j++) captures.push(maybeToString(result[j]));
var namedCaptures = result.groups;
if (functionalReplace) {
var replacerArgs = [matched].concat(captures, position, S);
Expand Down Expand Up @@ -104,7 +102,7 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
}
return nativeReplace.call(replacement, symbols, function (match, ch) {
var capture;
switch (ch[0]) {
switch (ch.charAt(0)) {
case '$': return '$';
case '&': return matched;
case '`': return str.slice(0, position);
Expand All @@ -118,7 +116,7 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
if (n > m) {
var f = floor(n / 10);
if (f === 0) return ch;
if (f <= m) return captures[f - 1] === undefined ? ch[1] : captures[f - 1] + ch[1];
if (f <= m) return captures[f - 1] === undefined ? ch.charAt(1) : captures[f - 1] + ch.charAt(1);
return ch;
}
capture = captures[n - 1];
Expand Down
Loading