]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 1999-2002 Harri Porten (porten@kde.org) | |
3 | * Copyright (C) 2001 Peter Kelly (pmk@post.com) | |
4 | * Copyright (C) 2004, 2007, 2008 Apple Inc. All rights reserved. | |
5 | * | |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Library General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2 of the License, or (at your option) any later version. | |
10 | * | |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Library General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Library General Public License | |
17 | * along with this library; see the file COPYING.LIB. If not, write to | |
18 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
19 | * Boston, MA 02110-1301, USA. | |
20 | * | |
21 | */ | |
22 | ||
23 | #include "config.h" | |
24 | #include "JSString.h" | |
25 | ||
26 | #include "JSGlobalObject.h" | |
27 | #include "JSGlobalObjectFunctions.h" | |
28 | #include "JSObject.h" | |
29 | #include "Operations.h" | |
30 | #include "StringObject.h" | |
31 | #include "StringPrototype.h" | |
32 | ||
33 | namespace JSC { | |
34 | ||
35 | static const unsigned substringFromRopeCutoff = 4; | |
36 | ||
37 | const ClassInfo JSString::s_info = { "string", 0, 0, 0, CREATE_METHOD_TABLE(JSString) }; | |
38 | ||
39 | void JSRopeString::RopeBuilder::expand() | |
40 | { | |
41 | ASSERT(m_index == JSRopeString::s_maxInternalRopeLength); | |
42 | JSString* jsString = m_jsString; | |
43 | m_jsString = jsStringBuilder(&m_vm); | |
44 | m_index = 0; | |
45 | append(jsString); | |
46 | } | |
47 | ||
48 | void JSString::destroy(JSCell* cell) | |
49 | { | |
50 | JSString* thisObject = static_cast<JSString*>(cell); | |
51 | thisObject->JSString::~JSString(); | |
52 | } | |
53 | ||
54 | void JSString::visitChildren(JSCell* cell, SlotVisitor& visitor) | |
55 | { | |
56 | JSString* thisObject = jsCast<JSString*>(cell); | |
57 | Base::visitChildren(thisObject, visitor); | |
58 | ||
59 | MARK_LOG_MESSAGE1("[%u]: ", thisObject->length()); | |
60 | ||
61 | #if ENABLE(OBJECT_MARK_LOGGING) | |
62 | if (!thisObject->isRope()) { | |
63 | WTF::StringImpl* ourImpl = thisObject->m_value.impl(); | |
64 | if (ourImpl->is8Bit()) | |
65 | MARK_LOG_MESSAGE1("[8 %p]", ourImpl->characters8()); | |
66 | else | |
67 | MARK_LOG_MESSAGE1("[16 %p]", ourImpl->characters16()); | |
68 | } else | |
69 | MARK_LOG_MESSAGE0("[rope]: "); | |
70 | #endif | |
71 | ||
72 | if (thisObject->isRope()) | |
73 | static_cast<JSRopeString*>(thisObject)->visitFibers(visitor); | |
74 | } | |
75 | ||
76 | void JSRopeString::visitFibers(SlotVisitor& visitor) | |
77 | { | |
78 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) | |
79 | visitor.append(&m_fibers[i]); | |
80 | } | |
81 | ||
82 | void JSRopeString::resolveRope(ExecState* exec) const | |
83 | { | |
84 | ASSERT(isRope()); | |
85 | ||
86 | if (is8Bit()) { | |
87 | LChar* buffer; | |
88 | if (RefPtr<StringImpl> newImpl = StringImpl::tryCreateUninitialized(m_length, buffer)) { | |
89 | Heap::heap(this)->reportExtraMemoryCost(newImpl->cost()); | |
90 | m_value = newImpl.release(); | |
91 | } else { | |
92 | outOfMemory(exec); | |
93 | return; | |
94 | } | |
95 | ||
96 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
97 | if (m_fibers[i]->isRope()) | |
98 | return resolveRopeSlowCase8(buffer); | |
99 | } | |
100 | ||
101 | LChar* position = buffer; | |
102 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
103 | StringImpl* string = m_fibers[i]->m_value.impl(); | |
104 | unsigned length = string->length(); | |
105 | StringImpl::copyChars(position, string->characters8(), length); | |
106 | position += length; | |
107 | m_fibers[i].clear(); | |
108 | } | |
109 | ASSERT((buffer + m_length) == position); | |
110 | ASSERT(!isRope()); | |
111 | ||
112 | return; | |
113 | } | |
114 | ||
115 | UChar* buffer; | |
116 | if (RefPtr<StringImpl> newImpl = StringImpl::tryCreateUninitialized(m_length, buffer)) { | |
117 | Heap::heap(this)->reportExtraMemoryCost(newImpl->cost()); | |
118 | m_value = newImpl.release(); | |
119 | } else { | |
120 | outOfMemory(exec); | |
121 | return; | |
122 | } | |
123 | ||
124 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
125 | if (m_fibers[i]->isRope()) | |
126 | return resolveRopeSlowCase(buffer); | |
127 | } | |
128 | ||
129 | UChar* position = buffer; | |
130 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
131 | StringImpl* string = m_fibers[i]->m_value.impl(); | |
132 | unsigned length = string->length(); | |
133 | if (string->is8Bit()) | |
134 | StringImpl::copyChars(position, string->characters8(), length); | |
135 | else | |
136 | StringImpl::copyChars(position, string->characters16(), length); | |
137 | position += length; | |
138 | m_fibers[i].clear(); | |
139 | } | |
140 | ASSERT((buffer + m_length) == position); | |
141 | ASSERT(!isRope()); | |
142 | } | |
143 | ||
144 | // Overview: These functions convert a JSString from holding a string in rope form | |
145 | // down to a simple String representation. It does so by building up the string | |
146 | // backwards, since we want to avoid recursion, we expect that the tree structure | |
147 | // representing the rope is likely imbalanced with more nodes down the left side | |
148 | // (since appending to the string is likely more common) - and as such resolving | |
149 | // in this fashion should minimize work queue size. (If we built the queue forwards | |
150 | // we would likely have to place all of the constituent StringImpls into the | |
151 | // Vector before performing any concatenation, but by working backwards we likely | |
152 | // only fill the queue with the number of substrings at any given level in a | |
153 | // rope-of-ropes.) | |
154 | void JSRopeString::resolveRopeSlowCase8(LChar* buffer) const | |
155 | { | |
156 | LChar* position = buffer + m_length; // We will be working backwards over the rope. | |
157 | Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // Putting strings into a Vector is only OK because there are no GC points in this method. | |
158 | ||
159 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
160 | workQueue.append(m_fibers[i].get()); | |
161 | // Clearing here works only because there are no GC points in this method. | |
162 | m_fibers[i].clear(); | |
163 | } | |
164 | ||
165 | while (!workQueue.isEmpty()) { | |
166 | JSString* currentFiber = workQueue.last(); | |
167 | workQueue.removeLast(); | |
168 | ||
169 | if (currentFiber->isRope()) { | |
170 | JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); | |
171 | for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->m_fibers[i]; ++i) | |
172 | workQueue.append(currentFiberAsRope->m_fibers[i].get()); | |
173 | continue; | |
174 | } | |
175 | ||
176 | StringImpl* string = static_cast<StringImpl*>(currentFiber->m_value.impl()); | |
177 | unsigned length = string->length(); | |
178 | position -= length; | |
179 | StringImpl::copyChars(position, string->characters8(), length); | |
180 | } | |
181 | ||
182 | ASSERT(buffer == position); | |
183 | ASSERT(!isRope()); | |
184 | } | |
185 | ||
186 | void JSRopeString::resolveRopeSlowCase(UChar* buffer) const | |
187 | { | |
188 | UChar* position = buffer + m_length; // We will be working backwards over the rope. | |
189 | Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // These strings are kept alive by the parent rope, so using a Vector is OK. | |
190 | ||
191 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) | |
192 | workQueue.append(m_fibers[i].get()); | |
193 | ||
194 | while (!workQueue.isEmpty()) { | |
195 | JSString* currentFiber = workQueue.last(); | |
196 | workQueue.removeLast(); | |
197 | ||
198 | if (currentFiber->isRope()) { | |
199 | JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); | |
200 | for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->m_fibers[i]; ++i) | |
201 | workQueue.append(currentFiberAsRope->m_fibers[i].get()); | |
202 | continue; | |
203 | } | |
204 | ||
205 | StringImpl* string = static_cast<StringImpl*>(currentFiber->m_value.impl()); | |
206 | unsigned length = string->length(); | |
207 | position -= length; | |
208 | if (string->is8Bit()) | |
209 | StringImpl::copyChars(position, string->characters8(), length); | |
210 | else | |
211 | StringImpl::copyChars(position, string->characters16(), length); | |
212 | } | |
213 | ||
214 | ASSERT(buffer == position); | |
215 | ASSERT(!isRope()); | |
216 | } | |
217 | ||
218 | void JSRopeString::outOfMemory(ExecState* exec) const | |
219 | { | |
220 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) | |
221 | m_fibers[i].clear(); | |
222 | ASSERT(isRope()); | |
223 | ASSERT(m_value.isNull()); | |
224 | if (exec) | |
225 | throwOutOfMemoryError(exec); | |
226 | } | |
227 | ||
228 | JSString* JSRopeString::getIndexSlowCase(ExecState* exec, unsigned i) | |
229 | { | |
230 | ASSERT(isRope()); | |
231 | resolveRope(exec); | |
232 | // Return a safe no-value result, this should never be used, since the excetion will be thrown. | |
233 | if (exec->exception()) | |
234 | return jsEmptyString(exec); | |
235 | ASSERT(!isRope()); | |
236 | RELEASE_ASSERT(i < m_value.length()); | |
237 | return jsSingleCharacterSubstring(exec, m_value, i); | |
238 | } | |
239 | ||
240 | JSValue JSString::toPrimitive(ExecState*, PreferredPrimitiveType) const | |
241 | { | |
242 | return const_cast<JSString*>(this); | |
243 | } | |
244 | ||
245 | bool JSString::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const | |
246 | { | |
247 | result = this; | |
248 | number = jsToNumber(value(exec)); | |
249 | return false; | |
250 | } | |
251 | ||
252 | bool JSString::toBoolean() const | |
253 | { | |
254 | return m_length; | |
255 | } | |
256 | ||
257 | double JSString::toNumber(ExecState* exec) const | |
258 | { | |
259 | return jsToNumber(value(exec)); | |
260 | } | |
261 | ||
262 | inline StringObject* StringObject::create(ExecState* exec, JSGlobalObject* globalObject, JSString* string) | |
263 | { | |
264 | StringObject* object = new (NotNull, allocateCell<StringObject>(*exec->heap())) StringObject(exec->vm(), globalObject->stringObjectStructure()); | |
265 | object->finishCreation(exec->vm(), string); | |
266 | return object; | |
267 | } | |
268 | ||
269 | JSObject* JSString::toObject(ExecState* exec, JSGlobalObject* globalObject) const | |
270 | { | |
271 | return StringObject::create(exec, globalObject, const_cast<JSString*>(this)); | |
272 | } | |
273 | ||
274 | JSObject* JSString::toThisObject(JSCell* cell, ExecState* exec) | |
275 | { | |
276 | return StringObject::create(exec, exec->lexicalGlobalObject(), jsCast<JSString*>(cell)); | |
277 | } | |
278 | ||
279 | bool JSString::getOwnPropertySlot(JSCell* cell, ExecState* exec, PropertyName propertyName, PropertySlot& slot) | |
280 | { | |
281 | JSString* thisObject = jsCast<JSString*>(cell); | |
282 | // The semantics here are really getPropertySlot, not getOwnPropertySlot. | |
283 | // This function should only be called by JSValue::get. | |
284 | if (thisObject->getStringPropertySlot(exec, propertyName, slot)) | |
285 | return true; | |
286 | slot.setBase(thisObject); | |
287 | JSObject* object; | |
288 | for (JSValue prototype = exec->lexicalGlobalObject()->stringPrototype(); !prototype.isNull(); prototype = object->prototype()) { | |
289 | object = asObject(prototype); | |
290 | if (object->methodTable()->getOwnPropertySlot(object, exec, propertyName, slot)) | |
291 | return true; | |
292 | } | |
293 | slot.setUndefined(); | |
294 | return true; | |
295 | } | |
296 | ||
297 | bool JSString::getStringPropertyDescriptor(ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) | |
298 | { | |
299 | if (propertyName == exec->propertyNames().length) { | |
300 | descriptor.setDescriptor(jsNumber(m_length), DontEnum | DontDelete | ReadOnly); | |
301 | return true; | |
302 | } | |
303 | ||
304 | unsigned i = propertyName.asIndex(); | |
305 | if (i < m_length) { | |
306 | ASSERT(i != PropertyName::NotAnIndex); // No need for an explicit check, the above test would always fail! | |
307 | descriptor.setDescriptor(getIndex(exec, i), DontDelete | ReadOnly); | |
308 | return true; | |
309 | } | |
310 | ||
311 | return false; | |
312 | } | |
313 | ||
314 | bool JSString::getOwnPropertySlotByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, PropertySlot& slot) | |
315 | { | |
316 | JSString* thisObject = jsCast<JSString*>(cell); | |
317 | // The semantics here are really getPropertySlot, not getOwnPropertySlot. | |
318 | // This function should only be called by JSValue::get. | |
319 | if (thisObject->getStringPropertySlot(exec, propertyName, slot)) | |
320 | return true; | |
321 | return JSString::getOwnPropertySlot(thisObject, exec, Identifier::from(exec, propertyName), slot); | |
322 | } | |
323 | ||
324 | } // namespace JSC |