1 | /* |
2 | * Copyright (C) 1999-2001 Harri Porten ([email protected]) |
3 | * Copyright (C) 2001 Peter Kelly ([email protected]) |
4 | * Copyright (C) 2003-2019 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 | #pragma once |
24 | |
25 | #include "ArgList.h" |
26 | #include "CallFrame.h" |
27 | #include "CommonIdentifiers.h" |
28 | #include "GetVM.h" |
29 | #include "Identifier.h" |
30 | #include "PropertyDescriptor.h" |
31 | #include "PropertySlot.h" |
32 | #include "Structure.h" |
33 | #include "ThrowScope.h" |
34 | #include <array> |
35 | #include <wtf/CheckedArithmetic.h> |
36 | #include <wtf/ForbidHeapAllocation.h> |
37 | #include <wtf/text/StringView.h> |
38 | |
39 | namespace JSC { |
40 | |
41 | class JSString; |
42 | class JSRopeString; |
43 | class ; |
44 | |
45 | JSString* jsEmptyString(VM&); |
46 | JSString* jsString(VM&, const String&); // returns empty string if passed null string |
47 | |
48 | JSString* jsSingleCharacterString(VM&, UChar); |
49 | JSString* jsSubstring(VM&, const String&, unsigned offset, unsigned length); |
50 | |
51 | // Non-trivial strings are two or more characters long. |
52 | // These functions are faster than just calling jsString. |
53 | JSString* jsNontrivialString(VM&, const String&); |
54 | JSString* jsNontrivialString(VM&, String&&); |
55 | |
56 | // Should be used for strings that are owned by an object that will |
57 | // likely outlive the JSValue this makes, such as the parse tree or a |
58 | // DOM object that contains a String |
59 | JSString* jsOwnedString(VM&, const String&); |
60 | |
61 | bool isJSString(JSCell*); |
62 | bool isJSString(JSValue); |
63 | JSString* asString(JSValue); |
64 | |
65 | // In 64bit architecture, JSString and JSRopeString have the following memory layout to make sizeof(JSString) == 16 and sizeof(JSRopeString) == 32. |
66 | // JSString has only one pointer. We use it for String. length() and is8Bit() queries go to StringImpl. In JSRopeString, we reuse the above pointer |
67 | // place for the 1st fiber. JSRopeString has three fibers so its size is 48. To keep length and is8Bit flag information in JSRopeString, JSRopeString |
68 | // encodes these information into the fiber pointers. is8Bit flag is encoded in the 1st fiber pointer. length is embedded directly, and two fibers |
69 | // are compressed into 12bytes. isRope information is encoded in the first fiber's LSB. |
70 | // |
71 | // Since length of JSRopeString should be frequently accessed compared to each fiber, we put length in contiguous 32byte field, and compress 2nd |
72 | // and 3rd fibers into the following 80byte fields. One problem is that now 2nd and 3rd fibers are split. Storing and loading 2nd and 3rd fibers |
73 | // are not one pointer load operation. To make concurrent collector work correctly, we must initialize 2nd and 3rd fibers at JSRopeString creation |
74 | // and we must not modify these part later. |
75 | // |
76 | // 0 8 10 16 32 48 |
77 | // JSString [ ID ][ header ][ String pointer 0] |
78 | // JSRopeString [ ID ][ header ][ 1st fiber xyz][ length ][2nd lower32][2nd upper16][3rd lower16][3rd upper32] |
79 | // ^ |
80 | // x:(is8Bit),y:(isSubstring),z:(isRope) bit flags |
81 | class JSString : public JSCell { |
82 | public: |
83 | friend class JIT; |
84 | friend class VM; |
85 | friend class SpecializedThunkJIT; |
86 | friend class JSRopeString; |
87 | friend class MarkStack; |
88 | friend class SlotVisitor; |
89 | friend class SmallStrings; |
90 | |
91 | typedef JSCell Base; |
92 | static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero | StructureIsImmortal | OverridesToThis; |
93 | |
94 | static constexpr bool needsDestruction = true; |
95 | static void destroy(JSCell*); |
96 | |
97 | // We specialize the string subspace to get the fastest possible sweep. This wouldn't be |
98 | // necessary if JSString didn't have a destructor. |
99 | template<typename, SubspaceAccess> |
100 | static IsoSubspace* subspaceFor(VM& vm) |
101 | { |
102 | return &vm.stringSpace; |
103 | } |
104 | |
105 | // We employ overflow checks in many places with the assumption that MaxLength |
106 | // is INT_MAX. Hence, it cannot be changed into another length value without |
107 | // breaking all the bounds and overflow checks that assume this. |
108 | static constexpr unsigned MaxLength = std::numeric_limits<int32_t>::max(); |
109 | static_assert(MaxLength == String::MaxLength, "" ); |
110 | |
111 | static constexpr uintptr_t isRopeInPointer = 0x1; |
112 | |
113 | private: |
114 | String& uninitializedValueInternal() const |
115 | { |
116 | return *bitwise_cast<String*>(&m_fiber); |
117 | } |
118 | |
119 | String& valueInternal() const |
120 | { |
121 | ASSERT(!isRope()); |
122 | return uninitializedValueInternal(); |
123 | } |
124 | |
125 | JSString(VM& vm, Ref<StringImpl>&& value) |
126 | : JSCell(vm, vm.stringStructure.get()) |
127 | { |
128 | new (&uninitializedValueInternal()) String(WTFMove(value)); |
129 | } |
130 | |
131 | JSString(VM& vm) |
132 | : JSCell(vm, vm.stringStructure.get()) |
133 | , m_fiber(isRopeInPointer) |
134 | { |
135 | } |
136 | |
137 | void finishCreation(VM& vm, unsigned length) |
138 | { |
139 | ASSERT_UNUSED(length, length > 0); |
140 | ASSERT(!valueInternal().isNull()); |
141 | Base::finishCreation(vm); |
142 | } |
143 | |
144 | void finishCreation(VM& vm, unsigned length, size_t cost) |
145 | { |
146 | ASSERT_UNUSED(length, length > 0); |
147 | ASSERT(!valueInternal().isNull()); |
148 | Base::finishCreation(vm); |
149 | vm.heap.reportExtraMemoryAllocated(cost); |
150 | } |
151 | |
152 | static JSString* createEmptyString(VM&); |
153 | |
154 | static JSString* create(VM& vm, Ref<StringImpl>&& value) |
155 | { |
156 | unsigned length = value->length(); |
157 | ASSERT(length > 0); |
158 | size_t cost = value->cost(); |
159 | JSString* newString = new (NotNull, allocateCell<JSString>(vm.heap)) JSString(vm, WTFMove(value)); |
160 | newString->finishCreation(vm, length, cost); |
161 | return newString; |
162 | } |
163 | static JSString* createHasOtherOwner(VM& vm, Ref<StringImpl>&& value) |
164 | { |
165 | unsigned length = value->length(); |
166 | JSString* newString = new (NotNull, allocateCell<JSString>(vm.heap)) JSString(vm, WTFMove(value)); |
167 | newString->finishCreation(vm, length); |
168 | return newString; |
169 | } |
170 | |
171 | protected: |
172 | void finishCreation(VM& vm) |
173 | { |
174 | Base::finishCreation(vm); |
175 | } |
176 | |
177 | public: |
178 | ~JSString(); |
179 | |
180 | Identifier toIdentifier(JSGlobalObject*) const; |
181 | AtomString toAtomString(JSGlobalObject*) const; |
182 | RefPtr<AtomStringImpl> toExistingAtomString(JSGlobalObject*) const; |
183 | |
184 | StringViewWithUnderlyingString viewWithUnderlyingString(JSGlobalObject*) const; |
185 | |
186 | inline bool equal(JSGlobalObject*, JSString* other) const; |
187 | const String& value(JSGlobalObject*) const; |
188 | inline const String& tryGetValue(bool allocationAllowed = true) const; |
189 | const StringImpl* tryGetValueImpl() const; |
190 | ALWAYS_INLINE unsigned length() const; |
191 | |
192 | JSValue toPrimitive(JSGlobalObject*, PreferredPrimitiveType) const; |
193 | bool toBoolean() const { return !!length(); } |
194 | bool getPrimitiveNumber(JSGlobalObject*, double& number, JSValue&) const; |
195 | JSObject* toObject(JSGlobalObject*) const; |
196 | double toNumber(JSGlobalObject*) const; |
197 | |
198 | bool getStringPropertySlot(JSGlobalObject*, PropertyName, PropertySlot&); |
199 | bool getStringPropertySlot(JSGlobalObject*, unsigned propertyName, PropertySlot&); |
200 | bool getStringPropertyDescriptor(JSGlobalObject*, PropertyName, PropertyDescriptor&); |
201 | |
202 | bool canGetIndex(unsigned i) { return i < length(); } |
203 | JSString* getIndex(JSGlobalObject*, unsigned); |
204 | |
205 | static Structure* createStructure(VM&, JSGlobalObject*, JSValue); |
206 | |
207 | static ptrdiff_t offsetOfValue() { return OBJECT_OFFSETOF(JSString, m_fiber); } |
208 | |
209 | DECLARE_EXPORT_INFO; |
210 | |
211 | static void dumpToStream(const JSCell*, PrintStream&); |
212 | static size_t estimatedSize(JSCell*, VM&); |
213 | static void visitChildren(JSCell*, SlotVisitor&); |
214 | |
215 | ALWAYS_INLINE bool isRope() const |
216 | { |
217 | return m_fiber & isRopeInPointer; |
218 | } |
219 | |
220 | bool is8Bit() const; |
221 | |
222 | protected: |
223 | friend class JSValue; |
224 | |
225 | JS_EXPORT_PRIVATE bool equalSlowCase(JSGlobalObject*, JSString* other) const; |
226 | bool isSubstring() const; |
227 | |
228 | mutable uintptr_t m_fiber; |
229 | |
230 | private: |
231 | friend class LLIntOffsetsExtractor; |
232 | |
233 | static JSValue toThis(JSCell*, JSGlobalObject*, ECMAMode); |
234 | |
235 | StringView unsafeView(JSGlobalObject*) const; |
236 | |
237 | friend JSString* jsString(VM&, const String&); |
238 | friend JSString* jsString(JSGlobalObject*, JSString*, JSString*); |
239 | friend JSString* jsString(JSGlobalObject*, const String&, JSString*); |
240 | friend JSString* jsString(JSGlobalObject*, JSString*, const String&); |
241 | friend JSString* jsString(JSGlobalObject*, const String&, const String&); |
242 | friend JSString* jsString(JSGlobalObject*, JSString*, JSString*, JSString*); |
243 | friend JSString* jsString(JSGlobalObject*, const String&, const String&, const String&); |
244 | friend JSString* jsSingleCharacterString(VM&, UChar); |
245 | friend JSString* jsNontrivialString(VM&, const String&); |
246 | friend JSString* jsNontrivialString(VM&, String&&); |
247 | friend JSString* jsSubstring(VM&, const String&, unsigned, unsigned); |
248 | friend JSString* jsSubstring(VM&, JSGlobalObject*, JSString*, unsigned, unsigned); |
249 | friend JSString* jsSubstringOfResolved(VM&, GCDeferralContext*, JSString*, unsigned, unsigned); |
250 | friend JSString* jsOwnedString(VM&, const String&); |
251 | }; |
252 | |
253 | // NOTE: This class cannot override JSString's destructor. JSString's destructor is called directly |
254 | // from JSStringSubspace:: |
255 | class JSRopeString final : public JSString { |
256 | friend class JSString; |
257 | public: |
258 | template<typename, SubspaceAccess> |
259 | static IsoSubspace* subspaceFor(VM& vm) |
260 | { |
261 | return &vm.ropeStringSpace; |
262 | } |
263 | |
264 | // We use lower 3bits of fiber0 for flags. These bits are usable due to alignment, and it is OK even in 32bit architecture. |
265 | static constexpr uintptr_t is8BitInPointer = static_cast<uintptr_t>(StringImpl::flagIs8Bit()); |
266 | static constexpr uintptr_t isSubstringInPointer = 0x2; |
267 | static_assert(is8BitInPointer == 0b100, "" ); |
268 | static_assert(isSubstringInPointer == 0b010, "" ); |
269 | static_assert(isRopeInPointer == 0b001, "" ); |
270 | static constexpr uintptr_t stringMask = ~(isRopeInPointer | is8BitInPointer | isSubstringInPointer); |
271 | #if CPU(ADDRESS64) |
272 | static_assert(sizeof(uintptr_t) == sizeof(uint64_t), "" ); |
273 | class CompactFibers { |
274 | public: |
275 | static constexpr uintptr_t addressMask = (1ULL << WTF_CPU_EFFECTIVE_ADDRESS_WIDTH) - 1; |
276 | JSString* fiber1() const |
277 | { |
278 | #if CPU(LITTLE_ENDIAN) |
279 | return bitwise_cast<JSString*>(WTF::unalignedLoad<uintptr_t>(&m_fiber1Lower) & addressMask); |
280 | #else |
281 | return bitwise_cast<JSString*>(static_cast<uintptr_t>(m_fiber1Lower) | (static_cast<uintptr_t>(m_fiber1Upper) << 32)); |
282 | #endif |
283 | } |
284 | |
285 | void initializeFiber1(JSString* fiber) |
286 | { |
287 | uintptr_t pointer = bitwise_cast<uintptr_t>(fiber); |
288 | m_fiber1Lower = static_cast<uint32_t>(pointer); |
289 | m_fiber1Upper = static_cast<uint16_t>(pointer >> 32); |
290 | } |
291 | |
292 | JSString* fiber2() const |
293 | { |
294 | #if CPU(LITTLE_ENDIAN) |
295 | return bitwise_cast<JSString*>(WTF::unalignedLoad<uintptr_t>(&m_fiber1Upper) >> 16); |
296 | #else |
297 | return bitwise_cast<JSString*>(static_cast<uintptr_t>(m_fiber2Lower) | (static_cast<uintptr_t>(m_fiber2Upper) << 16)); |
298 | #endif |
299 | } |
300 | void initializeFiber2(JSString* fiber) |
301 | { |
302 | uintptr_t pointer = bitwise_cast<uintptr_t>(fiber); |
303 | m_fiber2Lower = static_cast<uint16_t>(pointer); |
304 | m_fiber2Upper = static_cast<uint32_t>(pointer >> 16); |
305 | } |
306 | |
307 | unsigned length() const { return m_length; } |
308 | void initializeLength(unsigned length) |
309 | { |
310 | m_length = length; |
311 | } |
312 | |
313 | static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(CompactFibers, m_length); } |
314 | static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(CompactFibers, m_length); } |
315 | static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1Upper); } |
316 | |
317 | private: |
318 | friend class LLIntOffsetsExtractor; |
319 | |
320 | uint32_t m_length { 0 }; |
321 | uint32_t m_fiber1Lower { 0 }; |
322 | uint16_t m_fiber1Upper { 0 }; |
323 | uint16_t m_fiber2Lower { 0 }; |
324 | uint32_t m_fiber2Upper { 0 }; |
325 | }; |
326 | static_assert(sizeof(CompactFibers) == sizeof(void*) * 2, "" ); |
327 | #else |
328 | class CompactFibers { |
329 | public: |
330 | JSString* fiber1() const |
331 | { |
332 | return m_fiber1; |
333 | } |
334 | void initializeFiber1(JSString* fiber) |
335 | { |
336 | m_fiber1 = fiber; |
337 | } |
338 | |
339 | JSString* fiber2() const |
340 | { |
341 | return m_fiber2; |
342 | } |
343 | void initializeFiber2(JSString* fiber) |
344 | { |
345 | m_fiber2 = fiber; |
346 | } |
347 | |
348 | unsigned length() const { return m_length; } |
349 | void initializeLength(unsigned length) |
350 | { |
351 | m_length = length; |
352 | } |
353 | |
354 | static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(CompactFibers, m_length); } |
355 | static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1); } |
356 | static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(CompactFibers, m_fiber2); } |
357 | |
358 | private: |
359 | friend class LLIntOffsetsExtractor; |
360 | |
361 | uint32_t m_length { 0 }; |
362 | JSString* m_fiber1 { nullptr }; |
363 | JSString* m_fiber2 { nullptr }; |
364 | }; |
365 | #endif |
366 | |
367 | template <class OverflowHandler = CrashOnOverflow> |
368 | class RopeBuilder : public OverflowHandler { |
369 | WTF_FORBID_HEAP_ALLOCATION; |
370 | public: |
371 | RopeBuilder(VM& vm) |
372 | : m_vm(vm) |
373 | { |
374 | } |
375 | |
376 | bool append(JSString* jsString) |
377 | { |
378 | if (UNLIKELY(this->hasOverflowed())) |
379 | return false; |
380 | if (!jsString->length()) |
381 | return true; |
382 | if (m_strings.size() == JSRopeString::s_maxInternalRopeLength) |
383 | expand(); |
384 | |
385 | static_assert(JSString::MaxLength == std::numeric_limits<int32_t>::max(), "" ); |
386 | auto sum = checkedSum<int32_t>(m_length, jsString->length()); |
387 | if (sum.hasOverflowed()) { |
388 | this->overflowed(); |
389 | return false; |
390 | } |
391 | ASSERT(static_cast<unsigned>(sum.unsafeGet()) <= MaxLength); |
392 | m_strings.append(jsString); |
393 | m_length = static_cast<unsigned>(sum.unsafeGet()); |
394 | return true; |
395 | } |
396 | |
397 | JSString* release() |
398 | { |
399 | RELEASE_ASSERT(!this->hasOverflowed()); |
400 | JSString* result = nullptr; |
401 | switch (m_strings.size()) { |
402 | case 0: { |
403 | ASSERT(!m_length); |
404 | result = jsEmptyString(m_vm); |
405 | break; |
406 | } |
407 | case 1: { |
408 | result = asString(m_strings.at(0)); |
409 | break; |
410 | } |
411 | case 2: { |
412 | result = JSRopeString::create(m_vm, asString(m_strings.at(0)), asString(m_strings.at(1))); |
413 | break; |
414 | } |
415 | case 3: { |
416 | result = JSRopeString::create(m_vm, asString(m_strings.at(0)), asString(m_strings.at(1)), asString(m_strings.at(2))); |
417 | break; |
418 | } |
419 | default: |
420 | ASSERT_NOT_REACHED(); |
421 | break; |
422 | } |
423 | ASSERT(result->length() == m_length); |
424 | m_strings.clear(); |
425 | m_length = 0; |
426 | return result; |
427 | } |
428 | |
429 | unsigned length() const |
430 | { |
431 | ASSERT(!this->hasOverflowed()); |
432 | return m_length; |
433 | } |
434 | |
435 | private: |
436 | void expand(); |
437 | |
438 | VM& m_vm; |
439 | MarkedArgumentBuffer m_strings; |
440 | unsigned m_length { 0 }; |
441 | }; |
442 | |
443 | inline unsigned length() const |
444 | { |
445 | return m_compactFibers.length(); |
446 | } |
447 | |
448 | private: |
449 | friend class LLIntOffsetsExtractor; |
450 | |
451 | void convertToNonRope(String&&) const; |
452 | |
453 | void initializeIs8Bit(bool flag) const |
454 | { |
455 | if (flag) |
456 | m_fiber |= is8BitInPointer; |
457 | else |
458 | m_fiber &= ~is8BitInPointer; |
459 | } |
460 | |
461 | void initializeIsSubstring(bool flag) const |
462 | { |
463 | if (flag) |
464 | m_fiber |= isSubstringInPointer; |
465 | else |
466 | m_fiber &= ~isSubstringInPointer; |
467 | } |
468 | |
469 | ALWAYS_INLINE void initializeLength(unsigned length) |
470 | { |
471 | ASSERT(length <= MaxLength); |
472 | m_compactFibers.initializeLength(length); |
473 | } |
474 | |
475 | JSRopeString(VM& vm) |
476 | : JSString(vm) |
477 | { |
478 | initializeIsSubstring(false); |
479 | initializeLength(0); |
480 | initializeIs8Bit(true); |
481 | initializeFiber0(nullptr); |
482 | initializeFiber1(nullptr); |
483 | initializeFiber2(nullptr); |
484 | } |
485 | |
486 | JSRopeString(VM& vm, JSString* s1, JSString* s2) |
487 | : JSString(vm) |
488 | { |
489 | ASSERT(!sumOverflows<int32_t>(s1->length(), s2->length())); |
490 | initializeIsSubstring(false); |
491 | initializeLength(s1->length() + s2->length()); |
492 | initializeIs8Bit(s1->is8Bit() && s2->is8Bit()); |
493 | initializeFiber0(s1); |
494 | initializeFiber1(s2); |
495 | initializeFiber2(nullptr); |
496 | ASSERT((s1->length() + s2->length()) == length()); |
497 | } |
498 | |
499 | JSRopeString(VM& vm, JSString* s1, JSString* s2, JSString* s3) |
500 | : JSString(vm) |
501 | { |
502 | ASSERT(!sumOverflows<int32_t>(s1->length(), s2->length(), s3->length())); |
503 | initializeIsSubstring(false); |
504 | initializeLength(s1->length() + s2->length() + s3->length()); |
505 | initializeIs8Bit(s1->is8Bit() && s2->is8Bit() && s3->is8Bit()); |
506 | initializeFiber0(s1); |
507 | initializeFiber1(s2); |
508 | initializeFiber2(s3); |
509 | ASSERT((s1->length() + s2->length() + s3->length()) == length()); |
510 | } |
511 | |
512 | JSRopeString(VM& vm, JSString* base, unsigned offset, unsigned length) |
513 | : JSString(vm) |
514 | { |
515 | RELEASE_ASSERT(!sumOverflows<int32_t>(offset, length)); |
516 | RELEASE_ASSERT(offset + length <= base->length()); |
517 | initializeIsSubstring(true); |
518 | initializeLength(length); |
519 | initializeIs8Bit(base->is8Bit()); |
520 | initializeSubstringBase(base); |
521 | initializeSubstringOffset(offset); |
522 | ASSERT(length == this->length()); |
523 | ASSERT(!base->isRope()); |
524 | } |
525 | |
526 | ALWAYS_INLINE void finishCreationSubstringOfResolved(VM& vm) |
527 | { |
528 | Base::finishCreation(vm); |
529 | } |
530 | |
531 | public: |
532 | static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfLength(); } // 32byte width. |
533 | static ptrdiff_t offsetOfFlags() { return offsetOfValue(); } |
534 | static ptrdiff_t offsetOfFiber0() { return offsetOfValue(); } |
535 | static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber1(); } |
536 | static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber2(); } |
537 | |
538 | static constexpr unsigned s_maxInternalRopeLength = 3; |
539 | |
540 | // This JSRopeString is only used to simulate half-baked JSRopeString in DFG and FTL MakeRope. If OSR exit happens in |
541 | // the middle of MakeRope due to string length overflow, we have half-baked JSRopeString which is the same to the result |
542 | // of this function. This half-baked JSRopeString will not be exposed to users, but still collectors can see it due to |
543 | // the conservative stack scan. This JSRopeString is used to test the collector with such a half-baked JSRopeString. |
544 | // Because this JSRopeString breaks the JSString's invariant (only one singleton JSString can be zero length), almost all the |
545 | // operations in JS fail to handle this string correctly. |
546 | static JSRopeString* createNullForTesting(VM& vm) |
547 | { |
548 | JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap)) JSRopeString(vm); |
549 | newString->finishCreation(vm); |
550 | ASSERT(!newString->length()); |
551 | ASSERT(newString->isRope()); |
552 | ASSERT(newString->fiber0() == nullptr); |
553 | return newString; |
554 | } |
555 | |
556 | private: |
557 | static JSRopeString* create(VM& vm, JSString* s1, JSString* s2) |
558 | { |
559 | JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap)) JSRopeString(vm, s1, s2); |
560 | newString->finishCreation(vm); |
561 | ASSERT(newString->length()); |
562 | ASSERT(newString->isRope()); |
563 | return newString; |
564 | } |
565 | static JSRopeString* create(VM& vm, JSString* s1, JSString* s2, JSString* s3) |
566 | { |
567 | JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap)) JSRopeString(vm, s1, s2, s3); |
568 | newString->finishCreation(vm); |
569 | ASSERT(newString->length()); |
570 | ASSERT(newString->isRope()); |
571 | return newString; |
572 | } |
573 | |
574 | ALWAYS_INLINE static JSRopeString* createSubstringOfResolved(VM& vm, GCDeferralContext* deferralContext, JSString* base, unsigned offset, unsigned length) |
575 | { |
576 | JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm.heap, deferralContext)) JSRopeString(vm, base, offset, length); |
577 | newString->finishCreationSubstringOfResolved(vm); |
578 | ASSERT(newString->length()); |
579 | ASSERT(newString->isRope()); |
580 | return newString; |
581 | } |
582 | |
583 | friend JSValue jsStringFromRegisterArray(JSGlobalObject*, Register*, unsigned); |
584 | |
585 | // If nullOrExecForOOM is null, resolveRope() will be do nothing in the event of an OOM error. |
586 | // The rope value will remain a null string in that case. |
587 | JS_EXPORT_PRIVATE const String& resolveRope(JSGlobalObject* nullOrGlobalObjectForOOM) const; |
588 | template<typename Function> const String& resolveRopeWithFunction(JSGlobalObject* nullOrGlobalObjectForOOM, Function&&) const; |
589 | JS_EXPORT_PRIVATE AtomString resolveRopeToAtomString(JSGlobalObject*) const; |
590 | JS_EXPORT_PRIVATE RefPtr<AtomStringImpl> resolveRopeToExistingAtomString(JSGlobalObject*) const; |
591 | void resolveRopeSlowCase8(LChar*) const; |
592 | void resolveRopeSlowCase(UChar*) const; |
593 | void outOfMemory(JSGlobalObject* nullOrGlobalObjectForOOM) const; |
594 | void resolveRopeInternal8(LChar*) const; |
595 | void resolveRopeInternal8NoSubstring(LChar*) const; |
596 | void resolveRopeInternal16(UChar*) const; |
597 | void resolveRopeInternal16NoSubstring(UChar*) const; |
598 | StringView unsafeView(JSGlobalObject*) const; |
599 | StringViewWithUnderlyingString viewWithUnderlyingString(JSGlobalObject*) const; |
600 | |
601 | JSString* fiber0() const |
602 | { |
603 | return bitwise_cast<JSString*>(m_fiber & stringMask); |
604 | } |
605 | |
606 | JSString* fiber1() const |
607 | { |
608 | return m_compactFibers.fiber1(); |
609 | } |
610 | |
611 | JSString* fiber2() const |
612 | { |
613 | return m_compactFibers.fiber2(); |
614 | } |
615 | |
616 | JSString* fiber(unsigned i) const |
617 | { |
618 | ASSERT(!isSubstring()); |
619 | ASSERT(i < s_maxInternalRopeLength); |
620 | switch (i) { |
621 | case 0: |
622 | return fiber0(); |
623 | case 1: |
624 | return fiber1(); |
625 | case 2: |
626 | return fiber2(); |
627 | } |
628 | ASSERT_NOT_REACHED(); |
629 | return nullptr; |
630 | } |
631 | |
632 | void initializeFiber0(JSString* fiber) |
633 | { |
634 | uintptr_t pointer = bitwise_cast<uintptr_t>(fiber); |
635 | ASSERT(!(pointer & ~stringMask)); |
636 | m_fiber = (pointer | (m_fiber & ~stringMask)); |
637 | } |
638 | |
639 | void initializeFiber1(JSString* fiber) |
640 | { |
641 | m_compactFibers.initializeFiber1(fiber); |
642 | } |
643 | |
644 | void initializeFiber2(JSString* fiber) |
645 | { |
646 | m_compactFibers.initializeFiber2(fiber); |
647 | } |
648 | |
649 | void initializeSubstringBase(JSString* fiber) |
650 | { |
651 | initializeFiber1(fiber); |
652 | } |
653 | |
654 | JSString* substringBase() const { return fiber1(); } |
655 | |
656 | void initializeSubstringOffset(unsigned offset) |
657 | { |
658 | m_compactFibers.initializeFiber2(bitwise_cast<JSString*>(static_cast<uintptr_t>(offset))); |
659 | } |
660 | |
661 | unsigned substringOffset() const |
662 | { |
663 | return static_cast<unsigned>(bitwise_cast<uintptr_t>(fiber2())); |
664 | } |
665 | |
666 | static_assert(s_maxInternalRopeLength >= 2, "" ); |
667 | mutable CompactFibers m_compactFibers; |
668 | |
669 | friend JSString* jsString(JSGlobalObject*, JSString*, JSString*); |
670 | friend JSString* jsString(JSGlobalObject*, const String&, JSString*); |
671 | friend JSString* jsString(JSGlobalObject*, JSString*, const String&); |
672 | friend JSString* jsString(JSGlobalObject*, const String&, const String&); |
673 | friend JSString* jsString(JSGlobalObject*, JSString*, JSString*, JSString*); |
674 | friend JSString* jsString(JSGlobalObject*, const String&, const String&, const String&); |
675 | friend JSString* jsSubstringOfResolved(VM&, GCDeferralContext*, JSString*, unsigned, unsigned); |
676 | friend JSString* jsSubstring(VM&, JSGlobalObject*, JSString*, unsigned, unsigned); |
677 | }; |
678 | |
679 | JS_EXPORT_PRIVATE JSString* jsStringWithCacheSlowCase(VM&, StringImpl&); |
680 | |
681 | // JSString::is8Bit is safe to be called concurrently. Concurrent threads can access is8Bit even if the main thread |
682 | // is in the middle of converting JSRopeString to JSString. |
683 | ALWAYS_INLINE bool JSString::is8Bit() const |
684 | { |
685 | uintptr_t pointer = m_fiber; |
686 | if (pointer & isRopeInPointer) { |
687 | // Do not load m_fiber twice. We should use the information in pointer. |
688 | // Otherwise, JSRopeString may be converted to JSString between the first and second accesses. |
689 | return pointer & JSRopeString::is8BitInPointer; |
690 | } |
691 | return bitwise_cast<StringImpl*>(pointer)->is8Bit(); |
692 | } |
693 | |
694 | // JSString::length is safe to be called concurrently. Concurrent threads can access length even if the main thread |
695 | // is in the middle of converting JSRopeString to JSString. This is OK because we never override the length bits |
696 | // when we resolve a JSRopeString. |
697 | ALWAYS_INLINE unsigned JSString::length() const |
698 | { |
699 | uintptr_t pointer = m_fiber; |
700 | if (pointer & isRopeInPointer) |
701 | return jsCast<const JSRopeString*>(this)->length(); |
702 | return bitwise_cast<StringImpl*>(pointer)->length(); |
703 | } |
704 | |
705 | inline const StringImpl* JSString::tryGetValueImpl() const |
706 | { |
707 | uintptr_t pointer = m_fiber; |
708 | if (pointer & isRopeInPointer) |
709 | return nullptr; |
710 | return bitwise_cast<StringImpl*>(pointer); |
711 | } |
712 | |
713 | inline JSString* asString(JSValue value) |
714 | { |
715 | ASSERT(value.asCell()->isString()); |
716 | return jsCast<JSString*>(value.asCell()); |
717 | } |
718 | |
719 | // This MUST NOT GC. |
720 | inline JSString* jsEmptyString(VM& vm) |
721 | { |
722 | return vm.smallStrings.emptyString(); |
723 | } |
724 | |
725 | ALWAYS_INLINE JSString* jsSingleCharacterString(VM& vm, UChar c) |
726 | { |
727 | if (validateDFGDoesGC) |
728 | RELEASE_ASSERT(vm.heap.expectDoesGC()); |
729 | if (c <= maxSingleCharacterString) |
730 | return vm.smallStrings.singleCharacterString(c); |
731 | return JSString::create(vm, StringImpl::create(&c, 1)); |
732 | } |
733 | |
734 | inline JSString* jsNontrivialString(VM& vm, const String& s) |
735 | { |
736 | ASSERT(s.length() > 1); |
737 | return JSString::create(vm, *s.impl()); |
738 | } |
739 | |
740 | inline JSString* jsNontrivialString(VM& vm, String&& s) |
741 | { |
742 | ASSERT(s.length() > 1); |
743 | return JSString::create(vm, s.releaseImpl().releaseNonNull()); |
744 | } |
745 | |
746 | ALWAYS_INLINE Identifier JSString::toIdentifier(JSGlobalObject* globalObject) const |
747 | { |
748 | VM& vm = getVM(globalObject); |
749 | auto scope = DECLARE_THROW_SCOPE(vm); |
750 | AtomString atomString = toAtomString(globalObject); |
751 | RETURN_IF_EXCEPTION(scope, { }); |
752 | return Identifier::fromString(vm, atomString); |
753 | } |
754 | |
755 | ALWAYS_INLINE AtomString JSString::toAtomString(JSGlobalObject* globalObject) const |
756 | { |
757 | if (validateDFGDoesGC) |
758 | RELEASE_ASSERT(vm().heap.expectDoesGC()); |
759 | if (isRope()) |
760 | return static_cast<const JSRopeString*>(this)->resolveRopeToAtomString(globalObject); |
761 | return AtomString(valueInternal()); |
762 | } |
763 | |
764 | ALWAYS_INLINE RefPtr<AtomStringImpl> JSString::toExistingAtomString(JSGlobalObject* globalObject) const |
765 | { |
766 | if (validateDFGDoesGC) |
767 | RELEASE_ASSERT(vm().heap.expectDoesGC()); |
768 | if (isRope()) |
769 | return static_cast<const JSRopeString*>(this)->resolveRopeToExistingAtomString(globalObject); |
770 | if (valueInternal().impl()->isAtom()) |
771 | return static_cast<AtomStringImpl*>(valueInternal().impl()); |
772 | return AtomStringImpl::lookUp(valueInternal().impl()); |
773 | } |
774 | |
775 | inline const String& JSString::value(JSGlobalObject* globalObject) const |
776 | { |
777 | if (validateDFGDoesGC) |
778 | RELEASE_ASSERT(vm().heap.expectDoesGC()); |
779 | if (isRope()) |
780 | return static_cast<const JSRopeString*>(this)->resolveRope(globalObject); |
781 | return valueInternal(); |
782 | } |
783 | |
784 | inline const String& JSString::tryGetValue(bool allocationAllowed) const |
785 | { |
786 | if (allocationAllowed) { |
787 | if (validateDFGDoesGC) |
788 | RELEASE_ASSERT(vm().heap.expectDoesGC()); |
789 | if (isRope()) { |
790 | // Pass nullptr for the JSGlobalObject so that resolveRope does not throw in the event of an OOM error. |
791 | return static_cast<const JSRopeString*>(this)->resolveRope(nullptr); |
792 | } |
793 | } else |
794 | RELEASE_ASSERT(!isRope()); |
795 | return valueInternal(); |
796 | } |
797 | |
798 | inline JSString* JSString::getIndex(JSGlobalObject* globalObject, unsigned i) |
799 | { |
800 | VM& vm = getVM(globalObject); |
801 | auto scope = DECLARE_THROW_SCOPE(vm); |
802 | ASSERT(canGetIndex(i)); |
803 | StringView view = unsafeView(globalObject); |
804 | RETURN_IF_EXCEPTION(scope, nullptr); |
805 | return jsSingleCharacterString(vm, view[i]); |
806 | } |
807 | |
808 | inline JSString* jsString(VM& vm, const String& s) |
809 | { |
810 | int size = s.length(); |
811 | if (!size) |
812 | return vm.smallStrings.emptyString(); |
813 | if (size == 1) { |
814 | UChar c = s.characterAt(0); |
815 | if (c <= maxSingleCharacterString) |
816 | return vm.smallStrings.singleCharacterString(c); |
817 | } |
818 | return JSString::create(vm, *s.impl()); |
819 | } |
820 | |
821 | inline JSString* jsSubstring(VM& vm, JSGlobalObject* globalObject, JSString* base, unsigned offset, unsigned length) |
822 | { |
823 | auto scope = DECLARE_THROW_SCOPE(vm); |
824 | |
825 | ASSERT(offset <= base->length()); |
826 | ASSERT(length <= base->length()); |
827 | ASSERT(offset + length <= base->length()); |
828 | if (!length) |
829 | return vm.smallStrings.emptyString(); |
830 | if (!offset && length == base->length()) |
831 | return base; |
832 | |
833 | // For now, let's not allow substrings with a rope base. |
834 | // Resolve non-substring rope bases so we don't have to deal with it. |
835 | // FIXME: Evaluate if this would be worth adding more branches. |
836 | if (base->isSubstring()) { |
837 | JSRopeString* baseRope = jsCast<JSRopeString*>(base); |
838 | base = baseRope->substringBase(); |
839 | offset = baseRope->substringOffset() + offset; |
840 | ASSERT(!base->isRope()); |
841 | } else if (base->isRope()) { |
842 | jsCast<JSRopeString*>(base)->resolveRope(globalObject); |
843 | RETURN_IF_EXCEPTION(scope, nullptr); |
844 | } |
845 | return jsSubstringOfResolved(vm, nullptr, base, offset, length); |
846 | } |
847 | |
848 | inline JSString* jsSubstringOfResolved(VM& vm, GCDeferralContext* deferralContext, JSString* s, unsigned offset, unsigned length) |
849 | { |
850 | ASSERT(offset <= s->length()); |
851 | ASSERT(length <= s->length()); |
852 | ASSERT(offset + length <= s->length()); |
853 | ASSERT(!s->isRope()); |
854 | if (!length) |
855 | return vm.smallStrings.emptyString(); |
856 | if (!offset && length == s->length()) |
857 | return s; |
858 | if (length == 1) { |
859 | auto& base = s->valueInternal(); |
860 | UChar character = base.characterAt(offset); |
861 | if (character <= maxSingleCharacterString) |
862 | return vm.smallStrings.singleCharacterString(character); |
863 | } |
864 | return JSRopeString::createSubstringOfResolved(vm, deferralContext, s, offset, length); |
865 | } |
866 | |
867 | inline JSString* jsSubstringOfResolved(VM& vm, JSString* s, unsigned offset, unsigned length) |
868 | { |
869 | return jsSubstringOfResolved(vm, nullptr, s, offset, length); |
870 | } |
871 | |
872 | inline JSString* jsSubstring(JSGlobalObject* globalObject, JSString* s, unsigned offset, unsigned length) |
873 | { |
874 | return jsSubstring(getVM(globalObject), globalObject, s, offset, length); |
875 | } |
876 | |
877 | inline JSString* jsSubstring(VM& vm, const String& s, unsigned offset, unsigned length) |
878 | { |
879 | ASSERT(offset <= s.length()); |
880 | ASSERT(length <= s.length()); |
881 | ASSERT(offset + length <= s.length()); |
882 | if (!length) |
883 | return vm.smallStrings.emptyString(); |
884 | if (length == 1) { |
885 | UChar c = s.characterAt(offset); |
886 | if (c <= maxSingleCharacterString) |
887 | return vm.smallStrings.singleCharacterString(c); |
888 | } |
889 | auto impl = StringImpl::createSubstringSharingImpl(*s.impl(), offset, length); |
890 | if (impl->isSubString()) |
891 | return JSString::createHasOtherOwner(vm, WTFMove(impl)); |
892 | return JSString::create(vm, WTFMove(impl)); |
893 | } |
894 | |
895 | inline JSString* jsOwnedString(VM& vm, const String& s) |
896 | { |
897 | int size = s.length(); |
898 | if (!size) |
899 | return vm.smallStrings.emptyString(); |
900 | if (size == 1) { |
901 | UChar c = s.characterAt(0); |
902 | if (c <= maxSingleCharacterString) |
903 | return vm.smallStrings.singleCharacterString(c); |
904 | } |
905 | return JSString::createHasOtherOwner(vm, *s.impl()); |
906 | } |
907 | |
908 | ALWAYS_INLINE JSString* jsStringWithCache(JSGlobalObject* globalObject, const String& s) |
909 | { |
910 | VM& vm = getVM(globalObject); |
911 | StringImpl* stringImpl = s.impl(); |
912 | if (!stringImpl || !stringImpl->length()) |
913 | return jsEmptyString(vm); |
914 | |
915 | if (stringImpl->length() == 1) { |
916 | UChar singleCharacter = (*stringImpl)[0u]; |
917 | if (singleCharacter <= maxSingleCharacterString) |
918 | return vm.smallStrings.singleCharacterString(static_cast<unsigned char>(singleCharacter)); |
919 | } |
920 | |
921 | if (JSString* lastCachedString = vm.lastCachedString.get()) { |
922 | if (lastCachedString->tryGetValueImpl() == stringImpl) |
923 | return lastCachedString; |
924 | } |
925 | |
926 | return jsStringWithCacheSlowCase(vm, *stringImpl); |
927 | } |
928 | |
929 | ALWAYS_INLINE bool JSString::getStringPropertySlot(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
930 | { |
931 | VM& vm = getVM(globalObject); |
932 | auto scope = DECLARE_THROW_SCOPE(vm); |
933 | |
934 | if (propertyName == vm.propertyNames->length) { |
935 | slot.setValue(this, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly, jsNumber(length())); |
936 | return true; |
937 | } |
938 | |
939 | Optional<uint32_t> index = parseIndex(propertyName); |
940 | if (index && index.value() < length()) { |
941 | JSValue value = getIndex(globalObject, index.value()); |
942 | RETURN_IF_EXCEPTION(scope, false); |
943 | slot.setValue(this, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly, value); |
944 | return true; |
945 | } |
946 | |
947 | return false; |
948 | } |
949 | |
950 | ALWAYS_INLINE bool JSString::getStringPropertySlot(JSGlobalObject* globalObject, unsigned propertyName, PropertySlot& slot) |
951 | { |
952 | VM& vm = getVM(globalObject); |
953 | auto scope = DECLARE_THROW_SCOPE(vm); |
954 | |
955 | if (propertyName < length()) { |
956 | JSValue value = getIndex(globalObject, propertyName); |
957 | RETURN_IF_EXCEPTION(scope, false); |
958 | slot.setValue(this, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly, value); |
959 | return true; |
960 | } |
961 | |
962 | return false; |
963 | } |
964 | |
965 | inline bool isJSString(JSCell* cell) |
966 | { |
967 | return cell->type() == StringType; |
968 | } |
969 | |
970 | inline bool isJSString(JSValue v) |
971 | { |
972 | return v.isCell() && isJSString(v.asCell()); |
973 | } |
974 | |
975 | ALWAYS_INLINE StringView JSRopeString::unsafeView(JSGlobalObject* globalObject) const |
976 | { |
977 | if (validateDFGDoesGC) |
978 | RELEASE_ASSERT(vm().heap.expectDoesGC()); |
979 | if (isSubstring()) { |
980 | auto& base = substringBase()->valueInternal(); |
981 | if (base.is8Bit()) |
982 | return StringView(base.characters8() + substringOffset(), length()); |
983 | return StringView(base.characters16() + substringOffset(), length()); |
984 | } |
985 | return resolveRope(globalObject); |
986 | } |
987 | |
988 | ALWAYS_INLINE StringViewWithUnderlyingString JSRopeString::viewWithUnderlyingString(JSGlobalObject* globalObject) const |
989 | { |
990 | if (validateDFGDoesGC) |
991 | RELEASE_ASSERT(vm().heap.expectDoesGC()); |
992 | if (isSubstring()) { |
993 | auto& base = substringBase()->valueInternal(); |
994 | if (base.is8Bit()) |
995 | return { { base.characters8() + substringOffset(), length() }, base }; |
996 | return { { base.characters16() + substringOffset(), length() }, base }; |
997 | } |
998 | auto& string = resolveRope(globalObject); |
999 | return { string, string }; |
1000 | } |
1001 | |
1002 | ALWAYS_INLINE StringView JSString::unsafeView(JSGlobalObject* globalObject) const |
1003 | { |
1004 | if (validateDFGDoesGC) |
1005 | RELEASE_ASSERT(vm().heap.expectDoesGC()); |
1006 | if (isRope()) |
1007 | return static_cast<const JSRopeString*>(this)->unsafeView(globalObject); |
1008 | return valueInternal(); |
1009 | } |
1010 | |
1011 | ALWAYS_INLINE StringViewWithUnderlyingString JSString::viewWithUnderlyingString(JSGlobalObject* globalObject) const |
1012 | { |
1013 | if (isRope()) |
1014 | return static_cast<const JSRopeString&>(*this).viewWithUnderlyingString(globalObject); |
1015 | return { valueInternal(), valueInternal() }; |
1016 | } |
1017 | |
1018 | inline bool JSString::isSubstring() const |
1019 | { |
1020 | return m_fiber & JSRopeString::isSubstringInPointer; |
1021 | } |
1022 | |
1023 | // --- JSValue inlines ---------------------------- |
1024 | |
1025 | inline bool JSValue::toBoolean(JSGlobalObject* globalObject) const |
1026 | { |
1027 | if (isInt32()) |
1028 | return asInt32(); |
1029 | if (isDouble()) |
1030 | return asDouble() > 0.0 || asDouble() < 0.0; // false for NaN |
1031 | if (isCell()) |
1032 | return asCell()->toBoolean(globalObject); |
1033 | return isTrue(); // false, null, and undefined all convert to false. |
1034 | } |
1035 | |
1036 | inline JSString* JSValue::toString(JSGlobalObject* globalObject) const |
1037 | { |
1038 | if (isString()) |
1039 | return asString(asCell()); |
1040 | bool returnEmptyStringOnError = true; |
1041 | return toStringSlowCase(globalObject, returnEmptyStringOnError); |
1042 | } |
1043 | |
1044 | inline JSString* JSValue::toStringOrNull(JSGlobalObject* globalObject) const |
1045 | { |
1046 | if (isString()) |
1047 | return asString(asCell()); |
1048 | bool returnEmptyStringOnError = false; |
1049 | return toStringSlowCase(globalObject, returnEmptyStringOnError); |
1050 | } |
1051 | |
1052 | inline String JSValue::toWTFString(JSGlobalObject* globalObject) const |
1053 | { |
1054 | if (isString()) |
1055 | return static_cast<JSString*>(asCell())->value(globalObject); |
1056 | return toWTFStringSlowCase(globalObject); |
1057 | } |
1058 | |
1059 | } // namespace JSC |
1060 | |