]> git.saurik.com Git - apple/javascriptcore.git/blob - runtime/RegExpObject.cpp
JavaScriptCore-1218.33.tar.gz
[apple/javascriptcore.git] / runtime / RegExpObject.cpp
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