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