]>
Commit | Line | Data |
---|---|---|
9dae56ea A |
1 | /* |
2 | * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) | |
6fe7ccc8 | 3 | * Copyright (C) 2003, 2007, 2008, 2012 Apple Inc. All Rights Reserved. |
9dae56ea A |
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 "RegExpObject.h" | |
23 | ||
93a37866 A |
24 | #include "ButterflyInlines.h" |
25 | #include "CopiedSpaceInlines.h" | |
ba379fdc | 26 | #include "Error.h" |
14957cd0 | 27 | #include "ExceptionHelpers.h" |
9dae56ea A |
28 | #include "JSArray.h" |
29 | #include "JSGlobalObject.h" | |
30 | #include "JSString.h" | |
6fe7ccc8 | 31 | #include "Lexer.h" |
4e4e5a6f | 32 | #include "Lookup.h" |
93a37866 | 33 | #include "Operations.h" |
9dae56ea | 34 | #include "RegExpConstructor.h" |
6fe7ccc8 | 35 | #include "RegExpMatchesArray.h" |
9dae56ea | 36 | #include "RegExpPrototype.h" |
14957cd0 | 37 | #include <wtf/PassOwnPtr.h> |
93a37866 | 38 | #include <wtf/text/StringBuilder.h> |
14957cd0 | 39 | |
93a37866 | 40 | #if PLATFORM(IOS) |
14957cd0 | 41 | #include <wtf/PassOwnPtr.h> |
93a37866 | 42 | #endif |
9dae56ea A |
43 | |
44 | namespace JSC { | |
45 | ||
93a37866 A |
46 | static JSValue regExpObjectGlobal(ExecState*, JSValue, PropertyName); |
47 | static JSValue regExpObjectIgnoreCase(ExecState*, JSValue, PropertyName); | |
48 | static JSValue regExpObjectMultiline(ExecState*, JSValue, PropertyName); | |
49 | static JSValue regExpObjectSource(ExecState*, JSValue, PropertyName); | |
9dae56ea A |
50 | |
51 | } // namespace JSC | |
52 | ||
53 | #include "RegExpObject.lut.h" | |
54 | ||
55 | namespace JSC { | |
56 | ||
93a37866 | 57 | ASSERT_HAS_TRIVIAL_DESTRUCTOR(RegExpObject); |
9dae56ea | 58 | |
93a37866 | 59 | const ClassInfo RegExpObject::s_info = { "RegExp", &Base::s_info, 0, ExecState::regExpTable, CREATE_METHOD_TABLE(RegExpObject) }; |
9dae56ea A |
60 | |
61 | /* Source for RegExpObject.lut.h | |
62 | @begin regExpTable | |
63 | global regExpObjectGlobal DontDelete|ReadOnly|DontEnum | |
64 | ignoreCase regExpObjectIgnoreCase DontDelete|ReadOnly|DontEnum | |
65 | multiline regExpObjectMultiline DontDelete|ReadOnly|DontEnum | |
66 | source regExpObjectSource DontDelete|ReadOnly|DontEnum | |
9dae56ea A |
67 | @end |
68 | */ | |
69 | ||
14957cd0 | 70 | RegExpObject::RegExpObject(JSGlobalObject* globalObject, Structure* structure, RegExp* regExp) |
93a37866 A |
71 | : JSNonFinalObject(globalObject->vm(), structure) |
72 | , m_regExp(globalObject->vm(), this, regExp) | |
6fe7ccc8 | 73 | , m_lastIndexIsWritable(true) |
9dae56ea | 74 | { |
6fe7ccc8 | 75 | m_lastIndex.setWithoutWriteBarrier(jsNumber(0)); |
9dae56ea A |
76 | } |
77 | ||
6fe7ccc8 | 78 | void RegExpObject::finishCreation(JSGlobalObject* globalObject) |
9dae56ea | 79 | { |
93a37866 | 80 | Base::finishCreation(globalObject->vm()); |
6fe7ccc8 | 81 | ASSERT(inherits(&s_info)); |
9dae56ea A |
82 | } |
83 | ||
6fe7ccc8 | 84 | void RegExpObject::visitChildren(JSCell* cell, SlotVisitor& visitor) |
14957cd0 | 85 | { |
6fe7ccc8 A |
86 | RegExpObject* thisObject = jsCast<RegExpObject*>(cell); |
87 | ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info); | |
14957cd0 | 88 | COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); |
6fe7ccc8 | 89 | ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren()); |
93a37866 | 90 | |
6fe7ccc8 | 91 | Base::visitChildren(thisObject, visitor); |
93a37866 A |
92 | visitor.append(&thisObject->m_regExp); |
93 | visitor.append(&thisObject->m_lastIndex); | |
6fe7ccc8 A |
94 | } |
95 | ||
93a37866 | 96 | bool RegExpObject::getOwnPropertySlot(JSCell* cell, ExecState* exec, PropertyName propertyName, PropertySlot& slot) |
6fe7ccc8 A |
97 | { |
98 | if (propertyName == exec->propertyNames().lastIndex) { | |
99 | RegExpObject* regExp = asRegExpObject(cell); | |
100 | slot.setValue(regExp, regExp->getLastIndex()); | |
101 | return true; | |
102 | } | |
103 | return getStaticValueSlot<RegExpObject, JSObject>(exec, ExecState::regExpTable(exec), jsCast<RegExpObject*>(cell), propertyName, slot); | |
14957cd0 A |
104 | } |
105 | ||
93a37866 | 106 | bool RegExpObject::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) |
9dae56ea | 107 | { |
6fe7ccc8 A |
108 | if (propertyName == exec->propertyNames().lastIndex) { |
109 | RegExpObject* regExp = asRegExpObject(object); | |
110 | descriptor.setDescriptor(regExp->getLastIndex(), regExp->m_lastIndexIsWritable ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly); | |
111 | return true; | |
112 | } | |
113 | return getStaticValueDescriptor<RegExpObject, JSObject>(exec, ExecState::regExpTable(exec), jsCast<RegExpObject*>(object), propertyName, descriptor); | |
114 | } | |
115 | ||
93a37866 | 116 | bool RegExpObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) |
6fe7ccc8 A |
117 | { |
118 | if (propertyName == exec->propertyNames().lastIndex) | |
119 | return false; | |
120 | return Base::deleteProperty(cell, exec, propertyName); | |
9dae56ea A |
121 | } |
122 | ||
93a37866 | 123 | void RegExpObject::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) |
f9bf01c6 | 124 | { |
6fe7ccc8 A |
125 | if (mode == IncludeDontEnumProperties) |
126 | propertyNames.add(exec->propertyNames().lastIndex); | |
93a37866 | 127 | Base::getOwnNonIndexPropertyNames(object, exec, propertyNames, mode); |
6fe7ccc8 A |
128 | } |
129 | ||
130 | void RegExpObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) | |
131 | { | |
132 | if (mode == IncludeDontEnumProperties) | |
133 | propertyNames.add(exec->propertyNames().lastIndex); | |
134 | Base::getPropertyNames(object, exec, propertyNames, mode); | |
135 | } | |
136 | ||
137 | static bool reject(ExecState* exec, bool throwException, const char* message) | |
138 | { | |
139 | if (throwException) | |
93a37866 | 140 | throwTypeError(exec, ASCIILiteral(message)); |
6fe7ccc8 A |
141 | return false; |
142 | } | |
143 | ||
93a37866 | 144 | bool RegExpObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor, bool shouldThrow) |
6fe7ccc8 A |
145 | { |
146 | if (propertyName == exec->propertyNames().lastIndex) { | |
147 | RegExpObject* regExp = asRegExpObject(object); | |
148 | if (descriptor.configurablePresent() && descriptor.configurable()) | |
149 | return reject(exec, shouldThrow, "Attempting to change configurable attribute of unconfigurable property."); | |
150 | if (descriptor.enumerablePresent() && descriptor.enumerable()) | |
151 | return reject(exec, shouldThrow, "Attempting to change enumerable attribute of unconfigurable property."); | |
152 | if (descriptor.isAccessorDescriptor()) | |
153 | return reject(exec, shouldThrow, "Attempting to change access mechanism for an unconfigurable property."); | |
154 | if (!regExp->m_lastIndexIsWritable) { | |
155 | if (descriptor.writablePresent() && descriptor.writable()) | |
156 | return reject(exec, shouldThrow, "Attempting to change writable attribute of unconfigurable property."); | |
157 | if (!sameValue(exec, regExp->getLastIndex(), descriptor.value())) | |
158 | return reject(exec, shouldThrow, "Attempting to change value of a readonly property."); | |
159 | return true; | |
160 | } | |
161 | if (descriptor.writablePresent() && !descriptor.writable()) | |
162 | regExp->m_lastIndexIsWritable = false; | |
163 | if (descriptor.value()) | |
164 | regExp->setLastIndex(exec, descriptor.value(), false); | |
165 | return true; | |
166 | } | |
167 | ||
168 | return Base::defineOwnProperty(object, exec, propertyName, descriptor, shouldThrow); | |
f9bf01c6 A |
169 | } |
170 | ||
93a37866 | 171 | JSValue regExpObjectGlobal(ExecState*, JSValue slotBase, PropertyName) |
9dae56ea | 172 | { |
4e4e5a6f | 173 | return jsBoolean(asRegExpObject(slotBase)->regExp()->global()); |
9dae56ea A |
174 | } |
175 | ||
93a37866 | 176 | JSValue regExpObjectIgnoreCase(ExecState*, JSValue slotBase, PropertyName) |
9dae56ea | 177 | { |
4e4e5a6f | 178 | return jsBoolean(asRegExpObject(slotBase)->regExp()->ignoreCase()); |
9dae56ea A |
179 | } |
180 | ||
93a37866 | 181 | JSValue regExpObjectMultiline(ExecState*, JSValue slotBase, PropertyName) |
9dae56ea | 182 | { |
4e4e5a6f | 183 | return jsBoolean(asRegExpObject(slotBase)->regExp()->multiline()); |
9dae56ea A |
184 | } |
185 | ||
93a37866 A |
186 | template <typename CharacterType> |
187 | static inline void appendLineTerminatorEscape(StringBuilder&, CharacterType); | |
188 | ||
189 | template <> | |
190 | inline void appendLineTerminatorEscape<LChar>(StringBuilder& builder, LChar lineTerminator) | |
191 | { | |
192 | if (lineTerminator == '\n') | |
193 | builder.append('n'); | |
194 | else | |
195 | builder.append('r'); | |
196 | } | |
197 | ||
198 | template <> | |
199 | inline void appendLineTerminatorEscape<UChar>(StringBuilder& builder, UChar lineTerminator) | |
200 | { | |
201 | if (lineTerminator == '\n') | |
202 | builder.append('n'); | |
203 | else if (lineTerminator == '\r') | |
204 | builder.append('r'); | |
205 | else if (lineTerminator == 0x2028) | |
206 | builder.appendLiteral("u2028"); | |
207 | else | |
208 | builder.appendLiteral("u2029"); | |
209 | } | |
210 | ||
211 | template <typename CharacterType> | |
212 | static inline JSValue regExpObjectSourceInternal(ExecState* exec, String pattern, const CharacterType* characters, unsigned length) | |
9dae56ea | 213 | { |
6fe7ccc8 A |
214 | bool previousCharacterWasBackslash = false; |
215 | bool inBrackets = false; | |
216 | bool shouldEscape = false; | |
9dae56ea | 217 | |
6fe7ccc8 A |
218 | // 15.10.6.4 specifies that RegExp.prototype.toString must return '/' + source + '/', |
219 | // and also states that the result must be a valid RegularExpressionLiteral. '//' is | |
220 | // not a valid RegularExpressionLiteral (since it is a single line comment), and hence | |
221 | // source cannot ever validly be "". If the source is empty, return a different Pattern | |
222 | // that would match the same thing. | |
223 | if (!length) | |
93a37866 | 224 | return jsNontrivialString(exec, ASCIILiteral("(?:)")); |
9dae56ea | 225 | |
6fe7ccc8 A |
226 | // early return for strings that don't contain a forwards slash and LineTerminator |
227 | for (unsigned i = 0; i < length; ++i) { | |
93a37866 | 228 | CharacterType ch = characters[i]; |
6fe7ccc8 A |
229 | if (!previousCharacterWasBackslash) { |
230 | if (inBrackets) { | |
231 | if (ch == ']') | |
232 | inBrackets = false; | |
233 | } else { | |
234 | if (ch == '/') { | |
235 | shouldEscape = true; | |
236 | break; | |
237 | } | |
238 | if (ch == '[') | |
239 | inBrackets = true; | |
240 | } | |
241 | } | |
9dae56ea | 242 | |
93a37866 | 243 | if (Lexer<CharacterType>::isLineTerminator(ch)) { |
6fe7ccc8 A |
244 | shouldEscape = true; |
245 | break; | |
246 | } | |
247 | ||
248 | if (previousCharacterWasBackslash) | |
249 | previousCharacterWasBackslash = false; | |
250 | else | |
251 | previousCharacterWasBackslash = ch == '\\'; | |
252 | } | |
253 | ||
254 | if (!shouldEscape) | |
255 | return jsString(exec, pattern); | |
256 | ||
257 | previousCharacterWasBackslash = false; | |
258 | inBrackets = false; | |
93a37866 | 259 | StringBuilder result; |
6fe7ccc8 | 260 | for (unsigned i = 0; i < length; ++i) { |
93a37866 | 261 | CharacterType ch = characters[i]; |
6fe7ccc8 A |
262 | if (!previousCharacterWasBackslash) { |
263 | if (inBrackets) { | |
264 | if (ch == ']') | |
265 | inBrackets = false; | |
266 | } else { | |
267 | if (ch == '/') | |
268 | result.append('\\'); | |
269 | else if (ch == '[') | |
270 | inBrackets = true; | |
271 | } | |
272 | } | |
273 | ||
274 | // escape LineTerminator | |
93a37866 | 275 | if (Lexer<CharacterType>::isLineTerminator(ch)) { |
6fe7ccc8 A |
276 | if (!previousCharacterWasBackslash) |
277 | result.append('\\'); | |
278 | ||
93a37866 | 279 | appendLineTerminatorEscape<CharacterType>(result, ch); |
6fe7ccc8 A |
280 | } else |
281 | result.append(ch); | |
282 | ||
283 | if (previousCharacterWasBackslash) | |
284 | previousCharacterWasBackslash = false; | |
285 | else | |
286 | previousCharacterWasBackslash = ch == '\\'; | |
287 | } | |
288 | ||
93a37866 A |
289 | return jsString(exec, result.toString()); |
290 | } | |
291 | ||
292 | JSValue regExpObjectSource(ExecState* exec, JSValue slotBase, PropertyName) | |
293 | { | |
294 | String pattern = asRegExpObject(slotBase)->regExp()->pattern(); | |
295 | if (pattern.is8Bit()) | |
296 | return regExpObjectSourceInternal(exec, pattern, pattern.characters8(), pattern.length()); | |
297 | return regExpObjectSourceInternal(exec, pattern, pattern.characters16(), pattern.length()); | |
9dae56ea A |
298 | } |
299 | ||
93a37866 | 300 | void RegExpObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) |
9dae56ea | 301 | { |
6fe7ccc8 A |
302 | if (propertyName == exec->propertyNames().lastIndex) { |
303 | asRegExpObject(cell)->setLastIndex(exec, value, slot.isStrictMode()); | |
304 | return; | |
305 | } | |
306 | lookupPut<RegExpObject, JSObject>(exec, propertyName, value, ExecState::regExpTable(exec), jsCast<RegExpObject*>(cell), slot); | |
9dae56ea A |
307 | } |
308 | ||
6fe7ccc8 | 309 | JSValue RegExpObject::exec(ExecState* exec, JSString* string) |
9dae56ea | 310 | { |
6fe7ccc8 A |
311 | if (MatchResult result = match(exec, string)) |
312 | return RegExpMatchesArray::create(exec, string, regExp(), result); | |
9dae56ea A |
313 | return jsNull(); |
314 | } | |
315 | ||
9dae56ea | 316 | // Shared implementation used by test and exec. |
6fe7ccc8 | 317 | MatchResult RegExpObject::match(ExecState* exec, JSString* string) |
9dae56ea | 318 | { |
6fe7ccc8 | 319 | RegExp* regExp = this->regExp(); |
9dae56ea | 320 | RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); |
93a37866 A |
321 | String input = string->value(exec); |
322 | VM& vm = exec->vm(); | |
6fe7ccc8 | 323 | if (!regExp->global()) |
93a37866 | 324 | return regExpConstructor->performMatch(vm, regExp, string, input, 0); |
9dae56ea | 325 | |
14957cd0 A |
326 | JSValue jsLastIndex = getLastIndex(); |
327 | unsigned lastIndex; | |
328 | if (LIKELY(jsLastIndex.isUInt32())) { | |
329 | lastIndex = jsLastIndex.asUInt32(); | |
330 | if (lastIndex > input.length()) { | |
6fe7ccc8 A |
331 | setLastIndex(exec, 0); |
332 | return MatchResult::failed(); | |
14957cd0 A |
333 | } |
334 | } else { | |
335 | double doubleLastIndex = jsLastIndex.toInteger(exec); | |
336 | if (doubleLastIndex < 0 || doubleLastIndex > input.length()) { | |
6fe7ccc8 A |
337 | setLastIndex(exec, 0); |
338 | return MatchResult::failed(); | |
14957cd0 A |
339 | } |
340 | lastIndex = static_cast<unsigned>(doubleLastIndex); | |
9dae56ea A |
341 | } |
342 | ||
93a37866 | 343 | MatchResult result = regExpConstructor->performMatch(vm, regExp, string, input, lastIndex); |
6fe7ccc8 A |
344 | setLastIndex(exec, result.end); |
345 | return result; | |
9dae56ea A |
346 | } |
347 | ||
348 | } // namespace JSC |