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