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