]>
Commit | Line | Data |
---|---|---|
9dae56ea A |
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" | |
14957cd0 | 27 | #include "JSGlobalObjectFunctions.h" |
9dae56ea | 28 | #include "JSObject.h" |
81345200 | 29 | #include "JSCInlines.h" |
9dae56ea A |
30 | #include "StringObject.h" |
31 | #include "StringPrototype.h" | |
81345200 | 32 | #include "StrongInlines.h" |
9dae56ea A |
33 | |
34 | namespace JSC { | |
b80e6193 | 35 | |
6fe7ccc8 | 36 | const ClassInfo JSString::s_info = { "string", 0, 0, 0, CREATE_METHOD_TABLE(JSString) }; |
14957cd0 | 37 | |
6fe7ccc8 A |
38 | void JSRopeString::RopeBuilder::expand() |
39 | { | |
40 | ASSERT(m_index == JSRopeString::s_maxInternalRopeLength); | |
41 | JSString* jsString = m_jsString; | |
4be4e309 | 42 | RELEASE_ASSERT(jsString); |
93a37866 | 43 | m_jsString = jsStringBuilder(&m_vm); |
6fe7ccc8 A |
44 | m_index = 0; |
45 | append(jsString); | |
46 | } | |
47 | ||
48 | void JSString::destroy(JSCell* cell) | |
49 | { | |
93a37866 | 50 | JSString* thisObject = static_cast<JSString*>(cell); |
6fe7ccc8 A |
51 | thisObject->JSString::~JSString(); |
52 | } | |
53 | ||
81345200 | 54 | void JSString::dumpToStream(const JSCell* cell, PrintStream& out) |
6fe7ccc8 | 55 | { |
81345200 A |
56 | const JSString* thisObject = jsCast<const JSString*>(cell); |
57 | out.printf("<%p, %s, [%u], ", thisObject, thisObject->className(), thisObject->length()); | |
58 | if (thisObject->isRope()) | |
59 | out.printf("[rope]"); | |
60 | else { | |
93a37866 A |
61 | WTF::StringImpl* ourImpl = thisObject->m_value.impl(); |
62 | if (ourImpl->is8Bit()) | |
81345200 | 63 | out.printf("[8 %p]", ourImpl->characters8()); |
93a37866 | 64 | else |
81345200 A |
65 | out.printf("[16 %p]", ourImpl->characters16()); |
66 | } | |
67 | out.printf(">"); | |
68 | } | |
93a37866 | 69 | |
81345200 A |
70 | void JSString::visitChildren(JSCell* cell, SlotVisitor& visitor) |
71 | { | |
72 | JSString* thisObject = jsCast<JSString*>(cell); | |
73 | Base::visitChildren(thisObject, visitor); | |
74 | ||
6fe7ccc8 A |
75 | if (thisObject->isRope()) |
76 | static_cast<JSRopeString*>(thisObject)->visitFibers(visitor); | |
81345200 A |
77 | else { |
78 | StringImpl* impl = thisObject->m_value.impl(); | |
79 | ASSERT(impl); | |
80 | visitor.reportExtraMemoryUsage(thisObject, impl->costDuringGC()); | |
81 | } | |
6fe7ccc8 A |
82 | } |
83 | ||
84 | void JSRopeString::visitFibers(SlotVisitor& visitor) | |
85 | { | |
86 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) | |
87 | visitor.append(&m_fibers[i]); | |
88 | } | |
89 | ||
81345200 A |
90 | static const unsigned maxLengthForOnStackResolve = 2048; |
91 | ||
92 | void JSRopeString::resolveRopeInternal8(LChar* buffer) const | |
93 | { | |
94 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
95 | if (m_fibers[i]->isRope()) { | |
96 | resolveRopeSlowCase8(buffer); | |
97 | return; | |
98 | } | |
99 | } | |
100 | ||
101 | LChar* position = buffer; | |
102 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
103 | const StringImpl& fiberString = *m_fibers[i]->m_value.impl(); | |
104 | unsigned length = fiberString.length(); | |
105 | StringImpl::copyChars(position, fiberString.characters8(), length); | |
106 | position += length; | |
107 | } | |
108 | ASSERT((buffer + m_length) == position); | |
109 | } | |
110 | ||
111 | void JSRopeString::resolveRopeInternal16(UChar* buffer) const | |
112 | { | |
113 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
114 | if (m_fibers[i]->isRope()) { | |
115 | resolveRopeSlowCase(buffer); | |
116 | return; | |
117 | } | |
118 | } | |
119 | ||
120 | UChar* position = buffer; | |
121 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) { | |
122 | const StringImpl& fiberString = *m_fibers[i]->m_value.impl(); | |
123 | unsigned length = fiberString.length(); | |
124 | if (fiberString.is8Bit()) | |
125 | StringImpl::copyChars(position, fiberString.characters8(), length); | |
126 | else | |
127 | StringImpl::copyChars(position, fiberString.characters16(), length); | |
128 | position += length; | |
129 | } | |
130 | ASSERT((buffer + m_length) == position); | |
131 | } | |
132 | ||
133 | void JSRopeString::resolveRopeToAtomicString(ExecState* exec) const | |
134 | { | |
135 | if (m_length > maxLengthForOnStackResolve) { | |
136 | resolveRope(exec); | |
137 | m_value = AtomicString(m_value); | |
138 | return; | |
139 | } | |
140 | ||
141 | if (is8Bit()) { | |
142 | LChar buffer[maxLengthForOnStackResolve]; | |
143 | resolveRopeInternal8(buffer); | |
144 | m_value = AtomicString(buffer, m_length); | |
145 | } else { | |
146 | UChar buffer[maxLengthForOnStackResolve]; | |
147 | resolveRopeInternal16(buffer); | |
148 | m_value = AtomicString(buffer, m_length); | |
149 | } | |
150 | ||
151 | clearFibers(); | |
152 | ||
153 | // If we resolved a string that didn't previously exist, notify the heap that we've grown. | |
154 | if (m_value.impl()->hasOneRef()) | |
155 | Heap::heap(this)->reportExtraMemoryCost(m_value.impl()->cost()); | |
156 | } | |
157 | ||
158 | void JSRopeString::clearFibers() const | |
159 | { | |
160 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) | |
161 | m_fibers[i].clear(); | |
162 | } | |
163 | ||
164 | AtomicStringImpl* JSRopeString::resolveRopeToExistingAtomicString(ExecState* exec) const | |
165 | { | |
166 | if (m_length > maxLengthForOnStackResolve) { | |
167 | resolveRope(exec); | |
168 | if (AtomicStringImpl* existingAtomicString = AtomicString::find(m_value.impl())) { | |
169 | m_value = *existingAtomicString; | |
170 | clearFibers(); | |
171 | return existingAtomicString; | |
172 | } | |
173 | return nullptr; | |
174 | } | |
175 | ||
176 | if (is8Bit()) { | |
177 | LChar buffer[maxLengthForOnStackResolve]; | |
178 | resolveRopeInternal8(buffer); | |
179 | if (AtomicStringImpl* existingAtomicString = AtomicString::find(buffer, m_length)) { | |
180 | m_value = *existingAtomicString; | |
181 | clearFibers(); | |
182 | return existingAtomicString; | |
183 | } | |
184 | } else { | |
185 | UChar buffer[maxLengthForOnStackResolve]; | |
186 | resolveRopeInternal16(buffer); | |
187 | if (AtomicStringImpl* existingAtomicString = AtomicString::find(buffer, m_length)) { | |
188 | m_value = *existingAtomicString; | |
189 | clearFibers(); | |
190 | return existingAtomicString; | |
191 | } | |
192 | } | |
193 | ||
194 | return nullptr; | |
195 | } | |
196 | ||
6fe7ccc8 | 197 | void JSRopeString::resolveRope(ExecState* exec) const |
14957cd0 A |
198 | { |
199 | ASSERT(isRope()); | |
200 | ||
6fe7ccc8 A |
201 | if (is8Bit()) { |
202 | LChar* buffer; | |
203 | if (RefPtr<StringImpl> newImpl = StringImpl::tryCreateUninitialized(m_length, buffer)) { | |
204 | Heap::heap(this)->reportExtraMemoryCost(newImpl->cost()); | |
205 | m_value = newImpl.release(); | |
206 | } else { | |
207 | outOfMemory(exec); | |
208 | return; | |
209 | } | |
81345200 A |
210 | resolveRopeInternal8(buffer); |
211 | clearFibers(); | |
6fe7ccc8 | 212 | ASSERT(!isRope()); |
6fe7ccc8 A |
213 | return; |
214 | } | |
215 | ||
14957cd0 | 216 | UChar* buffer; |
6fe7ccc8 A |
217 | if (RefPtr<StringImpl> newImpl = StringImpl::tryCreateUninitialized(m_length, buffer)) { |
218 | Heap::heap(this)->reportExtraMemoryCost(newImpl->cost()); | |
219 | m_value = newImpl.release(); | |
220 | } else { | |
14957cd0 A |
221 | outOfMemory(exec); |
222 | return; | |
223 | } | |
224 | ||
81345200 A |
225 | resolveRopeInternal16(buffer); |
226 | clearFibers(); | |
14957cd0 A |
227 | ASSERT(!isRope()); |
228 | } | |
9dae56ea | 229 | |
6fe7ccc8 | 230 | // Overview: These functions convert a JSString from holding a string in rope form |
93a37866 | 231 | // down to a simple String representation. It does so by building up the string |
f9bf01c6 A |
232 | // backwards, since we want to avoid recursion, we expect that the tree structure |
233 | // representing the rope is likely imbalanced with more nodes down the left side | |
234 | // (since appending to the string is likely more common) - and as such resolving | |
235 | // in this fashion should minimize work queue size. (If we built the queue forwards | |
14957cd0 | 236 | // we would likely have to place all of the constituent StringImpls into the |
f9bf01c6 A |
237 | // Vector before performing any concatenation, but by working backwards we likely |
238 | // only fill the queue with the number of substrings at any given level in a | |
14957cd0 | 239 | // rope-of-ropes.) |
6fe7ccc8 | 240 | void JSRopeString::resolveRopeSlowCase8(LChar* buffer) const |
f9bf01c6 | 241 | { |
6fe7ccc8 | 242 | LChar* position = buffer + m_length; // We will be working backwards over the rope. |
93a37866 | 243 | Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // Putting strings into a Vector is only OK because there are no GC points in this method. |
6fe7ccc8 | 244 | |
81345200 | 245 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) |
6fe7ccc8 | 246 | workQueue.append(m_fibers[i].get()); |
14957cd0 | 247 | |
6fe7ccc8 A |
248 | while (!workQueue.isEmpty()) { |
249 | JSString* currentFiber = workQueue.last(); | |
250 | workQueue.removeLast(); | |
b80e6193 | 251 | |
6fe7ccc8 A |
252 | if (currentFiber->isRope()) { |
253 | JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); | |
254 | for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->m_fibers[i]; ++i) | |
255 | workQueue.append(currentFiberAsRope->m_fibers[i].get()); | |
b80e6193 | 256 | continue; |
b80e6193 | 257 | } |
b80e6193 | 258 | |
6fe7ccc8 A |
259 | StringImpl* string = static_cast<StringImpl*>(currentFiber->m_value.impl()); |
260 | unsigned length = string->length(); | |
261 | position -= length; | |
262 | StringImpl::copyChars(position, string->characters8(), length); | |
b80e6193 | 263 | } |
6fe7ccc8 A |
264 | |
265 | ASSERT(buffer == position); | |
b80e6193 | 266 | } |
f9bf01c6 | 267 | |
6fe7ccc8 | 268 | void JSRopeString::resolveRopeSlowCase(UChar* buffer) const |
4e4e5a6f | 269 | { |
6fe7ccc8 | 270 | UChar* position = buffer + m_length; // We will be working backwards over the rope. |
93a37866 | 271 | Vector<JSString*, 32, UnsafeVectorOverflow> workQueue; // These strings are kept alive by the parent rope, so using a Vector is OK. |
4e4e5a6f | 272 | |
6fe7ccc8 A |
273 | for (size_t i = 0; i < s_maxInternalRopeLength && m_fibers[i]; ++i) |
274 | workQueue.append(m_fibers[i].get()); | |
4e4e5a6f | 275 | |
6fe7ccc8 A |
276 | while (!workQueue.isEmpty()) { |
277 | JSString* currentFiber = workQueue.last(); | |
278 | workQueue.removeLast(); | |
4e4e5a6f | 279 | |
6fe7ccc8 A |
280 | if (currentFiber->isRope()) { |
281 | JSRopeString* currentFiberAsRope = static_cast<JSRopeString*>(currentFiber); | |
282 | for (size_t i = 0; i < s_maxInternalRopeLength && currentFiberAsRope->m_fibers[i]; ++i) | |
283 | workQueue.append(currentFiberAsRope->m_fibers[i].get()); | |
4e4e5a6f A |
284 | continue; |
285 | } | |
286 | ||
6fe7ccc8 A |
287 | StringImpl* string = static_cast<StringImpl*>(currentFiber->m_value.impl()); |
288 | unsigned length = string->length(); | |
289 | position -= length; | |
93a37866 A |
290 | if (string->is8Bit()) |
291 | StringImpl::copyChars(position, string->characters8(), length); | |
292 | else | |
293 | StringImpl::copyChars(position, string->characters16(), length); | |
4e4e5a6f A |
294 | } |
295 | ||
6fe7ccc8 | 296 | ASSERT(buffer == position); |
6fe7ccc8 A |
297 | } |
298 | ||
299 | void JSRopeString::outOfMemory(ExecState* exec) const | |
300 | { | |
81345200 | 301 | clearFibers(); |
6fe7ccc8 | 302 | ASSERT(isRope()); |
93a37866 | 303 | ASSERT(m_value.isNull()); |
6fe7ccc8 A |
304 | if (exec) |
305 | throwOutOfMemoryError(exec); | |
4e4e5a6f A |
306 | } |
307 | ||
6fe7ccc8 | 308 | JSString* JSRopeString::getIndexSlowCase(ExecState* exec, unsigned i) |
4e4e5a6f A |
309 | { |
310 | ASSERT(isRope()); | |
311 | resolveRope(exec); | |
312 | // Return a safe no-value result, this should never be used, since the excetion will be thrown. | |
313 | if (exec->exception()) | |
93a37866 | 314 | return jsEmptyString(exec); |
4e4e5a6f | 315 | ASSERT(!isRope()); |
93a37866 | 316 | RELEASE_ASSERT(i < m_value.length()); |
4e4e5a6f A |
317 | return jsSingleCharacterSubstring(exec, m_value, i); |
318 | } | |
319 | ||
ba379fdc | 320 | JSValue JSString::toPrimitive(ExecState*, PreferredPrimitiveType) const |
9dae56ea A |
321 | { |
322 | return const_cast<JSString*>(this); | |
323 | } | |
324 | ||
6fe7ccc8 | 325 | bool JSString::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const |
9dae56ea | 326 | { |
f9bf01c6 | 327 | result = this; |
14957cd0 | 328 | number = jsToNumber(value(exec)); |
9dae56ea A |
329 | return false; |
330 | } | |
331 | ||
93a37866 | 332 | bool JSString::toBoolean() const |
9dae56ea | 333 | { |
4e4e5a6f | 334 | return m_length; |
9dae56ea A |
335 | } |
336 | ||
f9bf01c6 | 337 | double JSString::toNumber(ExecState* exec) const |
9dae56ea | 338 | { |
14957cd0 | 339 | return jsToNumber(value(exec)); |
9dae56ea A |
340 | } |
341 | ||
81345200 | 342 | inline StringObject* StringObject::create(VM& vm, JSGlobalObject* globalObject, JSString* string) |
9dae56ea | 343 | { |
81345200 A |
344 | StringObject* object = new (NotNull, allocateCell<StringObject>(vm.heap)) StringObject(vm, globalObject->stringObjectStructure()); |
345 | object->finishCreation(vm, string); | |
6fe7ccc8 | 346 | return object; |
9dae56ea A |
347 | } |
348 | ||
14957cd0 | 349 | JSObject* JSString::toObject(ExecState* exec, JSGlobalObject* globalObject) const |
9dae56ea | 350 | { |
81345200 | 351 | return StringObject::create(exec->vm(), globalObject, const_cast<JSString*>(this)); |
9dae56ea A |
352 | } |
353 | ||
81345200 | 354 | JSValue JSString::toThis(JSCell* cell, ExecState* exec, ECMAMode ecmaMode) |
9dae56ea | 355 | { |
81345200 A |
356 | if (ecmaMode == StrictMode) |
357 | return cell; | |
358 | return StringObject::create(exec->vm(), exec->lexicalGlobalObject(), jsCast<JSString*>(cell)); | |
9dae56ea A |
359 | } |
360 | ||
93a37866 | 361 | bool JSString::getStringPropertyDescriptor(ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) |
9dae56ea | 362 | { |
f9bf01c6 | 363 | if (propertyName == exec->propertyNames().length) { |
14957cd0 | 364 | descriptor.setDescriptor(jsNumber(m_length), DontEnum | DontDelete | ReadOnly); |
9dae56ea | 365 | return true; |
9dae56ea | 366 | } |
9dae56ea | 367 | |
93a37866 A |
368 | unsigned i = propertyName.asIndex(); |
369 | if (i < m_length) { | |
370 | ASSERT(i != PropertyName::NotAnIndex); // No need for an explicit check, the above test would always fail! | |
4e4e5a6f | 371 | descriptor.setDescriptor(getIndex(exec, i), DontDelete | ReadOnly); |
f9bf01c6 | 372 | return true; |
9dae56ea | 373 | } |
f9bf01c6 A |
374 | |
375 | return false; | |
9dae56ea A |
376 | } |
377 | ||
81345200 | 378 | JSString* jsStringWithCacheSlowCase(VM& vm, StringImpl& stringImpl) |
f9bf01c6 | 379 | { |
81345200 A |
380 | auto addResult = vm.stringCache.add(&stringImpl, nullptr); |
381 | if (addResult.isNewEntry) | |
382 | addResult.iterator->value = jsString(&vm, String(stringImpl)); | |
383 | vm.lastCachedString.set(vm, addResult.iterator->value.get()); | |
384 | return addResult.iterator->value.get(); | |
9dae56ea A |
385 | } |
386 | ||
387 | } // namespace JSC |