]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) | |
3 | * Copyright (C) 2003, 2007, 2008, 2012 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 | #include "config.h" | |
22 | #include "RegExpObject.h" | |
23 | ||
24 | #include "ButterflyInlines.h" | |
25 | #include "CopiedSpaceInlines.h" | |
26 | #include "Error.h" | |
27 | #include "ExceptionHelpers.h" | |
28 | #include "JSArray.h" | |
29 | #include "JSGlobalObject.h" | |
30 | #include "JSString.h" | |
31 | #include "Lexer.h" | |
32 | #include "Lookup.h" | |
33 | #include "Operations.h" | |
34 | #include "RegExpConstructor.h" | |
35 | #include "RegExpMatchesArray.h" | |
36 | #include "RegExpPrototype.h" | |
37 | #include <wtf/PassOwnPtr.h> | |
38 | #include <wtf/text/StringBuilder.h> | |
39 | ||
40 | #if PLATFORM(IOS) | |
41 | #include <wtf/PassOwnPtr.h> | |
42 | #endif | |
43 | ||
44 | namespace JSC { | |
45 | ||
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); | |
50 | ||
51 | } // namespace JSC | |
52 | ||
53 | #include "RegExpObject.lut.h" | |
54 | ||
55 | namespace JSC { | |
56 | ||
57 | ASSERT_HAS_TRIVIAL_DESTRUCTOR(RegExpObject); | |
58 | ||
59 | const ClassInfo RegExpObject::s_info = { "RegExp", &Base::s_info, 0, ExecState::regExpTable, CREATE_METHOD_TABLE(RegExpObject) }; | |
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 | |
67 | @end | |
68 | */ | |
69 | ||
70 | RegExpObject::RegExpObject(JSGlobalObject* globalObject, Structure* structure, RegExp* regExp) | |
71 | : JSNonFinalObject(globalObject->vm(), structure) | |
72 | , m_regExp(globalObject->vm(), this, regExp) | |
73 | , m_lastIndexIsWritable(true) | |
74 | { | |
75 | m_lastIndex.setWithoutWriteBarrier(jsNumber(0)); | |
76 | } | |
77 | ||
78 | void RegExpObject::finishCreation(JSGlobalObject* globalObject) | |
79 | { | |
80 | Base::finishCreation(globalObject->vm()); | |
81 | ASSERT(inherits(&s_info)); | |
82 | } | |
83 | ||
84 | void RegExpObject::visitChildren(JSCell* cell, SlotVisitor& visitor) | |
85 | { | |
86 | RegExpObject* thisObject = jsCast<RegExpObject*>(cell); | |
87 | ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info); | |
88 | COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); | |
89 | ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren()); | |
90 | ||
91 | Base::visitChildren(thisObject, visitor); | |
92 | visitor.append(&thisObject->m_regExp); | |
93 | visitor.append(&thisObject->m_lastIndex); | |
94 | } | |
95 | ||
96 | bool RegExpObject::getOwnPropertySlot(JSCell* cell, ExecState* exec, PropertyName propertyName, PropertySlot& slot) | |
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); | |
104 | } | |
105 | ||
106 | bool RegExpObject::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) | |
107 | { | |
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 | ||
116 | bool RegExpObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) | |
117 | { | |
118 | if (propertyName == exec->propertyNames().lastIndex) | |
119 | return false; | |
120 | return Base::deleteProperty(cell, exec, propertyName); | |
121 | } | |
122 | ||
123 | void RegExpObject::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) | |
124 | { | |
125 | if (mode == IncludeDontEnumProperties) | |
126 | propertyNames.add(exec->propertyNames().lastIndex); | |
127 | Base::getOwnNonIndexPropertyNames(object, exec, propertyNames, mode); | |
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) | |
140 | throwTypeError(exec, ASCIILiteral(message)); | |
141 | return false; | |
142 | } | |
143 | ||
144 | bool RegExpObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor, bool shouldThrow) | |
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); | |
169 | } | |
170 | ||
171 | JSValue regExpObjectGlobal(ExecState*, JSValue slotBase, PropertyName) | |
172 | { | |
173 | return jsBoolean(asRegExpObject(slotBase)->regExp()->global()); | |
174 | } | |
175 | ||
176 | JSValue regExpObjectIgnoreCase(ExecState*, JSValue slotBase, PropertyName) | |
177 | { | |
178 | return jsBoolean(asRegExpObject(slotBase)->regExp()->ignoreCase()); | |
179 | } | |
180 | ||
181 | JSValue regExpObjectMultiline(ExecState*, JSValue slotBase, PropertyName) | |
182 | { | |
183 | return jsBoolean(asRegExpObject(slotBase)->regExp()->multiline()); | |
184 | } | |
185 | ||
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) | |
213 | { | |
214 | bool previousCharacterWasBackslash = false; | |
215 | bool inBrackets = false; | |
216 | bool shouldEscape = false; | |
217 | ||
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) | |
224 | return jsNontrivialString(exec, ASCIILiteral("(?:)")); | |
225 | ||
226 | // early return for strings that don't contain a forwards slash and LineTerminator | |
227 | for (unsigned i = 0; i < length; ++i) { | |
228 | CharacterType ch = characters[i]; | |
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 | } | |
242 | ||
243 | if (Lexer<CharacterType>::isLineTerminator(ch)) { | |
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; | |
259 | StringBuilder result; | |
260 | for (unsigned i = 0; i < length; ++i) { | |
261 | CharacterType ch = characters[i]; | |
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 | |
275 | if (Lexer<CharacterType>::isLineTerminator(ch)) { | |
276 | if (!previousCharacterWasBackslash) | |
277 | result.append('\\'); | |
278 | ||
279 | appendLineTerminatorEscape<CharacterType>(result, ch); | |
280 | } else | |
281 | result.append(ch); | |
282 | ||
283 | if (previousCharacterWasBackslash) | |
284 | previousCharacterWasBackslash = false; | |
285 | else | |
286 | previousCharacterWasBackslash = ch == '\\'; | |
287 | } | |
288 | ||
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()); | |
298 | } | |
299 | ||
300 | void RegExpObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) | |
301 | { | |
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); | |
307 | } | |
308 | ||
309 | JSValue RegExpObject::exec(ExecState* exec, JSString* string) | |
310 | { | |
311 | if (MatchResult result = match(exec, string)) | |
312 | return RegExpMatchesArray::create(exec, string, regExp(), result); | |
313 | return jsNull(); | |
314 | } | |
315 | ||
316 | // Shared implementation used by test and exec. | |
317 | MatchResult RegExpObject::match(ExecState* exec, JSString* string) | |
318 | { | |
319 | RegExp* regExp = this->regExp(); | |
320 | RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); | |
321 | String input = string->value(exec); | |
322 | VM& vm = exec->vm(); | |
323 | if (!regExp->global()) | |
324 | return regExpConstructor->performMatch(vm, regExp, string, input, 0); | |
325 | ||
326 | JSValue jsLastIndex = getLastIndex(); | |
327 | unsigned lastIndex; | |
328 | if (LIKELY(jsLastIndex.isUInt32())) { | |
329 | lastIndex = jsLastIndex.asUInt32(); | |
330 | if (lastIndex > input.length()) { | |
331 | setLastIndex(exec, 0); | |
332 | return MatchResult::failed(); | |
333 | } | |
334 | } else { | |
335 | double doubleLastIndex = jsLastIndex.toInteger(exec); | |
336 | if (doubleLastIndex < 0 || doubleLastIndex > input.length()) { | |
337 | setLastIndex(exec, 0); | |
338 | return MatchResult::failed(); | |
339 | } | |
340 | lastIndex = static_cast<unsigned>(doubleLastIndex); | |
341 | } | |
342 | ||
343 | MatchResult result = regExpConstructor->performMatch(vm, regExp, string, input, lastIndex); | |
344 | setLastIndex(exec, result.end); | |
345 | return result; | |
346 | } | |
347 | ||
348 | } // namespace JSC |