Ignore:
Timestamp:
Nov 23, 2019, 3:23:31 PM (5 years ago)
Author:
Ross Kirsling
Message:

[JSC] GetSubstitution is performed incorrectly via RegExp.prototype[@@replace]
https://bugs.webkit.org/show_bug.cgi?id=204490

Reviewed by Mark Lam.

JSTests:

  • stress/replace-indexed-backreferences.js: Added.
  • test262/expectations.yaml: Mark four test cases as passing.

Source/JavaScriptCore:

String.prototype.replace and RegExp.prototype[Symbol.replace] are meant to perform the same substitution
of $-backreferences (called GetSubstitution in the spec: https://tc39.es/ecma262/#sec-getsubstitution).

The implementation of this in StringPrototype.cpp is correct but the one in RegExpPrototype.js is not.
In particular, the latter *removes* backreferences with out-of-range indices, instead of leaving them as-is.

One thing that is *not* broken in either implementation and thus maintained here is the fact $10 is interpreted
as $1 followed by a 0 when we have 1 <= n < 10 captures (and analogously for other invalid $nn backreferences).
This behavior is consistent across all engines but currently described incorrectly in the spec; this patch thus
aligns with the spec PR currently open to correct this (https://github.com/tc39/ecma262/pull/1732).

  • builtins/RegExpPrototype.js:

(getSubstitution): Ensure that invalid backreferences remain untouched in the output string.
(replace): Fix off-by-one error when populating captures list. We shouldn't be reserving a slot for the full match.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/builtins/RegExpPrototype.js

    r246692 r252836  
    223223                    let chCode = ch.charCodeAt(0);
    224224                    if (chCode >= 0x30 && chCode <= 0x39) {
     225                        let originalStart = start - 1;
    225226                        start++;
     227
    226228                        let n = chCode - 0x30;
    227                         if (n > m)
     229                        if (n > m) {
     230                            result = result + replacement.substring(originalStart, start);
    228231                            break;
     232                        }
     233
    229234                        if (start < replacementLength) {
    230235                            let nextChCode = replacement.charCodeAt(start);
     
    238243                        }
    239244
    240                         if (n == 0)
     245                        if (n == 0) {
     246                            result = result + replacement.substring(originalStart, start);
    241247                            break;
    242 
    243                         if (captures[n] != @undefined)
    244                             result = result + captures[n];
     248                        }
     249
     250                        let capture = captures[n - 1];
     251                        if (capture !== @undefined)
     252                            result = result + capture;
    245253                    } else
    246254                        result = result + "$";
     
    314322            if (capN !== @undefined)
    315323                capN = @toString(capN);
    316             captures[n] = capN;
     324            captures.@push(capN);
    317325        }
    318326
     
    320328
    321329        if (functionalReplace) {
    322             let replacerArgs = [ matched ].concat(captures.slice(1));
     330            let replacerArgs = [ matched ].concat(captures);
    323331            replacerArgs.@push(position);
    324332            replacerArgs.@push(str);
Note: See TracChangeset for help on using the changeset viewer.