| /* |
| * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) |
| * Copyright (C) 2001 Peter Kelly (pmk@post.com) |
| * Copyright (C) 2003-2022 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #pragma once |
| |
| #include "ArgList.h" |
| #include "CallFrame.h" |
| #include "CommonIdentifiers.h" |
| #include "GetVM.h" |
| #include "Identifier.h" |
| #include "PropertyDescriptor.h" |
| #include "PropertySlot.h" |
| #include "Structure.h" |
| #include "ThrowScope.h" |
| #include <array> |
| #include <wtf/CheckedArithmetic.h> |
| #include <wtf/ForbidHeapAllocation.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/text/StringView.h> |
| |
| #if OS(DARWIN) |
| #include <mach/vm_param.h> |
| #endif |
| |
| namespace JSC { |
| |
| class JSString; |
| class JSRopeString; |
| class LLIntOffsetsExtractor; |
| |
| JSString* jsEmptyString(VM&); |
| JSString* jsString(VM&, const String&); // returns empty string if passed null string |
| JSString* jsString(VM&, String&&); // returns empty string if passed null string |
| JSString* jsString(VM&, const AtomString&); // returns empty string if passed null string |
| JSString* jsString(VM&, AtomString&&); // returns empty string if passed null string |
| JSString* jsString(VM&, StringView); // returns empty string if passed null string |
| |
| JSString* jsString(VM&, RefPtr<AtomStringImpl>&&); |
| JSString* jsString(VM&, Ref<AtomStringImpl>&&); |
| JSString* jsString(VM&, Ref<StringImpl>&&); |
| |
| JSString* jsSingleCharacterString(VM&, UChar); |
| JSString* jsSubstring(VM&, const String&, unsigned offset, unsigned length); |
| |
| // Non-trivial strings are two or more characters long. |
| // These functions are faster than just calling jsString. |
| JSString* jsNontrivialString(VM&, const String&); |
| JSString* jsNontrivialString(VM&, String&&); |
| |
| // Should be used for strings that are owned by an object that will |
| // likely outlive the JSValue this makes, such as the parse tree or a |
| // DOM object that contains a String |
| JSString* jsOwnedString(VM&, const String&); |
| |
| bool isJSString(JSCell*); |
| bool isJSString(JSValue); |
| JSString* asString(JSValue); |
| |
| // In 64bit architecture, JSString and JSRopeString have the following memory layout to make sizeof(JSString) == 16 and sizeof(JSRopeString) == 32. |
| // JSString has only one pointer. We use it for String. length() and is8Bit() queries go to StringImpl. In JSRopeString, we reuse the above pointer |
| // place for the 1st fiber. JSRopeString has three fibers so its size is 48. To keep length and is8Bit flag information in JSRopeString, JSRopeString |
| // encodes these information into the fiber pointers. is8Bit flag is encoded in the 1st fiber pointer. length is embedded directly, and two fibers |
| // are compressed into 12bytes. isRope information is encoded in the first fiber's LSB. |
| // |
| // Since length of JSRopeString should be frequently accessed compared to each fiber, we put length in contiguous 32byte field, and compress 2nd |
| // 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 |
| // are not one pointer load operation. To make concurrent collector work correctly, we must initialize 2nd and 3rd fibers at JSRopeString creation |
| // and we must not modify these part later. |
| // |
| // 0 8 10 16 20 24 26 28 32 |
| // JSString [ ID ][ header ][ String pointer 0] |
| // JSRopeString [ ID ][ header ][ 1st fiber xyz][ length ][2nd lower32][2nd upper16][3rd lower16][3rd upper32] |
| // ^ |
| // x:(is8Bit),y:(isSubstring),z:(isRope) bit flags |
| class JSString : public JSCell { |
| public: |
| friend class JIT; |
| friend class VM; |
| friend class SpecializedThunkJIT; |
| friend class JSRopeString; |
| friend class MarkStack; |
| friend class SlotVisitor; |
| friend class SmallStrings; |
| |
| typedef JSCell Base; |
| // Do we really need OverridesGetOwnPropertySlot? |
| // FIXME: https://bugs.webkit.org/show_bug.cgi?id=212956 |
| // Do we really need InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero? |
| // FIXME: https://bugs.webkit.org/show_bug.cgi?id=212958 |
| static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero | StructureIsImmortal | OverridesToThis; |
| |
| static constexpr bool needsDestruction = true; |
| static ALWAYS_INLINE void destroy(JSCell* cell) |
| { |
| static_cast<JSString*>(cell)->JSString::~JSString(); |
| } |
| |
| // We specialize the string subspace to get the fastest possible sweep. This wouldn't be |
| // necessary if JSString didn't have a destructor. |
| template<typename, SubspaceAccess> |
| static GCClient::IsoSubspace* subspaceFor(VM& vm) |
| { |
| return &vm.stringSpace(); |
| } |
| |
| // We employ overflow checks in many places with the assumption that MaxLength |
| // is INT_MAX. Hence, it cannot be changed into another length value without |
| // breaking all the bounds and overflow checks that assume this. |
| static constexpr unsigned MaxLength = std::numeric_limits<int32_t>::max(); |
| static_assert(MaxLength == String::MaxLength); |
| |
| static constexpr uintptr_t isRopeInPointer = 0x1; |
| |
| private: |
| String& uninitializedValueInternal() const |
| { |
| return *bitwise_cast<String*>(&m_fiber); |
| } |
| |
| String& valueInternal() const |
| { |
| ASSERT(!isRope()); |
| return uninitializedValueInternal(); |
| } |
| |
| JSString(VM& vm, Ref<StringImpl>&& value) |
| : JSCell(vm, vm.stringStructure.get()) |
| { |
| new (&uninitializedValueInternal()) String(WTFMove(value)); |
| } |
| |
| JSString(VM& vm) |
| : JSCell(vm, vm.stringStructure.get()) |
| , m_fiber(isRopeInPointer) |
| { |
| } |
| |
| void finishCreation(VM& vm, unsigned length) |
| { |
| ASSERT_UNUSED(length, length > 0); |
| ASSERT(!valueInternal().isNull()); |
| Base::finishCreation(vm); |
| } |
| |
| void finishCreation(VM& vm, unsigned length, size_t cost) |
| { |
| ASSERT_UNUSED(length, length > 0); |
| ASSERT(!valueInternal().isNull()); |
| Base::finishCreation(vm); |
| vm.heap.reportExtraMemoryAllocated(cost); |
| } |
| |
| static JSString* createEmptyString(VM&); |
| |
| static JSString* create(VM& vm, Ref<StringImpl>&& value) |
| { |
| unsigned length = value->length(); |
| ASSERT(length > 0); |
| size_t cost = value->cost(); |
| JSString* newString = new (NotNull, allocateCell<JSString>(vm)) JSString(vm, WTFMove(value)); |
| newString->finishCreation(vm, length, cost); |
| return newString; |
| } |
| static JSString* createHasOtherOwner(VM& vm, Ref<StringImpl>&& value) |
| { |
| unsigned length = value->length(); |
| JSString* newString = new (NotNull, allocateCell<JSString>(vm)) JSString(vm, WTFMove(value)); |
| newString->finishCreation(vm, length); |
| return newString; |
| } |
| |
| protected: |
| void finishCreation(VM& vm) |
| { |
| Base::finishCreation(vm); |
| } |
| |
| public: |
| ~JSString(); |
| |
| Identifier toIdentifier(JSGlobalObject*) const; |
| AtomString toAtomString(JSGlobalObject*) const; |
| AtomString toExistingAtomString(JSGlobalObject*) const; |
| |
| StringViewWithUnderlyingString viewWithUnderlyingString(JSGlobalObject*) const; |
| |
| inline bool equal(JSGlobalObject*, JSString* other) const; |
| const String& value(JSGlobalObject*) const; |
| inline const String& tryGetValue(bool allocationAllowed = true) const; |
| const StringImpl* getValueImpl() const; |
| const StringImpl* tryGetValueImpl() const; |
| ALWAYS_INLINE unsigned length() const; |
| |
| JSValue toPrimitive(JSGlobalObject*, PreferredPrimitiveType) const; |
| bool toBoolean() const { return !!length(); } |
| JSObject* toObject(JSGlobalObject*) const; |
| double toNumber(JSGlobalObject*) const; |
| |
| bool getStringPropertySlot(JSGlobalObject*, PropertyName, PropertySlot&); |
| bool getStringPropertySlot(JSGlobalObject*, unsigned propertyName, PropertySlot&); |
| bool getStringPropertyDescriptor(JSGlobalObject*, PropertyName, PropertyDescriptor&); |
| |
| bool canGetIndex(unsigned i) { return i < length(); } |
| JSString* getIndex(JSGlobalObject*, unsigned); |
| |
| static Structure* createStructure(VM&, JSGlobalObject*, JSValue); |
| |
| static ptrdiff_t offsetOfValue() { return OBJECT_OFFSETOF(JSString, m_fiber); } |
| |
| DECLARE_EXPORT_INFO; |
| |
| static void dumpToStream(const JSCell*, PrintStream&); |
| static size_t estimatedSize(JSCell*, VM&); |
| DECLARE_VISIT_CHILDREN; |
| |
| ALWAYS_INLINE bool isRope() const |
| { |
| return m_fiber & isRopeInPointer; |
| } |
| |
| bool is8Bit() const; |
| |
| protected: |
| friend class JSValue; |
| |
| JS_EXPORT_PRIVATE bool equalSlowCase(JSGlobalObject*, JSString* other) const; |
| bool isSubstring() const; |
| |
| uintptr_t fiberConcurrently() const { return m_fiber; } |
| |
| mutable uintptr_t m_fiber; |
| |
| private: |
| friend class LLIntOffsetsExtractor; |
| |
| static JSValue toThis(JSCell*, JSGlobalObject*, ECMAMode); |
| |
| StringView unsafeView(JSGlobalObject*) const; |
| |
| void swapToAtomString(VM&, RefPtr<AtomStringImpl>&&) const; |
| |
| friend JSString* jsString(VM&, const String&); |
| friend JSString* jsString(VM&, String&&); |
| friend JSString* jsString(VM&, StringView); |
| friend JSString* jsString(JSGlobalObject*, JSString*, JSString*); |
| friend JSString* jsString(JSGlobalObject*, const String&, JSString*); |
| friend JSString* jsString(JSGlobalObject*, JSString*, const String&); |
| friend JSString* jsString(JSGlobalObject*, const String&, const String&); |
| friend JSString* jsString(JSGlobalObject*, JSString*, JSString*, JSString*); |
| friend JSString* jsString(JSGlobalObject*, const String&, const String&, const String&); |
| friend JSString* jsSingleCharacterString(VM&, UChar); |
| friend JSString* jsNontrivialString(VM&, const String&); |
| friend JSString* jsNontrivialString(VM&, String&&); |
| friend JSString* jsSubstring(VM&, const String&, unsigned, unsigned); |
| friend JSString* jsSubstring(VM&, JSGlobalObject*, JSString*, unsigned, unsigned); |
| friend JSString* jsSubstringOfResolved(VM&, GCDeferralContext*, JSString*, unsigned, unsigned); |
| friend JSString* jsOwnedString(VM&, const String&); |
| }; |
| |
| // NOTE: This class cannot override JSString's destructor. JSString's destructor is called directly |
| // from JSStringSubspace:: |
| class JSRopeString final : public JSString { |
| friend class JSString; |
| public: |
| template<typename, SubspaceAccess> |
| static GCClient::IsoSubspace* subspaceFor(VM& vm) |
| { |
| return &vm.ropeStringSpace(); |
| } |
| |
| // We use lower 3bits of fiber0 for flags. These bits are usable due to alignment, and it is OK even in 32bit architecture. |
| static constexpr uintptr_t is8BitInPointer = static_cast<uintptr_t>(StringImpl::flagIs8Bit()); |
| static constexpr uintptr_t isSubstringInPointer = 0x2; |
| static_assert(is8BitInPointer == 0b100); |
| static_assert(isSubstringInPointer == 0b010); |
| static_assert(isRopeInPointer == 0b001); |
| static constexpr uintptr_t stringMask = ~(isRopeInPointer | is8BitInPointer | isSubstringInPointer); |
| #if CPU(ADDRESS64) |
| static_assert(sizeof(uintptr_t) == sizeof(uint64_t)); |
| class CompactFibers { |
| public: |
| static constexpr uintptr_t addressMask = (1ULL << OS_CONSTANT(EFFECTIVE_ADDRESS_WIDTH)) - 1; |
| JSString* fiber1() const |
| { |
| #if CPU(LITTLE_ENDIAN) |
| return bitwise_cast<JSString*>(WTF::unalignedLoad<uintptr_t>(&m_fiber1Lower) & addressMask); |
| #else |
| return bitwise_cast<JSString*>(static_cast<uintptr_t>(m_fiber1Lower) | (static_cast<uintptr_t>(m_fiber1Upper) << 32)); |
| #endif |
| } |
| |
| void initializeFiber1(JSString* fiber) |
| { |
| uintptr_t pointer = bitwise_cast<uintptr_t>(fiber); |
| m_fiber1Lower = static_cast<uint32_t>(pointer); |
| m_fiber1Upper = static_cast<uint16_t>(pointer >> 32); |
| } |
| |
| JSString* fiber2() const |
| { |
| #if CPU(LITTLE_ENDIAN) |
| return bitwise_cast<JSString*>(WTF::unalignedLoad<uintptr_t>(&m_fiber1Upper) >> 16); |
| #else |
| return bitwise_cast<JSString*>(static_cast<uintptr_t>(m_fiber2Lower) | (static_cast<uintptr_t>(m_fiber2Upper) << 16)); |
| #endif |
| } |
| void initializeFiber2(JSString* fiber) |
| { |
| uintptr_t pointer = bitwise_cast<uintptr_t>(fiber); |
| m_fiber2Lower = static_cast<uint16_t>(pointer); |
| m_fiber2Upper = static_cast<uint32_t>(pointer >> 16); |
| } |
| |
| unsigned length() const { return m_length; } |
| void initializeLength(unsigned length) |
| { |
| m_length = length; |
| } |
| |
| static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(CompactFibers, m_length); } |
| static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(CompactFibers, m_length); } |
| static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1Upper); } |
| |
| private: |
| friend class LLIntOffsetsExtractor; |
| |
| uint32_t m_length { 0 }; |
| uint32_t m_fiber1Lower { 0 }; |
| uint16_t m_fiber1Upper { 0 }; |
| uint16_t m_fiber2Lower { 0 }; |
| uint32_t m_fiber2Upper { 0 }; |
| }; |
| static_assert(sizeof(CompactFibers) == sizeof(void*) * 2); |
| #else |
| class CompactFibers { |
| public: |
| JSString* fiber1() const |
| { |
| return m_fiber1; |
| } |
| void initializeFiber1(JSString* fiber) |
| { |
| m_fiber1 = fiber; |
| } |
| |
| JSString* fiber2() const |
| { |
| return m_fiber2; |
| } |
| void initializeFiber2(JSString* fiber) |
| { |
| m_fiber2 = fiber; |
| } |
| |
| unsigned length() const { return m_length; } |
| void initializeLength(unsigned length) |
| { |
| m_length = length; |
| } |
| |
| static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(CompactFibers, m_length); } |
| static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(CompactFibers, m_fiber1); } |
| static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(CompactFibers, m_fiber2); } |
| |
| private: |
| friend class LLIntOffsetsExtractor; |
| |
| uint32_t m_length { 0 }; |
| JSString* m_fiber1 { nullptr }; |
| JSString* m_fiber2 { nullptr }; |
| }; |
| #endif |
| |
| template <class OverflowHandler = CrashOnOverflow> |
| class RopeBuilder : public OverflowHandler { |
| WTF_FORBID_HEAP_ALLOCATION; |
| public: |
| RopeBuilder(VM& vm) |
| : m_vm(vm) |
| { |
| } |
| |
| bool append(JSString* jsString) |
| { |
| if (UNLIKELY(this->hasOverflowed())) |
| return false; |
| if (!jsString->length()) |
| return true; |
| if (m_strings.size() == JSRopeString::s_maxInternalRopeLength) |
| expand(); |
| |
| static_assert(JSString::MaxLength == std::numeric_limits<int32_t>::max()); |
| auto sum = checkedSum<int32_t>(m_length, jsString->length()); |
| if (sum.hasOverflowed()) { |
| this->overflowed(); |
| return false; |
| } |
| ASSERT(static_cast<unsigned>(sum) <= MaxLength); |
| m_strings.append(jsString); |
| m_length = static_cast<unsigned>(sum); |
| return true; |
| } |
| |
| JSString* release() |
| { |
| RELEASE_ASSERT(!this->hasOverflowed()); |
| JSString* result = nullptr; |
| switch (m_strings.size()) { |
| case 0: { |
| ASSERT(!m_length); |
| result = jsEmptyString(m_vm); |
| break; |
| } |
| case 1: { |
| result = asString(m_strings.at(0)); |
| break; |
| } |
| case 2: { |
| result = JSRopeString::create(m_vm, asString(m_strings.at(0)), asString(m_strings.at(1))); |
| break; |
| } |
| case 3: { |
| result = JSRopeString::create(m_vm, asString(m_strings.at(0)), asString(m_strings.at(1)), asString(m_strings.at(2))); |
| break; |
| } |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| ASSERT(result->length() == m_length); |
| m_strings.clear(); |
| m_length = 0; |
| return result; |
| } |
| |
| unsigned length() const |
| { |
| ASSERT(!this->hasOverflowed()); |
| return m_length; |
| } |
| |
| private: |
| void expand(); |
| |
| VM& m_vm; |
| MarkedArgumentBuffer m_strings; |
| unsigned m_length { 0 }; |
| }; |
| |
| inline unsigned length() const |
| { |
| return m_compactFibers.length(); |
| } |
| |
| private: |
| friend class LLIntOffsetsExtractor; |
| |
| void convertToNonRope(String&&) const; |
| |
| void initializeIs8Bit(bool flag) const |
| { |
| if (flag) |
| m_fiber |= is8BitInPointer; |
| else |
| m_fiber &= ~is8BitInPointer; |
| } |
| |
| void initializeIsSubstring(bool flag) const |
| { |
| if (flag) |
| m_fiber |= isSubstringInPointer; |
| else |
| m_fiber &= ~isSubstringInPointer; |
| } |
| |
| ALWAYS_INLINE void initializeLength(unsigned length) |
| { |
| ASSERT(length <= MaxLength); |
| m_compactFibers.initializeLength(length); |
| } |
| |
| JSRopeString(VM& vm) |
| : JSString(vm) |
| { |
| initializeIsSubstring(false); |
| initializeLength(0); |
| initializeIs8Bit(true); |
| initializeFiber0(nullptr); |
| initializeFiber1(nullptr); |
| initializeFiber2(nullptr); |
| } |
| |
| JSRopeString(VM& vm, JSString* s1, JSString* s2) |
| : JSString(vm) |
| { |
| ASSERT(!sumOverflows<int32_t>(s1->length(), s2->length())); |
| initializeIsSubstring(false); |
| initializeLength(s1->length() + s2->length()); |
| initializeIs8Bit(s1->is8Bit() && s2->is8Bit()); |
| initializeFiber0(s1); |
| initializeFiber1(s2); |
| initializeFiber2(nullptr); |
| ASSERT((s1->length() + s2->length()) == length()); |
| } |
| |
| JSRopeString(VM& vm, JSString* s1, JSString* s2, JSString* s3) |
| : JSString(vm) |
| { |
| ASSERT(!sumOverflows<int32_t>(s1->length(), s2->length(), s3->length())); |
| initializeIsSubstring(false); |
| initializeLength(s1->length() + s2->length() + s3->length()); |
| initializeIs8Bit(s1->is8Bit() && s2->is8Bit() && s3->is8Bit()); |
| initializeFiber0(s1); |
| initializeFiber1(s2); |
| initializeFiber2(s3); |
| ASSERT((s1->length() + s2->length() + s3->length()) == length()); |
| } |
| |
| JSRopeString(VM& vm, JSString* base, unsigned offset, unsigned length) |
| : JSString(vm) |
| { |
| RELEASE_ASSERT(!sumOverflows<int32_t>(offset, length)); |
| RELEASE_ASSERT(offset + length <= base->length()); |
| initializeIsSubstring(true); |
| initializeLength(length); |
| initializeIs8Bit(base->is8Bit()); |
| initializeSubstringBase(base); |
| initializeSubstringOffset(offset); |
| ASSERT(length == this->length()); |
| ASSERT(!base->isRope()); |
| } |
| |
| ALWAYS_INLINE void finishCreationSubstringOfResolved(VM& vm) |
| { |
| Base::finishCreation(vm); |
| } |
| |
| public: |
| static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfLength(); } // 32byte width. |
| static ptrdiff_t offsetOfFlags() { return offsetOfValue(); } |
| static ptrdiff_t offsetOfFiber0() { return offsetOfValue(); } |
| static ptrdiff_t offsetOfFiber1() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber1(); } |
| static ptrdiff_t offsetOfFiber2() { return OBJECT_OFFSETOF(JSRopeString, m_compactFibers) + CompactFibers::offsetOfFiber2(); } |
| |
| static constexpr unsigned s_maxInternalRopeLength = 3; |
| |
| // This JSRopeString is only used to simulate half-baked JSRopeString in DFG and FTL MakeRope. If OSR exit happens in |
| // the middle of MakeRope due to string length overflow, we have half-baked JSRopeString which is the same to the result |
| // of this function. This half-baked JSRopeString will not be exposed to users, but still collectors can see it due to |
| // the conservative stack scan. This JSRopeString is used to test the collector with such a half-baked JSRopeString. |
| // Because this JSRopeString breaks the JSString's invariant (only one singleton JSString can be zero length), almost all the |
| // operations in JS fail to handle this string correctly. |
| static JSRopeString* createNullForTesting(VM& vm) |
| { |
| JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm)) JSRopeString(vm); |
| newString->finishCreation(vm); |
| ASSERT(!newString->length()); |
| ASSERT(newString->isRope()); |
| ASSERT(newString->fiber0() == nullptr); |
| return newString; |
| } |
| |
| // If nullOrExecForOOM is null, resolveRope() will be do nothing in the event of an OOM error. |
| // The rope value will remain a null string in that case. |
| JS_EXPORT_PRIVATE const String& resolveRope(JSGlobalObject* nullOrGlobalObjectForOOM) const; |
| |
| private: |
| static JSRopeString* create(VM& vm, JSString* s1, JSString* s2) |
| { |
| JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm)) JSRopeString(vm, s1, s2); |
| newString->finishCreation(vm); |
| ASSERT(newString->length()); |
| ASSERT(newString->isRope()); |
| return newString; |
| } |
| static JSRopeString* create(VM& vm, JSString* s1, JSString* s2, JSString* s3) |
| { |
| JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm)) JSRopeString(vm, s1, s2, s3); |
| newString->finishCreation(vm); |
| ASSERT(newString->length()); |
| ASSERT(newString->isRope()); |
| return newString; |
| } |
| |
| ALWAYS_INLINE static JSRopeString* createSubstringOfResolved(VM& vm, GCDeferralContext* deferralContext, JSString* base, unsigned offset, unsigned length) |
| { |
| JSRopeString* newString = new (NotNull, allocateCell<JSRopeString>(vm, deferralContext)) JSRopeString(vm, base, offset, length); |
| newString->finishCreationSubstringOfResolved(vm); |
| ASSERT(newString->length()); |
| ASSERT(newString->isRope()); |
| return newString; |
| } |
| |
| friend JSValue jsStringFromRegisterArray(JSGlobalObject*, Register*, unsigned); |
| |
| template<typename Function> const String& resolveRopeWithFunction(JSGlobalObject* nullOrGlobalObjectForOOM, Function&&) const; |
| JS_EXPORT_PRIVATE AtomString resolveRopeToAtomString(JSGlobalObject*) const; |
| JS_EXPORT_PRIVATE RefPtr<AtomStringImpl> resolveRopeToExistingAtomString(JSGlobalObject*) const; |
| template<typename CharacterType> NEVER_INLINE void resolveRopeSlowCase(CharacterType*) const; |
| template<typename CharacterType> void resolveRopeInternalNoSubstring(CharacterType*) const; |
| Identifier toIdentifier(JSGlobalObject*) const; |
| void outOfMemory(JSGlobalObject* nullOrGlobalObjectForOOM) const; |
| template<typename CharacterType> void resolveRopeInternal(CharacterType*) const; |
| StringView unsafeView(JSGlobalObject*) const; |
| StringViewWithUnderlyingString viewWithUnderlyingString(JSGlobalObject*) const; |
| |
| JSString* fiber0() const |
| { |
| return bitwise_cast<JSString*>(m_fiber & stringMask); |
| } |
| |
| JSString* fiber1() const |
| { |
| return m_compactFibers.fiber1(); |
| } |
| |
| JSString* fiber2() const |
| { |
| return m_compactFibers.fiber2(); |
| } |
| |
| JSString* fiber(unsigned i) const |
| { |
| ASSERT(!isSubstring()); |
| ASSERT(i < s_maxInternalRopeLength); |
| switch (i) { |
| case 0: |
| return fiber0(); |
| case 1: |
| return fiber1(); |
| case 2: |
| return fiber2(); |
| } |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| void initializeFiber0(JSString* fiber) |
| { |
| uintptr_t pointer = bitwise_cast<uintptr_t>(fiber); |
| ASSERT(!(pointer & ~stringMask)); |
| m_fiber = (pointer | (m_fiber & ~stringMask)); |
| } |
| |
| void initializeFiber1(JSString* fiber) |
| { |
| m_compactFibers.initializeFiber1(fiber); |
| } |
| |
| void initializeFiber2(JSString* fiber) |
| { |
| m_compactFibers.initializeFiber2(fiber); |
| } |
| |
| void initializeSubstringBase(JSString* fiber) |
| { |
| initializeFiber1(fiber); |
| } |
| |
| JSString* substringBase() const { return fiber1(); } |
| |
| void initializeSubstringOffset(unsigned offset) |
| { |
| m_compactFibers.initializeFiber2(bitwise_cast<JSString*>(static_cast<uintptr_t>(offset))); |
| } |
| |
| unsigned substringOffset() const |
| { |
| return static_cast<unsigned>(bitwise_cast<uintptr_t>(fiber2())); |
| } |
| |
| static_assert(s_maxInternalRopeLength >= 2); |
| mutable CompactFibers m_compactFibers; |
| |
| friend JSString* jsString(JSGlobalObject*, JSString*, JSString*); |
| friend JSString* jsString(JSGlobalObject*, const String&, JSString*); |
| friend JSString* jsString(JSGlobalObject*, JSString*, const String&); |
| friend JSString* jsString(JSGlobalObject*, const String&, const String&); |
| friend JSString* jsString(JSGlobalObject*, JSString*, JSString*, JSString*); |
| friend JSString* jsString(JSGlobalObject*, const String&, const String&, const String&); |
| friend JSString* jsSubstringOfResolved(VM&, GCDeferralContext*, JSString*, unsigned, unsigned); |
| friend JSString* jsSubstring(VM&, JSGlobalObject*, JSString*, unsigned, unsigned); |
| }; |
| |
| JS_EXPORT_PRIVATE JSString* jsStringWithCacheSlowCase(VM&, StringImpl&); |
| |
| // JSString::is8Bit is safe to be called concurrently. Concurrent threads can access is8Bit even if the main thread |
| // is in the middle of converting JSRopeString to JSString. |
| ALWAYS_INLINE bool JSString::is8Bit() const |
| { |
| uintptr_t pointer = fiberConcurrently(); |
| if (pointer & isRopeInPointer) { |
| // Do not load m_fiber twice. We should use the information in pointer. |
| // Otherwise, JSRopeString may be converted to JSString between the first and second accesses. |
| return pointer & JSRopeString::is8BitInPointer; |
| } |
| return bitwise_cast<StringImpl*>(pointer)->is8Bit(); |
| } |
| |
| // JSString::length is safe to be called concurrently. Concurrent threads can access length even if the main thread |
| // is in the middle of converting JSRopeString to JSString. This is OK because we never override the length bits |
| // when we resolve a JSRopeString. |
| ALWAYS_INLINE unsigned JSString::length() const |
| { |
| uintptr_t pointer = fiberConcurrently(); |
| if (pointer & isRopeInPointer) |
| return jsCast<const JSRopeString*>(this)->length(); |
| return bitwise_cast<StringImpl*>(pointer)->length(); |
| } |
| |
| inline const StringImpl* JSString::getValueImpl() const |
| { |
| ASSERT(!isRope()); |
| return bitwise_cast<StringImpl*>(m_fiber); |
| } |
| |
| inline const StringImpl* JSString::tryGetValueImpl() const |
| { |
| uintptr_t pointer = fiberConcurrently(); |
| if (pointer & isRopeInPointer) |
| return nullptr; |
| return bitwise_cast<StringImpl*>(pointer); |
| } |
| |
| inline JSString* asString(JSValue value) |
| { |
| ASSERT(value.asCell()->isString()); |
| return jsCast<JSString*>(value.asCell()); |
| } |
| |
| // This MUST NOT GC. |
| inline JSString* jsEmptyString(VM& vm) |
| { |
| return vm.smallStrings.emptyString(); |
| } |
| |
| ALWAYS_INLINE JSString* jsSingleCharacterString(VM& vm, UChar c) |
| { |
| if constexpr (validateDFGDoesGC) |
| vm.verifyCanGC(); |
| if (c <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(c); |
| return JSString::create(vm, StringImpl::create(&c, 1)); |
| } |
| |
| inline JSString* jsNontrivialString(VM& vm, const String& s) |
| { |
| ASSERT(s.length() > 1); |
| return JSString::create(vm, *s.impl()); |
| } |
| |
| inline JSString* jsNontrivialString(VM& vm, String&& s) |
| { |
| ASSERT(s.length() > 1); |
| return JSString::create(vm, s.releaseImpl().releaseNonNull()); |
| } |
| |
| ALWAYS_INLINE Identifier JSRopeString::toIdentifier(JSGlobalObject* globalObject) const |
| { |
| VM& vm = getVM(globalObject); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| auto atomString = static_cast<const JSRopeString*>(this)->resolveRopeToAtomString(globalObject); |
| RETURN_IF_EXCEPTION(scope, { }); |
| return Identifier::fromString(vm, atomString); |
| } |
| |
| ALWAYS_INLINE void JSString::swapToAtomString(VM& vm, RefPtr<AtomStringImpl>&& atom) const |
| { |
| // We replace currently held string with new AtomString. But the old string can be accessed from concurrent compilers and GC threads at any time. |
| // So, we keep the old string alive by appending it to Heap::m_possiblyAccessedStringsFromConcurrentThreads. And GC clears that list when GC finishes. |
| // This is OK since (1) when finishing GC concurrent compiler threads and GC threads are stopped, and (2) AtomString is already held in the atom table, |
| // and we anyway keep this old string until this JSString* is GC-ed. So it does not increase any memory pressure, we release at the same timing. |
| ASSERT(!isCompilationThread() && !Thread::mayBeGCThread()); |
| String target(WTFMove(atom)); |
| WTF::storeStoreFence(); // Ensure AtomStringImpl's string is fully initialized when it is exposed to concurrent threads. |
| valueInternal().swap(target); |
| vm.heap.appendPossiblyAccessedStringFromConcurrentThreads(WTFMove(target)); |
| } |
| |
| ALWAYS_INLINE Identifier JSString::toIdentifier(JSGlobalObject* globalObject) const |
| { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isRope()) |
| return static_cast<const JSRopeString*>(this)->toIdentifier(globalObject); |
| VM& vm = getVM(globalObject); |
| if (valueInternal().impl()->isAtom()) |
| return Identifier::fromString(vm, Ref { *static_cast<AtomStringImpl*>(valueInternal().impl()) }); |
| if (vm.lastAtomizedIdentifierStringImpl.ptr() != valueInternal().impl()) { |
| vm.lastAtomizedIdentifierStringImpl = *valueInternal().impl(); |
| vm.lastAtomizedIdentifierAtomStringImpl = AtomStringImpl::add(valueInternal().impl()).releaseNonNull(); |
| } |
| // It is possible that AtomStringImpl::add converts existing valueInternal()'s StringImpl to AtomicStringImpl, |
| // thus we need to recheck atomicity status here. |
| if (!valueInternal().impl()->isAtom()) |
| swapToAtomString(vm, RefPtr { vm.lastAtomizedIdentifierAtomStringImpl.ptr() }); |
| return Identifier::fromString(vm, Ref { vm.lastAtomizedIdentifierAtomStringImpl }); |
| } |
| |
| ALWAYS_INLINE AtomString JSString::toAtomString(JSGlobalObject* globalObject) const |
| { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isRope()) |
| return static_cast<const JSRopeString*>(this)->resolveRopeToAtomString(globalObject); |
| AtomString atom(valueInternal()); |
| // It is possible that AtomString constructor converts existing valueInternal()'s StringImpl to AtomicStringImpl, |
| // thus we need to recheck atomicity status here. |
| if (!valueInternal().impl()->isAtom()) |
| swapToAtomString(getVM(globalObject), RefPtr { atom.impl() }); |
| return atom; |
| } |
| |
| ALWAYS_INLINE AtomString JSString::toExistingAtomString(JSGlobalObject* globalObject) const |
| { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isRope()) |
| return static_cast<const JSRopeString*>(this)->resolveRopeToExistingAtomString(globalObject); |
| if (valueInternal().impl()->isAtom()) |
| return static_cast<AtomStringImpl*>(valueInternal().impl()); |
| return AtomStringImpl::lookUp(valueInternal().impl()); |
| } |
| |
| inline const String& JSString::value(JSGlobalObject* globalObject) const |
| { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isRope()) |
| return static_cast<const JSRopeString*>(this)->resolveRope(globalObject); |
| return valueInternal(); |
| } |
| |
| inline const String& JSString::tryGetValue(bool allocationAllowed) const |
| { |
| if (allocationAllowed) { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isRope()) { |
| // Pass nullptr for the JSGlobalObject so that resolveRope does not throw in the event of an OOM error. |
| return static_cast<const JSRopeString*>(this)->resolveRope(nullptr); |
| } |
| } else |
| RELEASE_ASSERT(!isRope()); |
| return valueInternal(); |
| } |
| |
| inline JSString* JSString::getIndex(JSGlobalObject* globalObject, unsigned i) |
| { |
| VM& vm = getVM(globalObject); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| ASSERT(canGetIndex(i)); |
| StringView view = unsafeView(globalObject); |
| RETURN_IF_EXCEPTION(scope, nullptr); |
| return jsSingleCharacterString(vm, view[i]); |
| } |
| |
| inline JSString* jsString(VM& vm, const String& s) |
| { |
| int size = s.length(); |
| if (!size) |
| return vm.smallStrings.emptyString(); |
| if (size == 1) { |
| UChar c = s.characterAt(0); |
| if (c <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(c); |
| } |
| return JSString::create(vm, *s.impl()); |
| } |
| |
| inline JSString* jsString(VM& vm, String&& s) |
| { |
| int size = s.length(); |
| if (!size) |
| return vm.smallStrings.emptyString(); |
| if (size == 1) { |
| UChar c = s.characterAt(0); |
| if (c <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(c); |
| } |
| return JSString::create(vm, s.releaseImpl().releaseNonNull()); |
| } |
| |
| ALWAYS_INLINE JSString* jsString(VM& vm, const AtomString& s) |
| { |
| return jsString(vm, s.string()); |
| } |
| |
| ALWAYS_INLINE JSString* jsString(VM& vm, AtomString&& s) |
| { |
| return jsString(vm, s.releaseString()); |
| } |
| |
| inline JSString* jsString(VM& vm, StringView s) |
| { |
| int size = s.length(); |
| if (!size) |
| return vm.smallStrings.emptyString(); |
| if (size == 1) { |
| UChar c = s.characterAt(0); |
| if (c <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(c); |
| } |
| auto impl = s.is8Bit() ? StringImpl::create(s.characters8(), s.length()) : StringImpl::create(s.characters16(), s.length()); |
| return JSString::create(vm, WTFMove(impl)); |
| } |
| |
| ALWAYS_INLINE JSString* jsString(VM& vm, RefPtr<AtomStringImpl>&& s) |
| { |
| return jsString(vm, String { WTFMove(s) }); |
| } |
| |
| ALWAYS_INLINE JSString* jsString(VM& vm, Ref<AtomStringImpl>&& s) |
| { |
| return jsString(vm, String { WTFMove(s) }); |
| } |
| |
| ALWAYS_INLINE JSString* jsString(VM& vm, Ref<StringImpl>&& s) |
| { |
| return jsString(vm, String { WTFMove(s) }); |
| } |
| |
| inline JSString* jsSubstring(VM& vm, JSGlobalObject* globalObject, JSString* base, unsigned offset, unsigned length) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| ASSERT(offset <= base->length()); |
| ASSERT(length <= base->length()); |
| ASSERT(offset + length <= base->length()); |
| if (!length) |
| return vm.smallStrings.emptyString(); |
| if (!offset && length == base->length()) |
| return base; |
| |
| // For now, let's not allow substrings with a rope base. |
| // Resolve non-substring rope bases so we don't have to deal with it. |
| // FIXME: Evaluate if this would be worth adding more branches. |
| if (base->isSubstring()) { |
| JSRopeString* baseRope = jsCast<JSRopeString*>(base); |
| base = baseRope->substringBase(); |
| offset = baseRope->substringOffset() + offset; |
| ASSERT(!base->isRope()); |
| } else if (base->isRope()) { |
| jsCast<JSRopeString*>(base)->resolveRope(globalObject); |
| RETURN_IF_EXCEPTION(scope, nullptr); |
| } |
| return jsSubstringOfResolved(vm, nullptr, base, offset, length); |
| } |
| |
| inline JSString* jsSubstringOfResolved(VM& vm, GCDeferralContext* deferralContext, JSString* s, unsigned offset, unsigned length) |
| { |
| ASSERT(offset <= s->length()); |
| ASSERT(length <= s->length()); |
| ASSERT(offset + length <= s->length()); |
| ASSERT(!s->isRope()); |
| if (!length) |
| return vm.smallStrings.emptyString(); |
| if (!offset && length == s->length()) |
| return s; |
| if (length == 1) { |
| auto& base = s->valueInternal(); |
| UChar character = base.characterAt(offset); |
| if (character <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(character); |
| } |
| return JSRopeString::createSubstringOfResolved(vm, deferralContext, s, offset, length); |
| } |
| |
| inline JSString* jsSubstringOfResolved(VM& vm, JSString* s, unsigned offset, unsigned length) |
| { |
| return jsSubstringOfResolved(vm, nullptr, s, offset, length); |
| } |
| |
| inline JSString* jsSubstring(JSGlobalObject* globalObject, JSString* s, unsigned offset, unsigned length) |
| { |
| return jsSubstring(getVM(globalObject), globalObject, s, offset, length); |
| } |
| |
| inline JSString* jsSubstring(VM& vm, const String& s, unsigned offset, unsigned length) |
| { |
| ASSERT(offset <= s.length()); |
| ASSERT(length <= s.length()); |
| ASSERT(offset + length <= s.length()); |
| if (!length) |
| return vm.smallStrings.emptyString(); |
| if (length == 1) { |
| UChar c = s.characterAt(offset); |
| if (c <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(c); |
| } |
| auto impl = StringImpl::createSubstringSharingImpl(*s.impl(), offset, length); |
| if (impl->isSubString()) |
| return JSString::createHasOtherOwner(vm, WTFMove(impl)); |
| return JSString::create(vm, WTFMove(impl)); |
| } |
| |
| inline JSString* jsOwnedString(VM& vm, const String& s) |
| { |
| int size = s.length(); |
| if (!size) |
| return vm.smallStrings.emptyString(); |
| if (size == 1) { |
| UChar c = s.characterAt(0); |
| if (c <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(c); |
| } |
| return JSString::createHasOtherOwner(vm, *s.impl()); |
| } |
| |
| ALWAYS_INLINE JSString* jsStringWithCache(VM& vm, const String& s) |
| { |
| StringImpl* stringImpl = s.impl(); |
| if (!stringImpl || !stringImpl->length()) |
| return jsEmptyString(vm); |
| |
| if (stringImpl->length() == 1) { |
| UChar singleCharacter = (*stringImpl)[0u]; |
| if (singleCharacter <= maxSingleCharacterString) |
| return vm.smallStrings.singleCharacterString(static_cast<unsigned char>(singleCharacter)); |
| } |
| |
| if (JSString* lastCachedString = vm.lastCachedString.get()) { |
| if (lastCachedString->tryGetValueImpl() == stringImpl) |
| return lastCachedString; |
| } |
| |
| return jsStringWithCacheSlowCase(vm, *stringImpl); |
| } |
| |
| ALWAYS_INLINE bool JSString::getStringPropertySlot(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
| { |
| VM& vm = getVM(globalObject); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (propertyName == vm.propertyNames->length) { |
| slot.setValue(this, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly, jsNumber(length())); |
| return true; |
| } |
| |
| std::optional<uint32_t> index = parseIndex(propertyName); |
| if (index && index.value() < length()) { |
| JSValue value = getIndex(globalObject, index.value()); |
| RETURN_IF_EXCEPTION(scope, false); |
| slot.setValue(this, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly, value); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| ALWAYS_INLINE bool JSString::getStringPropertySlot(JSGlobalObject* globalObject, unsigned propertyName, PropertySlot& slot) |
| { |
| VM& vm = getVM(globalObject); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (propertyName < length()) { |
| JSValue value = getIndex(globalObject, propertyName); |
| RETURN_IF_EXCEPTION(scope, false); |
| slot.setValue(this, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly, value); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| inline bool isJSString(JSCell* cell) |
| { |
| return cell->type() == StringType; |
| } |
| |
| inline bool isJSString(JSValue v) |
| { |
| return v.isCell() && isJSString(v.asCell()); |
| } |
| |
| ALWAYS_INLINE StringView JSRopeString::unsafeView(JSGlobalObject* globalObject) const |
| { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isSubstring()) { |
| auto& base = substringBase()->valueInternal(); |
| if (base.is8Bit()) |
| return StringView(base.characters8() + substringOffset(), length()); |
| return StringView(base.characters16() + substringOffset(), length()); |
| } |
| return resolveRope(globalObject); |
| } |
| |
| ALWAYS_INLINE StringViewWithUnderlyingString JSRopeString::viewWithUnderlyingString(JSGlobalObject* globalObject) const |
| { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isSubstring()) { |
| auto& base = substringBase()->valueInternal(); |
| if (base.is8Bit()) |
| return { { base.characters8() + substringOffset(), length() }, base }; |
| return { { base.characters16() + substringOffset(), length() }, base }; |
| } |
| auto& string = resolveRope(globalObject); |
| return { string, string }; |
| } |
| |
| ALWAYS_INLINE StringView JSString::unsafeView(JSGlobalObject* globalObject) const |
| { |
| if constexpr (validateDFGDoesGC) |
| vm().verifyCanGC(); |
| if (isRope()) |
| return static_cast<const JSRopeString*>(this)->unsafeView(globalObject); |
| return valueInternal(); |
| } |
| |
| ALWAYS_INLINE StringViewWithUnderlyingString JSString::viewWithUnderlyingString(JSGlobalObject* globalObject) const |
| { |
| if (isRope()) |
| return static_cast<const JSRopeString&>(*this).viewWithUnderlyingString(globalObject); |
| return { valueInternal(), valueInternal() }; |
| } |
| |
| inline bool JSString::isSubstring() const |
| { |
| return fiberConcurrently() & JSRopeString::isSubstringInPointer; |
| } |
| |
| } // namespace JSC |