]> git.saurik.com Git - apple/javascriptcore.git/blob - runtime/RegExpObject.cpp
JavaScriptCore-1097.13.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 "Error.h"
25 #include "ExceptionHelpers.h"
26 #include "JSArray.h"
27 #include "JSGlobalObject.h"
28 #include "JSString.h"
29 #include "Lexer.h"
30 #include "Lookup.h"
31 #include "RegExpConstructor.h"
32 #include "RegExpMatchesArray.h"
33 #include "RegExpPrototype.h"
34 #include "UStringBuilder.h"
35 #include "UStringConcatenate.h"
36 #include <wtf/PassOwnPtr.h>
37
38 #include <wtf/PassOwnPtr.h>
39
40 namespace JSC {
41
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&);
46
47 } // namespace JSC
48
49 #include "RegExpObject.lut.h"
50
51 namespace JSC {
52
53 ASSERT_CLASS_FITS_IN_CELL(RegExpObject);
54
55 const ClassInfo RegExpObject::s_info = { "RegExp", &JSNonFinalObject::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(JSGlobalObject* globalObject, Structure* structure, RegExp* regExp)
67 : JSNonFinalObject(globalObject->globalData(), structure)
68 , m_regExp(globalObject->globalData(), this, regExp)
69 , m_lastIndexIsWritable(true)
70 {
71 m_lastIndex.setWithoutWriteBarrier(jsNumber(0));
72 }
73
74 void RegExpObject::finishCreation(JSGlobalObject* globalObject)
75 {
76 Base::finishCreation(globalObject->globalData());
77 ASSERT(inherits(&s_info));
78 }
79
80 void RegExpObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
81 {
82 RegExpObject* thisObject = jsCast<RegExpObject*>(cell);
83 ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info);
84 COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag);
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);
101 }
102
103 bool RegExpObject::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
104 {
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);
118 }
119
120 void RegExpObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
121 {
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);
166 }
167
168 JSValue regExpObjectGlobal(ExecState*, JSValue slotBase, const Identifier&)
169 {
170 return jsBoolean(asRegExpObject(slotBase)->regExp()->global());
171 }
172
173 JSValue regExpObjectIgnoreCase(ExecState*, JSValue slotBase, const Identifier&)
174 {
175 return jsBoolean(asRegExpObject(slotBase)->regExp()->ignoreCase());
176 }
177
178 JSValue regExpObjectMultiline(ExecState*, JSValue slotBase, const Identifier&)
179 {
180 return jsBoolean(asRegExpObject(slotBase)->regExp()->multiline());
181 }
182
183 JSValue regExpObjectSource(ExecState* exec, JSValue slotBase, const Identifier&)
184 {
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;
191
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, "(?:)");
199
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 }
216
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());
271 }
272
273 void RegExpObject::put(JSCell* cell, ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
274 {
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);
280 }
281
282 JSValue RegExpObject::exec(ExecState* exec, JSString* string)
283 {
284 if (MatchResult result = match(exec, string))
285 return RegExpMatchesArray::create(exec, string, regExp(), result);
286 return jsNull();
287 }
288
289 // Shared implementation used by test and exec.
290 MatchResult RegExpObject::match(ExecState* exec, JSString* string)
291 {
292 RegExp* regExp = this->regExp();
293 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
294 UString input = string->value(exec);
295 JSGlobalData& globalData = exec->globalData();
296 if (!regExp->global())
297 return regExpConstructor->performMatch(globalData, regExp, string, input, 0);
298
299 JSValue jsLastIndex = getLastIndex();
300 unsigned lastIndex;
301 if (LIKELY(jsLastIndex.isUInt32())) {
302 lastIndex = jsLastIndex.asUInt32();
303 if (lastIndex > input.length()) {
304 setLastIndex(exec, 0);
305 return MatchResult::failed();
306 }
307 } else {
308 double doubleLastIndex = jsLastIndex.toInteger(exec);
309 if (doubleLastIndex < 0 || doubleLastIndex > input.length()) {
310 setLastIndex(exec, 0);
311 return MatchResult::failed();
312 }
313 lastIndex = static_cast<unsigned>(doubleLastIndex);
314 }
315
316 MatchResult result = regExpConstructor->performMatch(globalData, regExp, string, input, lastIndex);
317 setLastIndex(exec, result.end);
318 return result;
319 }
320
321 } // namespace JSC