]>
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 "JSCInlines.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 | namespace JSC { | |
41 | ||
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); | |
46 | ||
47 | } // namespace JSC | |
48 | ||
49 | #include "RegExpObject.lut.h" | |
50 | ||
51 | namespace JSC { | |
52 | ||
53 | STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(RegExpObject); | |
54 | ||
55 | const ClassInfo RegExpObject::s_info = { "RegExp", &Base::s_info, 0, ExecState::regExpTable, CREATE_METHOD_TABLE(RegExpObject) }; | |
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 | |
63 | @end | |
64 | */ | |
65 | ||
66 | RegExpObject::RegExpObject(VM& vm, Structure* structure, RegExp* regExp) | |
67 | : JSNonFinalObject(vm, structure) | |
68 | , m_regExp(vm, this, regExp) | |
69 | , m_lastIndexIsWritable(true) | |
70 | { | |
71 | m_lastIndex.setWithoutWriteBarrier(jsNumber(0)); | |
72 | } | |
73 | ||
74 | void RegExpObject::finishCreation(VM& vm) | |
75 | { | |
76 | Base::finishCreation(vm); | |
77 | ASSERT(inherits(info())); | |
78 | } | |
79 | ||
80 | void RegExpObject::visitChildren(JSCell* cell, SlotVisitor& visitor) | |
81 | { | |
82 | RegExpObject* thisObject = jsCast<RegExpObject*>(cell); | |
83 | ASSERT_GC_OBJECT_INHERITS(thisObject, info()); | |
84 | COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); | |
85 | ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren()); | |
86 | ||
87 | Base::visitChildren(thisObject, visitor); | |
88 | visitor.append(&thisObject->m_regExp); | |
89 | visitor.append(&thisObject->m_lastIndex); | |
90 | } | |
91 | ||
92 | bool RegExpObject::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) | |
93 | { | |
94 | if (propertyName == exec->propertyNames().lastIndex) { | |
95 | RegExpObject* regExp = asRegExpObject(object); | |
96 | unsigned attributes = regExp->m_lastIndexIsWritable ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly; | |
97 | slot.setValue(regExp, attributes, regExp->getLastIndex()); | |
98 | return true; | |
99 | } | |
100 | return getStaticValueSlot<RegExpObject, JSObject>(exec, ExecState::regExpTable(exec->vm()), jsCast<RegExpObject*>(object), propertyName, slot); | |
101 | } | |
102 | ||
103 | bool RegExpObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) | |
104 | { | |
105 | if (propertyName == exec->propertyNames().lastIndex) | |
106 | return false; | |
107 | return Base::deleteProperty(cell, exec, propertyName); | |
108 | } | |
109 | ||
110 | void RegExpObject::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) | |
111 | { | |
112 | if (mode == IncludeDontEnumProperties) | |
113 | propertyNames.add(exec->propertyNames().lastIndex); | |
114 | Base::getOwnNonIndexPropertyNames(object, exec, propertyNames, mode); | |
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) | |
127 | throwTypeError(exec, ASCIILiteral(message)); | |
128 | return false; | |
129 | } | |
130 | ||
131 | bool RegExpObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) | |
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); | |
156 | } | |
157 | ||
158 | EncodedJSValue regExpObjectGlobal(ExecState*, JSObject* slotBase, EncodedJSValue, PropertyName) | |
159 | { | |
160 | return JSValue::encode(jsBoolean(asRegExpObject(slotBase)->regExp()->global())); | |
161 | } | |
162 | ||
163 | EncodedJSValue regExpObjectIgnoreCase(ExecState*, JSObject* slotBase, EncodedJSValue, PropertyName) | |
164 | { | |
165 | return JSValue::encode(jsBoolean(asRegExpObject(slotBase)->regExp()->ignoreCase())); | |
166 | } | |
167 | ||
168 | EncodedJSValue regExpObjectMultiline(ExecState*, JSObject* slotBase, EncodedJSValue, PropertyName) | |
169 | { | |
170 | return JSValue::encode(jsBoolean(asRegExpObject(slotBase)->regExp()->multiline())); | |
171 | } | |
172 | ||
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) | |
200 | { | |
201 | bool previousCharacterWasBackslash = false; | |
202 | bool inBrackets = false; | |
203 | bool shouldEscape = false; | |
204 | ||
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) | |
211 | return jsNontrivialString(exec, ASCIILiteral("(?:)")); | |
212 | ||
213 | // early return for strings that don't contain a forwards slash and LineTerminator | |
214 | for (unsigned i = 0; i < length; ++i) { | |
215 | CharacterType ch = characters[i]; | |
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 | } | |
229 | ||
230 | if (Lexer<CharacterType>::isLineTerminator(ch)) { | |
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; | |
246 | StringBuilder result; | |
247 | for (unsigned i = 0; i < length; ++i) { | |
248 | CharacterType ch = characters[i]; | |
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 | |
262 | if (Lexer<CharacterType>::isLineTerminator(ch)) { | |
263 | if (!previousCharacterWasBackslash) | |
264 | result.append('\\'); | |
265 | ||
266 | appendLineTerminatorEscape<CharacterType>(result, ch); | |
267 | } else | |
268 | result.append(ch); | |
269 | ||
270 | if (previousCharacterWasBackslash) | |
271 | previousCharacterWasBackslash = false; | |
272 | else | |
273 | previousCharacterWasBackslash = ch == '\\'; | |
274 | } | |
275 | ||
276 | return jsString(exec, result.toString()); | |
277 | } | |
278 | ||
279 | ||
280 | ||
281 | EncodedJSValue regExpObjectSource(ExecState* exec, JSObject* slotBase, EncodedJSValue, PropertyName) | |
282 | { | |
283 | String pattern = asRegExpObject(slotBase)->regExp()->pattern(); | |
284 | if (pattern.is8Bit()) | |
285 | return JSValue::encode(regExpObjectSourceInternal(exec, pattern, pattern.characters8(), pattern.length())); | |
286 | return JSValue::encode(regExpObjectSourceInternal(exec, pattern, pattern.characters16(), pattern.length())); | |
287 | } | |
288 | ||
289 | void RegExpObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) | |
290 | { | |
291 | if (propertyName == exec->propertyNames().lastIndex) { | |
292 | asRegExpObject(cell)->setLastIndex(exec, value, slot.isStrictMode()); | |
293 | return; | |
294 | } | |
295 | Base::put(cell, exec, propertyName, value, slot); | |
296 | } | |
297 | ||
298 | JSValue RegExpObject::exec(ExecState* exec, JSString* string) | |
299 | { | |
300 | if (MatchResult result = match(exec, string)) | |
301 | return RegExpMatchesArray::create(exec, string, regExp(), result); | |
302 | return jsNull(); | |
303 | } | |
304 | ||
305 | // Shared implementation used by test and exec. | |
306 | MatchResult RegExpObject::match(ExecState* exec, JSString* string) | |
307 | { | |
308 | RegExp* regExp = this->regExp(); | |
309 | RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); | |
310 | String input = string->value(exec); | |
311 | VM& vm = exec->vm(); | |
312 | if (!regExp->global()) | |
313 | return regExpConstructor->performMatch(vm, regExp, string, input, 0); | |
314 | ||
315 | JSValue jsLastIndex = getLastIndex(); | |
316 | unsigned lastIndex; | |
317 | if (LIKELY(jsLastIndex.isUInt32())) { | |
318 | lastIndex = jsLastIndex.asUInt32(); | |
319 | if (lastIndex > input.length()) { | |
320 | setLastIndex(exec, 0); | |
321 | return MatchResult::failed(); | |
322 | } | |
323 | } else { | |
324 | double doubleLastIndex = jsLastIndex.toInteger(exec); | |
325 | if (doubleLastIndex < 0 || doubleLastIndex > input.length()) { | |
326 | setLastIndex(exec, 0); | |
327 | return MatchResult::failed(); | |
328 | } | |
329 | lastIndex = static_cast<unsigned>(doubleLastIndex); | |
330 | } | |
331 | ||
332 | MatchResult result = regExpConstructor->performMatch(vm, regExp, string, input, lastIndex); | |
333 | setLastIndex(exec, result.end); | |
334 | return result; | |
335 | } | |
336 | ||
337 | } // namespace JSC |