All prototypes should call didBecomePrototype()
[WebKit-https.git] / Source / JavaScriptCore / runtime / RegExpPrototype.cpp
1 /*
2  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2003-2017 Apple Inc. All Rights Reserved.
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20
21 #include "config.h"
22 #include "RegExpPrototype.h"
23
24 #include "ArrayPrototype.h"
25 #include "BuiltinNames.h"
26 #include "Error.h"
27 #include "JSArray.h"
28 #include "JSCBuiltins.h"
29 #include "JSCInlines.h"
30 #include "JSCJSValue.h"
31 #include "JSFunction.h"
32 #include "JSStringInlines.h"
33 #include "Lexer.h"
34 #include "ObjectPrototype.h"
35 #include "RegExpCache.h"
36 #include "RegExpObject.h"
37 #include "RegExpObjectInlines.h"
38 #include "StringObject.h"
39 #include "StringRecursionChecker.h"
40 #include "YarrFlags.h"
41 #include <wtf/text/StringBuilder.h>
42
43 namespace JSC {
44
45 static EncodedJSValue JSC_HOST_CALL regExpProtoFuncExec(ExecState*);
46 static EncodedJSValue JSC_HOST_CALL regExpProtoFuncCompile(ExecState*);
47 static EncodedJSValue JSC_HOST_CALL regExpProtoFuncToString(ExecState*);
48 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterGlobal(ExecState*);
49 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterIgnoreCase(ExecState*);
50 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterMultiline(ExecState*);
51 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterDotAll(ExecState*);
52 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterSticky(ExecState*);
53 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterUnicode(ExecState*);
54 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterSource(ExecState*);
55 static EncodedJSValue JSC_HOST_CALL regExpProtoGetterFlags(ExecState*);
56
57 const ClassInfo RegExpPrototype::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(RegExpPrototype) };
58
59 RegExpPrototype::RegExpPrototype(VM& vm, Structure* structure)
60     : JSNonFinalObject(vm, structure)
61 {
62 }
63
64 void RegExpPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
65 {
66     Base::finishCreation(vm);
67     ASSERT(inherits(vm, info()));
68     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->compile, regExpProtoFuncCompile, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
69     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->exec, regExpProtoFuncExec, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, RegExpExecIntrinsic);
70     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, regExpProtoFuncToString, static_cast<unsigned>(PropertyAttribute::DontEnum), 0);
71     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->global, regExpProtoGetterGlobal, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
72     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->dotAll, regExpProtoGetterDotAll, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
73     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->ignoreCase, regExpProtoGetterIgnoreCase, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
74     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->multiline, regExpProtoGetterMultiline, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
75     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->sticky, regExpProtoGetterSticky, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
76     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->unicode, regExpProtoGetterUnicode, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
77     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->source, regExpProtoGetterSource, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
78     JSC_NATIVE_GETTER_WITHOUT_TRANSITION(vm.propertyNames->flags, regExpProtoGetterFlags, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
79     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->matchSymbol, regExpPrototypeMatchCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
80     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->matchAllSymbol, regExpPrototypeMatchAllCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
81     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->replaceSymbol, regExpPrototypeReplaceCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
82     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->searchSymbol, regExpPrototypeSearchCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
83     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->splitSymbol, regExpPrototypeSplitCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
84     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->test, regExpPrototypeTestCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
85
86     didBecomePrototype();
87 }
88
89 // ------------------------------ Functions ---------------------------
90
91 EncodedJSValue JSC_HOST_CALL regExpProtoFuncTestFast(ExecState* exec)
92 {
93     VM& vm = exec->vm();
94     auto scope = DECLARE_THROW_SCOPE(vm);
95
96     JSValue thisValue = exec->thisValue();
97     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
98     if (UNLIKELY(!regexp))
99         return throwVMTypeError(exec, scope);
100     JSString* string = exec->argument(0).toStringOrNull(exec);
101     EXCEPTION_ASSERT(!!scope.exception() == !string);
102     if (!string)
103         return JSValue::encode(jsUndefined());
104     RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(regexp->test(exec, exec->lexicalGlobalObject(), string))));
105 }
106
107 EncodedJSValue JSC_HOST_CALL regExpProtoFuncExec(ExecState* exec)
108 {
109     VM& vm = exec->vm();
110     auto scope = DECLARE_THROW_SCOPE(vm);
111
112     JSValue thisValue = exec->thisValue();
113     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
114     if (UNLIKELY(!regexp))
115         return throwVMTypeError(exec, scope, "Builtin RegExp exec can only be called on a RegExp object");
116     JSString* string = exec->argument(0).toStringOrNull(exec);
117     EXCEPTION_ASSERT(!!scope.exception() == !string);
118     if (!string)
119         return JSValue::encode(jsUndefined());
120     RELEASE_AND_RETURN(scope, JSValue::encode(regexp->exec(exec, exec->lexicalGlobalObject(), string)));
121 }
122
123 EncodedJSValue JSC_HOST_CALL regExpProtoFuncMatchFast(ExecState* exec)
124 {
125     RegExpObject* thisObject = jsCast<RegExpObject*>(exec->thisValue());
126     JSString* string = jsCast<JSString*>(exec->uncheckedArgument(0));
127     if (!thisObject->regExp()->global())
128         return JSValue::encode(thisObject->exec(exec, exec->lexicalGlobalObject(), string));
129     return JSValue::encode(thisObject->matchGlobal(exec, exec->lexicalGlobalObject(), string));
130 }
131
132 EncodedJSValue JSC_HOST_CALL regExpProtoFuncCompile(ExecState* exec)
133 {
134     VM& vm = exec->vm();
135     auto scope = DECLARE_THROW_SCOPE(vm);
136
137     JSValue thisValue = exec->thisValue();
138     auto* thisRegExp = jsDynamicCast<RegExpObject*>(vm, thisValue);
139     if (UNLIKELY(!thisRegExp))
140         return throwVMTypeError(exec, scope);
141
142     RegExp* regExp;
143     JSValue arg0 = exec->argument(0);
144     JSValue arg1 = exec->argument(1);
145     
146     if (auto* regExpObject = jsDynamicCast<RegExpObject*>(vm, arg0)) {
147         if (!arg1.isUndefined())
148             return throwVMTypeError(exec, scope, "Cannot supply flags when constructing one RegExp from another."_s);
149         regExp = regExpObject->regExp();
150     } else {
151         String pattern = arg0.isUndefined() ? emptyString() : arg0.toWTFString(exec);
152         RETURN_IF_EXCEPTION(scope, encodedJSValue());
153
154         auto flags = arg1.isUndefined() ? makeOptional(OptionSet<Yarr::Flags> { }) : Yarr::parseFlags(arg1.toWTFString(exec));
155         RETURN_IF_EXCEPTION(scope, encodedJSValue());
156         if (!flags)
157             return throwVMError(exec, scope, createSyntaxError(exec, "Invalid flags supplied to RegExp constructor."_s));
158
159         regExp = RegExp::create(vm, pattern, flags.value());
160     }
161
162     if (!regExp->isValid())
163         return throwVMError(exec, scope, regExp->errorToThrow(exec));
164
165     thisRegExp->setRegExp(vm, regExp);
166     scope.release();
167     thisRegExp->setLastIndex(exec, 0);
168     return JSValue::encode(thisRegExp);
169 }
170
171 typedef std::array<char, 6 + 1> FlagsString; // 6 different flags and a null character terminator.
172
173 static inline FlagsString flagsString(ExecState* exec, JSObject* regexp)
174 {
175     FlagsString string;
176     string[0] = 0;
177
178     VM& vm = exec->vm();
179     auto scope = DECLARE_THROW_SCOPE(vm);
180
181     JSValue globalValue = regexp->get(exec, vm.propertyNames->global);
182     RETURN_IF_EXCEPTION(scope, string);
183     JSValue ignoreCaseValue = regexp->get(exec, vm.propertyNames->ignoreCase);
184     RETURN_IF_EXCEPTION(scope, string);
185     JSValue multilineValue = regexp->get(exec, vm.propertyNames->multiline);
186     RETURN_IF_EXCEPTION(scope, string);
187     JSValue dotAllValue = regexp->get(exec, vm.propertyNames->dotAll);
188     RETURN_IF_EXCEPTION(scope, string);
189     JSValue unicodeValue = regexp->get(exec, vm.propertyNames->unicode);
190     RETURN_IF_EXCEPTION(scope, string);
191     JSValue stickyValue = regexp->get(exec, vm.propertyNames->sticky);
192     RETURN_IF_EXCEPTION(scope, string);
193
194     unsigned index = 0;
195     if (globalValue.toBoolean(exec))
196         string[index++] = 'g';
197     if (ignoreCaseValue.toBoolean(exec))
198         string[index++] = 'i';
199     if (multilineValue.toBoolean(exec))
200         string[index++] = 'm';
201     if (dotAllValue.toBoolean(exec))
202         string[index++] = 's';
203     if (unicodeValue.toBoolean(exec))
204         string[index++] = 'u';
205     if (stickyValue.toBoolean(exec))
206         string[index++] = 'y';
207     ASSERT(index < string.size());
208     string[index] = 0;
209     return string;
210 }
211
212 EncodedJSValue JSC_HOST_CALL regExpProtoFuncToString(ExecState* exec)
213 {
214     VM& vm = exec->vm();
215     auto scope = DECLARE_THROW_SCOPE(vm);
216
217     JSValue thisValue = exec->thisValue();
218     if (!thisValue.isObject())
219         return throwVMTypeError(exec, scope);
220
221     JSObject* thisObject = asObject(thisValue);
222
223     StringRecursionChecker checker(exec, thisObject);
224     EXCEPTION_ASSERT(!scope.exception() || checker.earlyReturnValue());
225     if (JSValue earlyReturnValue = checker.earlyReturnValue())
226         return JSValue::encode(earlyReturnValue);
227
228     JSValue sourceValue = thisObject->get(exec, vm.propertyNames->source);
229     RETURN_IF_EXCEPTION(scope, encodedJSValue());
230     String source = sourceValue.toWTFString(exec);
231     RETURN_IF_EXCEPTION(scope, encodedJSValue());
232
233     JSValue flagsValue = thisObject->get(exec, vm.propertyNames->flags);
234     RETURN_IF_EXCEPTION(scope, encodedJSValue());
235     String flags = flagsValue.toWTFString(exec);
236     RETURN_IF_EXCEPTION(scope, encodedJSValue());
237
238     RELEASE_AND_RETURN(scope, JSValue::encode(jsMakeNontrivialString(exec, '/', source, '/', flags)));
239 }
240
241 EncodedJSValue JSC_HOST_CALL regExpProtoGetterGlobal(ExecState* exec)
242 {
243     VM& vm = exec->vm();
244     auto scope = DECLARE_THROW_SCOPE(vm);
245
246     JSValue thisValue = exec->thisValue();
247     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
248     if (UNLIKELY(!regexp)) {
249         if (thisValue.inherits<RegExpPrototype>(vm))
250             return JSValue::encode(jsUndefined());
251         return throwVMTypeError(exec, scope, "The RegExp.prototype.global getter can only be called on a RegExp object"_s);
252     }
253
254     return JSValue::encode(jsBoolean(regexp->regExp()->global()));
255 }
256
257 EncodedJSValue JSC_HOST_CALL regExpProtoGetterIgnoreCase(ExecState* exec)
258 {
259     VM& vm = exec->vm();
260     auto scope = DECLARE_THROW_SCOPE(vm);
261
262     JSValue thisValue = exec->thisValue();
263     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
264     if (UNLIKELY(!regexp)) {
265         if (thisValue.inherits<RegExpPrototype>(vm))
266             return JSValue::encode(jsUndefined());
267         return throwVMTypeError(exec, scope, "The RegExp.prototype.ignoreCase getter can only be called on a RegExp object"_s);
268     }
269
270     return JSValue::encode(jsBoolean(regexp->regExp()->ignoreCase()));
271 }
272
273 EncodedJSValue JSC_HOST_CALL regExpProtoGetterMultiline(ExecState* exec)
274 {
275     VM& vm = exec->vm();
276     auto scope = DECLARE_THROW_SCOPE(vm);
277
278     JSValue thisValue = exec->thisValue();
279     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
280     if (UNLIKELY(!regexp)) {
281         if (thisValue.inherits<RegExpPrototype>(vm))
282             return JSValue::encode(jsUndefined());
283         return throwVMTypeError(exec, scope, "The RegExp.prototype.multiline getter can only be called on a RegExp object"_s);
284     }
285
286     return JSValue::encode(jsBoolean(regexp->regExp()->multiline()));
287 }
288
289 EncodedJSValue JSC_HOST_CALL regExpProtoGetterDotAll(ExecState* exec)
290 {
291     VM& vm = exec->vm();
292     auto scope = DECLARE_THROW_SCOPE(vm);
293     
294     JSValue thisValue = exec->thisValue();
295     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
296     if (UNLIKELY(!regexp)) {
297         if (thisValue.inherits<RegExpPrototype>(vm))
298             return JSValue::encode(jsUndefined());
299         return throwVMTypeError(exec, scope, "The RegExp.prototype.dotAll getter can only be called on a RegExp object"_s);
300     }
301     
302     return JSValue::encode(jsBoolean(regexp->regExp()->dotAll()));
303 }
304     
305 EncodedJSValue JSC_HOST_CALL regExpProtoGetterSticky(ExecState* exec)
306 {
307     VM& vm = exec->vm();
308     auto scope = DECLARE_THROW_SCOPE(vm);
309
310     JSValue thisValue = exec->thisValue();
311     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
312     if (UNLIKELY(!regexp)) {
313         if (thisValue.inherits<RegExpPrototype>(vm))
314             return JSValue::encode(jsUndefined());
315         return throwVMTypeError(exec, scope, "The RegExp.prototype.sticky getter can only be called on a RegExp object"_s);
316     }
317     
318     return JSValue::encode(jsBoolean(regexp->regExp()->sticky()));
319 }
320
321 EncodedJSValue JSC_HOST_CALL regExpProtoGetterUnicode(ExecState* exec)
322 {
323     VM& vm = exec->vm();
324     auto scope = DECLARE_THROW_SCOPE(vm);
325
326     JSValue thisValue = exec->thisValue();
327     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
328     if (UNLIKELY(!regexp)) {
329         if (thisValue.inherits<RegExpPrototype>(vm))
330             return JSValue::encode(jsUndefined());
331         return throwVMTypeError(exec, scope, "The RegExp.prototype.unicode getter can only be called on a RegExp object"_s);
332     }
333     
334     return JSValue::encode(jsBoolean(regexp->regExp()->unicode()));
335 }
336
337 EncodedJSValue JSC_HOST_CALL regExpProtoGetterFlags(ExecState* exec)
338 {
339     VM& vm = exec->vm();
340     auto scope = DECLARE_THROW_SCOPE(vm);
341
342     JSValue thisValue = exec->thisValue();
343     if (UNLIKELY(!thisValue.isObject()))
344         return throwVMTypeError(exec, scope, "The RegExp.prototype.flags getter can only be called on an object"_s);
345
346     auto flags = flagsString(exec, asObject(thisValue));
347     RETURN_IF_EXCEPTION(scope, encodedJSValue());
348
349     return JSValue::encode(jsString(exec, flags.data()));
350 }
351
352 template <typename CharacterType>
353 static inline void appendLineTerminatorEscape(StringBuilder&, CharacterType);
354
355 template <>
356 inline void appendLineTerminatorEscape<LChar>(StringBuilder& builder, LChar lineTerminator)
357 {
358     if (lineTerminator == '\n')
359         builder.append('n');
360     else
361         builder.append('r');
362 }
363
364 template <>
365 inline void appendLineTerminatorEscape<UChar>(StringBuilder& builder, UChar lineTerminator)
366 {
367     if (lineTerminator == '\n')
368         builder.append('n');
369     else if (lineTerminator == '\r')
370         builder.append('r');
371     else if (lineTerminator == 0x2028)
372         builder.appendLiteral("u2028");
373     else
374         builder.appendLiteral("u2029");
375 }
376
377 template <typename CharacterType>
378 static inline JSValue regExpProtoGetterSourceInternal(ExecState* exec, const String& pattern, const CharacterType* characters, unsigned length)
379 {
380     bool previousCharacterWasBackslash = false;
381     bool inBrackets = false;
382     bool shouldEscape = false;
383
384     // 15.10.6.4 specifies that RegExp.prototype.toString must return '/' + source + '/',
385     // and also states that the result must be a valid RegularExpressionLiteral. '//' is
386     // not a valid RegularExpressionLiteral (since it is a single line comment), and hence
387     // source cannot ever validly be "". If the source is empty, return a different Pattern
388     // that would match the same thing.
389     if (!length)
390         return jsNontrivialString(exec, "(?:)"_s);
391
392     // early return for strings that don't contain a forwards slash and LineTerminator
393     for (unsigned i = 0; i < length; ++i) {
394         CharacterType ch = characters[i];
395         if (!previousCharacterWasBackslash) {
396             if (inBrackets) {
397                 if (ch == ']')
398                     inBrackets = false;
399             } else {
400                 if (ch == '/') {
401                     shouldEscape = true;
402                     break;
403                 }
404                 if (ch == '[')
405                     inBrackets = true;
406             }
407         }
408
409         if (Lexer<CharacterType>::isLineTerminator(ch)) {
410             shouldEscape = true;
411             break;
412         }
413
414         if (previousCharacterWasBackslash)
415             previousCharacterWasBackslash = false;
416         else
417             previousCharacterWasBackslash = ch == '\\';
418     }
419
420     if (!shouldEscape)
421         return jsString(exec, pattern);
422
423     previousCharacterWasBackslash = false;
424     inBrackets = false;
425     StringBuilder result;
426     for (unsigned i = 0; i < length; ++i) {
427         CharacterType ch = characters[i];
428         if (!previousCharacterWasBackslash) {
429             if (inBrackets) {
430                 if (ch == ']')
431                     inBrackets = false;
432             } else {
433                 if (ch == '/')
434                     result.append('\\');
435                 else if (ch == '[')
436                     inBrackets = true;
437             }
438         }
439
440         // escape LineTerminator
441         if (Lexer<CharacterType>::isLineTerminator(ch)) {
442             if (!previousCharacterWasBackslash)
443                 result.append('\\');
444
445             appendLineTerminatorEscape<CharacterType>(result, ch);
446         } else
447             result.append(ch);
448
449         if (previousCharacterWasBackslash)
450             previousCharacterWasBackslash = false;
451         else
452             previousCharacterWasBackslash = ch == '\\';
453     }
454
455     return jsString(exec, result.toString());
456 }
457
458 EncodedJSValue JSC_HOST_CALL regExpProtoGetterSource(ExecState* exec)
459 {
460     VM& vm = exec->vm();
461     auto scope = DECLARE_THROW_SCOPE(vm);
462
463     JSValue thisValue = exec->thisValue();
464     auto* regexp = jsDynamicCast<RegExpObject*>(vm, thisValue);
465     if (UNLIKELY(!regexp)) {
466         if (thisValue.inherits<RegExpPrototype>(vm))
467             return JSValue::encode(jsString(exec, "(?:)"_s));
468         return throwVMTypeError(exec, scope, "The RegExp.prototype.source getter can only be called on a RegExp object"_s);
469     }
470
471     String pattern = regexp->regExp()->pattern();
472     if (pattern.is8Bit())
473         return JSValue::encode(regExpProtoGetterSourceInternal(exec, pattern, pattern.characters8(), pattern.length()));
474     return JSValue::encode(regExpProtoGetterSourceInternal(exec, pattern, pattern.characters16(), pattern.length()));
475 }
476
477 EncodedJSValue JSC_HOST_CALL regExpProtoFuncSearchFast(ExecState* exec)
478 {
479     VM& vm = exec->vm();
480     auto scope = DECLARE_THROW_SCOPE(vm);
481     JSValue thisValue = exec->thisValue();
482     RegExp* regExp = jsCast<RegExpObject*>(thisValue)->regExp();
483
484     JSString* string = exec->uncheckedArgument(0).toString(exec);
485     String s = string->value(exec);
486     RETURN_IF_EXCEPTION(scope, encodedJSValue());
487
488     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
489     MatchResult result = globalObject->regExpGlobalData().performMatch(vm, globalObject, regExp, string, s, 0);
490     RETURN_IF_EXCEPTION(scope, encodedJSValue());
491     return JSValue::encode(result ? jsNumber(result.start) : jsNumber(-1));
492 }
493
494 static inline unsigned advanceStringIndex(String str, unsigned strSize, unsigned index, bool isUnicode)
495 {
496     if (!isUnicode)
497         return ++index;
498     return advanceStringUnicode(str, strSize, index);
499 }
500
501 enum SplitControl {
502     ContinueSplit,
503     AbortSplit
504 };
505
506 template<typename ControlFunc, typename PushFunc>
507 void genericSplit(
508     VM& vm, RegExp* regexp, const String& input, unsigned inputSize, unsigned& position,
509     unsigned& matchPosition, bool regExpIsSticky, bool regExpIsUnicode,
510     const ControlFunc& control, const PushFunc& push)
511 {
512     Vector<int> ovector;
513         
514     while (matchPosition < inputSize) {
515         if (control() == AbortSplit)
516             return;
517         
518         ovector.shrink(0);
519         
520         // a. Perform ? Set(splitter, "lastIndex", q, true).
521         // b. Let z be ? RegExpExec(splitter, S).
522         int mpos = regexp->match(vm, input, matchPosition, ovector);
523
524         // c. If z is null, let q be AdvanceStringIndex(S, q, unicodeMatching).
525         if (mpos < 0) {
526             if (!regExpIsSticky)
527                 break;
528             matchPosition = advanceStringIndex(input, inputSize, matchPosition, regExpIsUnicode);
529             continue;
530         }
531         if (static_cast<unsigned>(mpos) >= inputSize) {
532             // The spec redoes the RegExpExec starting at the next character of the input.
533             // But in our case, mpos < 0 means that the native regexp already searched all permutations
534             // and know that we won't be able to find a match for the separator even if we redo the
535             // RegExpExec starting at the next character of the input. So, just bail.
536             break;
537         }
538
539         // d. Else, z is not null
540         //    i. Let e be ? ToLength(? Get(splitter, "lastIndex")).
541         //   ii. Let e be min(e, size).
542         matchPosition = mpos;
543         unsigned matchEnd = ovector[1];
544
545         //  iii. If e = p, let q be AdvanceStringIndex(S, q, unicodeMatching).
546         if (matchEnd == position) {
547             matchPosition = advanceStringIndex(input, inputSize, matchPosition, regExpIsUnicode);
548             continue;
549         }
550         // if matchEnd == 0 then position should also be zero and thus matchEnd should equal position.
551         ASSERT(matchEnd);
552
553         //   iv. Else e != p,
554         unsigned numberOfCaptures = regexp->numSubpatterns();
555         
556         // 1. Let T be a String value equal to the substring of S consisting of the elements at indices p (inclusive) through q (exclusive).
557         // 2. Perform ! CreateDataProperty(A, ! ToString(lengthA), T).
558         if (push(true, position, matchPosition - position) == AbortSplit)
559             return;
560         
561         // 5. Let p be e.
562         position = matchEnd;
563         
564         // 6. Let numberOfCaptures be ? ToLength(? Get(z, "length")).
565         // 7. Let numberOfCaptures be max(numberOfCaptures-1, 0).
566         // 8. Let i be 1.
567         // 9. Repeat, while i <= numberOfCaptures,
568         for (unsigned i = 1; i <= numberOfCaptures; ++i) {
569             // a. Let nextCapture be ? Get(z, ! ToString(i)).
570             // b. Perform ! CreateDataProperty(A, ! ToString(lengthA), nextCapture).
571             int sub = ovector[i * 2];
572             if (push(sub >= 0, sub, ovector[i * 2 + 1] - sub) == AbortSplit)
573                 return;
574         }
575         
576         // 10. Let q be p.
577         matchPosition = position;
578     }
579 }
580
581 // ES 21.2.5.11 RegExp.prototype[@@split](string, limit)
582 EncodedJSValue JSC_HOST_CALL regExpProtoFuncSplitFast(ExecState* exec)
583 {
584     VM& vm = exec->vm();
585     auto scope = DECLARE_THROW_SCOPE(vm);
586
587     // 1. [handled by JS builtin] Let rx be the this value.
588     // 2. [handled by JS builtin] If Type(rx) is not Object, throw a TypeError exception.
589     JSValue thisValue = exec->thisValue();
590     RegExp* regexp = jsCast<RegExpObject*>(thisValue)->regExp();
591
592     // 3. [handled by JS builtin] Let S be ? ToString(string).
593     JSString* inputString = exec->argument(0).toString(exec);
594     String input = inputString->value(exec);
595     RETURN_IF_EXCEPTION(scope, encodedJSValue());
596     ASSERT(!input.isNull());
597
598     // 4. [handled by JS builtin] Let C be ? SpeciesConstructor(rx, %RegExp%).
599     // 5. [handled by JS builtin] Let flags be ? ToString(? Get(rx, "flags")).
600     // 6. [handled by JS builtin] If flags contains "u", let unicodeMatching be true.
601     // 7. [handled by JS builtin] Else, let unicodeMatching be false.
602     // 8. [handled by JS builtin] If flags contains "y", let newFlags be flags.
603     // 9. [handled by JS builtin] Else, let newFlags be the string that is the concatenation of flags and "y".
604     // 10. [handled by JS builtin] Let splitter be ? Construct(C, « rx, newFlags »).
605
606     // 11. Let A be ArrayCreate(0).
607     // 12. Let lengthA be 0.
608     JSArray* result = constructEmptyArray(exec, 0);
609     RETURN_IF_EXCEPTION(scope, encodedJSValue());
610     unsigned resultLength = 0;
611
612     // 13. If limit is undefined, let lim be 2^32-1; else let lim be ? ToUint32(limit).
613     JSValue limitValue = exec->argument(1);
614     unsigned limit = limitValue.isUndefined() ? 0xFFFFFFFFu : limitValue.toUInt32(exec);
615     RETURN_IF_EXCEPTION(scope, encodedJSValue());
616
617     // 14. Let size be the number of elements in S.
618     unsigned inputSize = input.length();
619
620     // 15. Let p = 0.
621     unsigned position = 0;
622
623     // 16. If lim == 0, return A.
624     if (!limit)
625         return JSValue::encode(result);
626
627     // 17. If size == 0, then
628     if (input.isEmpty()) {
629         // a. Let z be ? RegExpExec(splitter, S).
630         // b. If z is not null, return A.
631         // c. Perform ! CreateDataProperty(A, "0", S).
632         // d. Return A.
633         if (!regexp->match(vm, input, 0)) {
634             result->putDirectIndex(exec, 0, inputString);
635             RETURN_IF_EXCEPTION(scope, encodedJSValue());
636         }
637         return JSValue::encode(result);
638     }
639
640     // 18. Let q = p.
641     unsigned matchPosition = position;
642     // 19. Repeat, while q < size
643     bool regExpIsSticky = regexp->sticky();
644     bool regExpIsUnicode = regexp->unicode();
645     
646     unsigned maxSizeForDirectPath = 100000;
647     
648     genericSplit(
649         vm, regexp, input, inputSize, position, matchPosition, regExpIsSticky, regExpIsUnicode,
650         [&] () -> SplitControl {
651             if (resultLength >= maxSizeForDirectPath)
652                 return AbortSplit;
653             return ContinueSplit;
654         },
655         [&] (bool isDefined, unsigned start, unsigned length) -> SplitControl {
656             result->putDirectIndex(exec, resultLength++, isDefined ? jsSubstringOfResolved(vm, inputString, start, length) : jsUndefined());
657             RETURN_IF_EXCEPTION(scope, AbortSplit);
658             if (resultLength >= limit)
659                 return AbortSplit;
660             return ContinueSplit;
661         });
662     RETURN_IF_EXCEPTION(scope, encodedJSValue());
663
664     if (resultLength >= limit)
665         return JSValue::encode(result);
666     if (resultLength < maxSizeForDirectPath) {
667         // 20. Let T be a String value equal to the substring of S consisting of the elements at indices p (inclusive) through size (exclusive).
668         // 21. Perform ! CreateDataProperty(A, ! ToString(lengthA), T).
669         scope.release();
670         result->putDirectIndex(exec, resultLength, jsSubstringOfResolved(vm, inputString, position, inputSize - position));
671         
672         // 22. Return A.
673         return JSValue::encode(result);
674     }
675     
676     // Now do a dry run to see how big things get. Give up if they get absurd.
677     unsigned savedPosition = position;
678     unsigned savedMatchPosition = matchPosition;
679     unsigned dryRunCount = 0;
680     genericSplit(
681         vm, regexp, input, inputSize, position, matchPosition, regExpIsSticky, regExpIsUnicode,
682         [&] () -> SplitControl {
683             if (resultLength + dryRunCount > MAX_STORAGE_VECTOR_LENGTH)
684                 return AbortSplit;
685             return ContinueSplit;
686         },
687         [&] (bool, unsigned, unsigned) -> SplitControl {
688             dryRunCount++;
689             if (resultLength + dryRunCount >= limit)
690                 return AbortSplit;
691             return ContinueSplit;
692         });
693     
694     if (resultLength + dryRunCount > MAX_STORAGE_VECTOR_LENGTH) {
695         throwOutOfMemoryError(exec, scope);
696         return encodedJSValue();
697     }
698     
699     // OK, we know that if we finish the split, we won't have to OOM.
700     position = savedPosition;
701     matchPosition = savedMatchPosition;
702     
703     genericSplit(
704         vm, regexp, input, inputSize, position, matchPosition, regExpIsSticky, regExpIsUnicode,
705         [&] () -> SplitControl {
706             return ContinueSplit;
707         },
708         [&] (bool isDefined, unsigned start, unsigned length) -> SplitControl {
709             result->putDirectIndex(exec, resultLength++, isDefined ? jsSubstringOfResolved(vm, inputString, start, length) : jsUndefined());
710             RETURN_IF_EXCEPTION(scope, AbortSplit);
711             if (resultLength >= limit)
712                 return AbortSplit;
713             return ContinueSplit;
714         });
715     RETURN_IF_EXCEPTION(scope, encodedJSValue());
716
717     if (resultLength >= limit)
718         return JSValue::encode(result);
719     
720     // 20. Let T be a String value equal to the substring of S consisting of the elements at indices p (inclusive) through size (exclusive).
721     // 21. Perform ! CreateDataProperty(A, ! ToString(lengthA), T).
722     scope.release();
723     result->putDirectIndex(exec, resultLength, jsSubstringOfResolved(vm, inputString, position, inputSize - position));
724     // 22. Return A.
725     return JSValue::encode(result);
726 }
727
728 } // namespace JSC