ccea70880fbda69b46f12220b905ae7c92341359
[WebKit-https.git] / Source / JavaScriptCore / 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 namespace JSC {
41
42 static JSValue regExpObjectGlobal(ExecState*, JSValue, PropertyName);
43 static JSValue regExpObjectIgnoreCase(ExecState*, JSValue, PropertyName);
44 static JSValue regExpObjectMultiline(ExecState*, JSValue, PropertyName);
45 static JSValue regExpObjectSource(ExecState*, JSValue, 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), 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 JSValue regExpObjectGlobal(ExecState*, JSValue slotBase, PropertyName)
159 {
160     return jsBoolean(asRegExpObject(slotBase)->regExp()->global());
161 }
162
163 JSValue regExpObjectIgnoreCase(ExecState*, JSValue slotBase, PropertyName)
164 {
165     return jsBoolean(asRegExpObject(slotBase)->regExp()->ignoreCase());
166 }
167  
168 JSValue regExpObjectMultiline(ExecState*, JSValue slotBase, PropertyName)
169 {            
170     return 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 JSValue regExpObjectSource(ExecState* exec, JSValue slotBase, PropertyName)
280 {
281     String pattern = asRegExpObject(slotBase)->regExp()->pattern();
282     if (pattern.is8Bit())
283         return regExpObjectSourceInternal(exec, pattern, pattern.characters8(), pattern.length());
284     return regExpObjectSourceInternal(exec, pattern, pattern.characters16(), pattern.length());
285 }
286
287 void RegExpObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
288 {
289     if (propertyName == exec->propertyNames().lastIndex) {
290         asRegExpObject(cell)->setLastIndex(exec, value, slot.isStrictMode());
291         return;
292     }
293     lookupPut<RegExpObject, JSObject>(exec, propertyName, value, ExecState::regExpTable(exec), jsCast<RegExpObject*>(cell), slot);
294 }
295
296 JSValue RegExpObject::exec(ExecState* exec, JSString* string)
297 {
298     if (MatchResult result = match(exec, string))
299         return RegExpMatchesArray::create(exec, string, regExp(), result);
300     return jsNull();
301 }
302
303 // Shared implementation used by test and exec.
304 MatchResult RegExpObject::match(ExecState* exec, JSString* string)
305 {
306     RegExp* regExp = this->regExp();
307     RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
308     String input = string->value(exec);
309     VM& vm = exec->vm();
310     if (!regExp->global())
311         return regExpConstructor->performMatch(vm, regExp, string, input, 0);
312
313     JSValue jsLastIndex = getLastIndex();
314     unsigned lastIndex;
315     if (LIKELY(jsLastIndex.isUInt32())) {
316         lastIndex = jsLastIndex.asUInt32();
317         if (lastIndex > input.length()) {
318             setLastIndex(exec, 0);
319             return MatchResult::failed();
320         }
321     } else {
322         double doubleLastIndex = jsLastIndex.toInteger(exec);
323         if (doubleLastIndex < 0 || doubleLastIndex > input.length()) {
324             setLastIndex(exec, 0);
325             return MatchResult::failed();
326         }
327         lastIndex = static_cast<unsigned>(doubleLastIndex);
328     }
329
330     MatchResult result = regExpConstructor->performMatch(vm, regExp, string, input, lastIndex);
331     setLastIndex(exec, result.end);
332     return result;
333 }
334
335 } // namespace JSC