RegExpObject's collectMatches should not be using JSArray::push to fill in its match...
[WebKit-https.git] / Source / JavaScriptCore / runtime / RegExpObjectInlines.h
1 /*
2  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2003-2018 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 #pragma once
22
23 #include "ButterflyInlines.h"
24 #include "Error.h"
25 #include "ExceptionHelpers.h"
26 #include "JSArray.h"
27 #include "JSGlobalObject.h"
28 #include "JSString.h"
29 #include "JSCInlines.h"
30 #include "RegExpConstructor.h"
31 #include "RegExpMatchesArray.h"
32 #include "RegExpObject.h"
33
34 namespace JSC {
35
36 ALWAYS_INLINE unsigned getRegExpObjectLastIndexAsUnsigned(
37     ExecState* exec, RegExpObject* regExpObject, const String& input)
38 {
39     VM& vm = exec->vm();
40     auto scope = DECLARE_THROW_SCOPE(vm);
41     JSValue jsLastIndex = regExpObject->getLastIndex();
42     unsigned lastIndex;
43     if (LIKELY(jsLastIndex.isUInt32())) {
44         lastIndex = jsLastIndex.asUInt32();
45         if (lastIndex > input.length()) {
46             scope.release();
47             regExpObject->setLastIndex(exec, 0);
48             return UINT_MAX;
49         }
50     } else {
51         double doubleLastIndex = jsLastIndex.toInteger(exec);
52         RETURN_IF_EXCEPTION(scope, UINT_MAX);
53         if (doubleLastIndex < 0 || doubleLastIndex > input.length()) {
54             scope.release();
55             regExpObject->setLastIndex(exec, 0);
56             return UINT_MAX;
57         }
58         lastIndex = static_cast<unsigned>(doubleLastIndex);
59     }
60     return lastIndex;
61 }
62
63 inline JSValue RegExpObject::execInline(ExecState* exec, JSGlobalObject* globalObject, JSString* string)
64 {
65     VM& vm = globalObject->vm();
66     auto scope = DECLARE_THROW_SCOPE(vm);
67
68     RegExp* regExp = this->regExp();
69     RegExpConstructor* regExpConstructor = globalObject->regExpConstructor();
70     String input = string->value(exec);
71     RETURN_IF_EXCEPTION(scope, { });
72
73     bool globalOrSticky = regExp->globalOrSticky();
74
75     unsigned lastIndex;
76     if (globalOrSticky) {
77         lastIndex = getRegExpObjectLastIndexAsUnsigned(exec, this, input);
78         EXCEPTION_ASSERT(!scope.exception() || lastIndex == UINT_MAX);
79         if (lastIndex == UINT_MAX)
80             return jsNull();
81     } else
82         lastIndex = 0;
83     
84     MatchResult result;
85     JSArray* array =
86         createRegExpMatchesArray(vm, globalObject, string, input, regExp, lastIndex, result);
87     if (!array) {
88         RETURN_IF_EXCEPTION(scope, { });
89         scope.release();
90         if (globalOrSticky)
91             setLastIndex(exec, 0);
92         return jsNull();
93     }
94
95     if (globalOrSticky)
96         setLastIndex(exec, result.end);
97     RETURN_IF_EXCEPTION(scope, { });
98     regExpConstructor->recordMatch(vm, regExp, string, result);
99     return array;
100 }
101
102 // Shared implementation used by test and exec.
103 inline MatchResult RegExpObject::matchInline(
104     ExecState* exec, JSGlobalObject* globalObject, JSString* string)
105 {
106     VM& vm = globalObject->vm();
107     auto scope = DECLARE_THROW_SCOPE(vm);
108
109     RegExp* regExp = this->regExp();
110     RegExpConstructor* regExpConstructor = globalObject->regExpConstructor();
111     String input = string->value(exec);
112     RETURN_IF_EXCEPTION(scope, { });
113
114     if (!regExp->global() && !regExp->sticky()) {
115         scope.release();
116         return regExpConstructor->performMatch(vm, regExp, string, input, 0);
117     }
118
119     unsigned lastIndex = getRegExpObjectLastIndexAsUnsigned(exec, this, input);
120     EXCEPTION_ASSERT(!scope.exception() || (lastIndex == UINT_MAX));
121     if (lastIndex == UINT_MAX)
122         return MatchResult::failed();
123     
124     MatchResult result = regExpConstructor->performMatch(vm, regExp, string, input, lastIndex);
125     RETURN_IF_EXCEPTION(scope, { });
126     scope.release();
127     setLastIndex(exec, result.end);
128     return result;
129 }
130
131 inline unsigned advanceStringUnicode(String s, unsigned length, unsigned currentIndex)
132 {
133     if (currentIndex + 1 >= length)
134         return currentIndex + 1;
135
136     UChar first = s[currentIndex];
137     if (first < 0xD800 || first > 0xDBFF)
138         return currentIndex + 1;
139
140     UChar second = s[currentIndex + 1];
141     if (second < 0xDC00 || second > 0xDFFF)
142         return currentIndex + 1;
143
144     return currentIndex + 2;
145 }
146
147 template<typename FixEndFunc>
148 JSValue collectMatches(VM& vm, ExecState* exec, JSString* string, const String& s, RegExpConstructor* constructor, RegExp* regExp, const FixEndFunc& fixEnd)
149 {
150     auto scope = DECLARE_THROW_SCOPE(vm);
151
152     MatchResult result = constructor->performMatch(vm, regExp, string, s, 0);
153     RETURN_IF_EXCEPTION(scope, { });
154     if (!result)
155         return jsNull();
156     
157     static unsigned maxSizeForDirectPath = 100000;
158     
159     JSArray* array = constructEmptyArray(exec, nullptr);
160     RETURN_IF_EXCEPTION(scope, { });
161
162     bool hasException = false;
163     unsigned arrayIndex = 0;
164     auto iterate = [&] () {
165         size_t end = result.end;
166         size_t length = end - result.start;
167         array->putDirectIndex(exec, arrayIndex++, JSRopeString::createSubstringOfResolved(vm, string, result.start, length));
168         if (UNLIKELY(scope.exception())) {
169             hasException = true;
170             return;
171         }
172         if (!length)
173             end = fixEnd(end);
174         result = constructor->performMatch(vm, regExp, string, s, end);
175         if (UNLIKELY(scope.exception())) {
176             hasException = true;
177             return;
178         }
179     };
180     
181     do {
182         if (array->length() >= maxSizeForDirectPath) {
183             // First do a throw-away match to see how many matches we'll get.
184             unsigned matchCount = 0;
185             MatchResult savedResult = result;
186             do {
187                 if (array->length() + matchCount > MAX_STORAGE_VECTOR_LENGTH) {
188                     throwOutOfMemoryError(exec, scope);
189                     return jsUndefined();
190                 }
191                 
192                 size_t end = result.end;
193                 matchCount++;
194                 if (result.empty())
195                     end = fixEnd(end);
196                 
197                 // Using RegExpConstructor::performMatch() instead of calling RegExp::match()
198                 // directly is a surprising but profitable choice: it means that when we do OOM, we
199                 // will leave the cached result in the state it ought to have had just before the
200                 // OOM! On the other hand, if this loop concludes that the result is small enough,
201                 // then the iterate() loop below will overwrite the cached result anyway.
202                 result = constructor->performMatch(vm, regExp, string, s, end);
203                 RETURN_IF_EXCEPTION(scope, { });
204             } while (result);
205             
206             // OK, we have a sensible number of matches. Now we can create them for reals.
207             result = savedResult;
208             do {
209                 iterate();
210                 EXCEPTION_ASSERT(!!scope.exception() == hasException);
211                 if (UNLIKELY(hasException))
212                     return { };
213             } while (result);
214             
215             return array;
216         }
217         
218         iterate();
219         EXCEPTION_ASSERT(!!scope.exception() == hasException);
220         if (UNLIKELY(hasException))
221             return { };
222     } while (result);
223     
224     return array;
225 }
226
227 } // namespace JSC