Add support for private names
[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 "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 namespace JSC {
39
40 static JSValue regExpObjectGlobal(ExecState*, JSValue, PropertyName);
41 static JSValue regExpObjectIgnoreCase(ExecState*, JSValue, PropertyName);
42 static JSValue regExpObjectMultiline(ExecState*, JSValue, PropertyName);
43 static JSValue regExpObjectSource(ExecState*, JSValue, PropertyName);
44
45 } // namespace JSC
46
47 #include "RegExpObject.lut.h"
48
49 namespace JSC {
50
51 ASSERT_CLASS_FITS_IN_CELL(RegExpObject);
52
53 const ClassInfo RegExpObject::s_info = { "RegExp", &JSNonFinalObject::s_info, 0, ExecState::regExpTable, CREATE_METHOD_TABLE(RegExpObject) };
54
55 /* Source for RegExpObject.lut.h
56 @begin regExpTable
57     global        regExpObjectGlobal       DontDelete|ReadOnly|DontEnum
58     ignoreCase    regExpObjectIgnoreCase   DontDelete|ReadOnly|DontEnum
59     multiline     regExpObjectMultiline    DontDelete|ReadOnly|DontEnum
60     source        regExpObjectSource       DontDelete|ReadOnly|DontEnum
61 @end
62 */
63
64 RegExpObject::RegExpObject(JSGlobalObject* globalObject, Structure* structure, RegExp* regExp)
65     : JSNonFinalObject(globalObject->globalData(), structure)
66     , m_regExp(globalObject->globalData(), this, regExp)
67     , m_lastIndexIsWritable(true)
68 {
69     m_lastIndex.setWithoutWriteBarrier(jsNumber(0));
70 }
71
72 void RegExpObject::finishCreation(JSGlobalObject* globalObject)
73 {
74     Base::finishCreation(globalObject->globalData());
75     ASSERT(inherits(&s_info));
76 }
77
78 void RegExpObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
79 {
80     RegExpObject* thisObject = jsCast<RegExpObject*>(cell);
81     ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info);
82     COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag);
83     ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren());
84     Base::visitChildren(thisObject, visitor);
85     if (thisObject->m_regExp)
86         visitor.append(&thisObject->m_regExp);
87     if (UNLIKELY(!thisObject->m_lastIndex.get().isInt32()))
88         visitor.append(&thisObject->m_lastIndex);
89 }
90
91 bool RegExpObject::getOwnPropertySlot(JSCell* cell, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
92 {
93     if (propertyName == exec->propertyNames().lastIndex) {
94         RegExpObject* regExp = asRegExpObject(cell);
95         slot.setValue(regExp, regExp->getLastIndex());
96         return true;
97     }
98     return getStaticValueSlot<RegExpObject, JSObject>(exec, ExecState::regExpTable(exec), jsCast<RegExpObject*>(cell), propertyName, slot);
99 }
100
101 bool RegExpObject::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor)
102 {
103     if (propertyName == exec->propertyNames().lastIndex) {
104         RegExpObject* regExp = asRegExpObject(object);
105         descriptor.setDescriptor(regExp->getLastIndex(), regExp->m_lastIndexIsWritable ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly);
106         return true;
107     }
108     return getStaticValueDescriptor<RegExpObject, JSObject>(exec, ExecState::regExpTable(exec), jsCast<RegExpObject*>(object), propertyName, descriptor);
109 }
110
111 bool RegExpObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
112 {
113     if (propertyName == exec->propertyNames().lastIndex)
114         return false;
115     return Base::deleteProperty(cell, exec, propertyName);
116 }
117
118 void RegExpObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
119 {
120     if (mode == IncludeDontEnumProperties)
121         propertyNames.add(exec->propertyNames().lastIndex);
122     Base::getOwnPropertyNames(object, exec, propertyNames, mode);
123 }
124
125 void RegExpObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
126 {
127     if (mode == IncludeDontEnumProperties)
128         propertyNames.add(exec->propertyNames().lastIndex);
129     Base::getPropertyNames(object, exec, propertyNames, mode);
130 }
131
132 static bool reject(ExecState* exec, bool throwException, const char* message)
133 {
134     if (throwException)
135         throwTypeError(exec, message);
136     return false;
137 }
138
139 bool RegExpObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor, bool shouldThrow)
140 {
141     if (propertyName == exec->propertyNames().lastIndex) {
142         RegExpObject* regExp = asRegExpObject(object);
143         if (descriptor.configurablePresent() && descriptor.configurable())
144             return reject(exec, shouldThrow, "Attempting to change configurable attribute of unconfigurable property.");
145         if (descriptor.enumerablePresent() && descriptor.enumerable())
146             return reject(exec, shouldThrow, "Attempting to change enumerable attribute of unconfigurable property.");
147         if (descriptor.isAccessorDescriptor())
148             return reject(exec, shouldThrow, "Attempting to change access mechanism for an unconfigurable property.");
149         if (!regExp->m_lastIndexIsWritable) {
150             if (descriptor.writablePresent() && descriptor.writable())
151                 return reject(exec, shouldThrow, "Attempting to change writable attribute of unconfigurable property.");
152             if (!sameValue(exec, regExp->getLastIndex(), descriptor.value()))
153                 return reject(exec, shouldThrow, "Attempting to change value of a readonly property.");
154             return true;
155         }
156         if (descriptor.writablePresent() && !descriptor.writable())
157             regExp->m_lastIndexIsWritable = false;
158         if (descriptor.value())
159             regExp->setLastIndex(exec, descriptor.value(), false);
160         return true;
161     }
162
163     return Base::defineOwnProperty(object, exec, propertyName, descriptor, shouldThrow);
164 }
165
166 JSValue regExpObjectGlobal(ExecState*, JSValue slotBase, PropertyName)
167 {
168     return jsBoolean(asRegExpObject(slotBase)->regExp()->global());
169 }
170
171 JSValue regExpObjectIgnoreCase(ExecState*, JSValue slotBase, PropertyName)
172 {
173     return jsBoolean(asRegExpObject(slotBase)->regExp()->ignoreCase());
174 }
175  
176 JSValue regExpObjectMultiline(ExecState*, JSValue slotBase, PropertyName)
177 {            
178     return jsBoolean(asRegExpObject(slotBase)->regExp()->multiline());
179 }
180
181 JSValue regExpObjectSource(ExecState* exec, JSValue slotBase, PropertyName)
182 {
183     UString pattern = asRegExpObject(slotBase)->regExp()->pattern();
184     unsigned length = pattern.length();
185     const UChar* characters = pattern.characters();
186     bool previousCharacterWasBackslash = false;
187     bool inBrackets = false;
188     bool shouldEscape = false;
189
190     // 15.10.6.4 specifies that RegExp.prototype.toString must return '/' + source + '/',
191     // and also states that the result must be a valid RegularExpressionLiteral. '//' is
192     // not a valid RegularExpressionLiteral (since it is a single line comment), and hence
193     // source cannot ever validly be "". If the source is empty, return a different Pattern
194     // that would match the same thing.
195     if (!length)
196         return jsString(exec, "(?:)");
197
198     // early return for strings that don't contain a forwards slash and LineTerminator
199     for (unsigned i = 0; i < length; ++i) {
200         UChar ch = characters[i];
201         if (!previousCharacterWasBackslash) {
202             if (inBrackets) {
203                 if (ch == ']')
204                     inBrackets = false;
205             } else {
206                 if (ch == '/') {
207                     shouldEscape = true;
208                     break;
209                 }
210                 if (ch == '[')
211                     inBrackets = true;
212             }
213         }
214
215         if (Lexer<UChar>::isLineTerminator(ch)) {
216             shouldEscape = true;
217             break;
218         }
219
220         if (previousCharacterWasBackslash)
221             previousCharacterWasBackslash = false;
222         else
223             previousCharacterWasBackslash = ch == '\\';
224     }
225
226     if (!shouldEscape)
227         return jsString(exec, pattern);
228
229     previousCharacterWasBackslash = false;
230     inBrackets = false;
231     UStringBuilder result;
232     for (unsigned i = 0; i < length; ++i) {
233         UChar ch = characters[i];
234         if (!previousCharacterWasBackslash) {
235             if (inBrackets) {
236                 if (ch == ']')
237                     inBrackets = false;
238             } else {
239                 if (ch == '/')
240                     result.append('\\');
241                 else if (ch == '[')
242                     inBrackets = true;
243             }
244         }
245
246         // escape LineTerminator
247         if (Lexer<UChar>::isLineTerminator(ch)) {
248             if (!previousCharacterWasBackslash)
249                 result.append('\\');
250
251             if (ch == '\n')
252                 result.append('n');
253             else if (ch == '\r')
254                 result.append('r');
255             else if (ch == 0x2028)
256                 result.append("u2028");
257             else
258                 result.append("u2029");
259         } else
260             result.append(ch);
261
262         if (previousCharacterWasBackslash)
263             previousCharacterWasBackslash = false;
264         else
265             previousCharacterWasBackslash = ch == '\\';
266     }
267
268     return jsString(exec, result.toUString());
269 }
270
271 void RegExpObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
272 {
273     if (propertyName == exec->propertyNames().lastIndex) {
274         asRegExpObject(cell)->setLastIndex(exec, value, slot.isStrictMode());
275         return;
276     }
277     lookupPut<RegExpObject, JSObject>(exec, propertyName, value, ExecState::regExpTable(exec), jsCast<RegExpObject*>(cell), slot);
278 }
279
280 JSValue RegExpObject::exec(ExecState* exec, JSString* string)
281 {
282     if (MatchResult result = match(exec, string))
283         return RegExpMatchesArray::create(exec, string, regExp(), result);
284     return jsNull();
285 }
286
287 // Shared implementation used by test and exec.
288 MatchResult RegExpObject::match(ExecState* exec, JSString* string)
289 {
290     RegExp* regExp = this->regExp();
291     RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor();
292     UString input = string->value(exec);
293     JSGlobalData& globalData = exec->globalData();
294     if (!regExp->global())
295         return regExpConstructor->performMatch(globalData, regExp, string, input, 0);
296
297     JSValue jsLastIndex = getLastIndex();
298     unsigned lastIndex;
299     if (LIKELY(jsLastIndex.isUInt32())) {
300         lastIndex = jsLastIndex.asUInt32();
301         if (lastIndex > input.length()) {
302             setLastIndex(exec, 0);
303             return MatchResult::failed();
304         }
305     } else {
306         double doubleLastIndex = jsLastIndex.toInteger(exec);
307         if (doubleLastIndex < 0 || doubleLastIndex > input.length()) {
308             setLastIndex(exec, 0);
309             return MatchResult::failed();
310         }
311         lastIndex = static_cast<unsigned>(doubleLastIndex);
312     }
313
314     MatchResult result = regExpConstructor->performMatch(globalData, regExp, string, input, lastIndex);
315     setLastIndex(exec, result.end);
316     return result;
317 }
318
319 } // namespace JSC