| /* |
| * Copyright (C) 2009-2019 Apple Inc. All rights reserved. |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #pragma once |
| |
| #include <wtf/CheckedArithmetic.h> |
| #include <wtf/text/AtomString.h> |
| #include <wtf/text/IntegerToStringConversion.h> |
| #include <wtf/text/StringConcatenateNumbers.h> |
| #include <wtf/text/StringView.h> |
| #include <wtf/text/WTFString.h> |
| |
| namespace WTF { |
| |
| // StringBuilder currently uses a Checked<int32_t, ConditionalCrashOnOverflow> for m_length. |
| // Ideally, we would want to make StringBuilder a template with an OverflowHandler parameter, and |
| // m_length can be instantiated based on that OverflowHandler instead. However, currently, we're |
| // not able to get clang to export explicitly instantiated template methods (which would be needed |
| // if we templatize StringBuilder). As a workaround, we use the ConditionalCrashOnOverflow handler |
| // instead to do a runtime check on whether it should crash on overflows or not. |
| // |
| // When clang is able to export explicitly instantiated template methods, we can templatize |
| // StringBuilder and do away with ConditionalCrashOnOverflow. |
| // See https://bugs.webkit.org/show_bug.cgi?id=191050. |
| |
| class StringBuilder { |
| // Disallow copying since it's expensive and we don't want code to do it by accident. |
| WTF_MAKE_NONCOPYABLE(StringBuilder); |
| WTF_MAKE_FAST_ALLOCATED; |
| |
| public: |
| enum class OverflowHandler { |
| CrashOnOverflow, |
| RecordOverflow |
| }; |
| |
| StringBuilder(OverflowHandler handler = OverflowHandler::CrashOnOverflow) |
| : m_bufferCharacters8(nullptr) |
| { |
| m_length.setShouldCrashOnOverflow(handler == OverflowHandler::CrashOnOverflow); |
| } |
| StringBuilder(StringBuilder&&) = default; |
| StringBuilder& operator=(StringBuilder&&) = default; |
| |
| ALWAYS_INLINE void didOverflow() { m_length.overflowed(); } |
| ALWAYS_INLINE bool hasOverflowed() const { return m_length.hasOverflowed(); } |
| ALWAYS_INLINE bool crashesOnOverflow() const { return m_length.shouldCrashOnOverflow(); } |
| |
| WTF_EXPORT_PRIVATE void appendCharacters(const UChar*, unsigned); |
| WTF_EXPORT_PRIVATE void appendCharacters(const LChar*, unsigned); |
| |
| ALWAYS_INLINE void appendCharacters(const char* characters, unsigned length) { appendCharacters(reinterpret_cast<const LChar*>(characters), length); } |
| |
| void append(const AtomString& atomString) |
| { |
| append(atomString.string()); |
| } |
| |
| void append(const String& string) |
| { |
| if (hasOverflowed()) |
| return; |
| |
| if (!string.length()) |
| return; |
| |
| // If we're appending to an empty string, and there is not a buffer (reserveCapacity has not been called) |
| // then just retain the string. |
| if (!m_length && !m_buffer) { |
| m_string = string; |
| m_length = string.length(); |
| m_is8Bit = m_string.is8Bit(); |
| return; |
| } |
| |
| if (string.is8Bit()) |
| appendCharacters(string.characters8(), string.length()); |
| else |
| appendCharacters(string.characters16(), string.length()); |
| } |
| |
| void append(const StringBuilder& other) |
| { |
| if (hasOverflowed()) |
| return; |
| if (other.hasOverflowed()) |
| return didOverflow(); |
| |
| if (!other.m_length) |
| return; |
| |
| // If we're appending to an empty string, and there is not a buffer (reserveCapacity has not been called) |
| // then just retain the string. |
| if (!m_length && !m_buffer && !other.m_string.isNull()) { |
| m_string = other.m_string; |
| m_length = other.m_length; |
| m_is8Bit = other.m_is8Bit; |
| return; |
| } |
| |
| if (other.is8Bit()) |
| appendCharacters(other.characters8(), other.m_length.unsafeGet()); |
| else |
| appendCharacters(other.characters16(), other.m_length.unsafeGet()); |
| } |
| |
| void append(StringView stringView) |
| { |
| if (stringView.is8Bit()) |
| appendCharacters(stringView.characters8(), stringView.length()); |
| else |
| appendCharacters(stringView.characters16(), stringView.length()); |
| } |
| |
| #if USE(CF) |
| WTF_EXPORT_PRIVATE void append(CFStringRef); |
| #endif |
| #if USE(CF) && defined(__OBJC__) |
| void append(NSString *string) { append((__bridge CFStringRef)string); } |
| #endif |
| |
| void appendSubstring(const String& string, unsigned offset, unsigned length = String::MaxLength) |
| { |
| if (offset >= string.length()) |
| return; |
| |
| unsigned clampedLength = std::min(length, string.length() - offset); |
| if (string.is8Bit()) |
| appendCharacters(string.characters8() + offset, clampedLength); |
| else |
| appendCharacters(string.characters16() + offset, clampedLength); |
| } |
| |
| void append(const char* characters) |
| { |
| if (characters) |
| appendCharacters(characters, strlen(characters)); |
| } |
| |
| void appendCharacter(UChar) = delete; |
| void append(UChar c) |
| { |
| if (hasOverflowed()) |
| return; |
| unsigned length = m_length.unsafeGet<unsigned>(); |
| if (m_buffer && length < m_buffer->length() && m_string.isNull()) { |
| if (!m_is8Bit) { |
| m_bufferCharacters16[length] = c; |
| m_length++; |
| return; |
| } |
| |
| if (isLatin1(c)) { |
| m_bufferCharacters8[length] = static_cast<LChar>(c); |
| m_length++; |
| return; |
| } |
| } |
| appendCharacters(&c, 1); |
| } |
| |
| void appendCharacter(LChar) = delete; |
| void append(LChar c) |
| { |
| if (hasOverflowed()) |
| return; |
| unsigned length = m_length.unsafeGet<unsigned>(); |
| if (m_buffer && length < m_buffer->length() && m_string.isNull()) { |
| if (m_is8Bit) |
| m_bufferCharacters8[length] = c; |
| else |
| m_bufferCharacters16[length] = c; |
| m_length++; |
| } else |
| appendCharacters(&c, 1); |
| } |
| |
| void appendCharacter(char) = delete; |
| void append(char c) |
| { |
| append(static_cast<LChar>(c)); |
| } |
| |
| void appendCharacter(UChar32 c) |
| { |
| if (U_IS_BMP(c)) { |
| append(static_cast<UChar>(c)); |
| return; |
| } |
| append(U16_LEAD(c)); |
| append(U16_TRAIL(c)); |
| } |
| |
| WTF_EXPORT_PRIVATE void appendQuotedJSONString(const String&); |
| |
| template<unsigned characterCount> |
| ALWAYS_INLINE void appendLiteral(const char (&characters)[characterCount]) { appendCharacters(characters, characterCount - 1); } |
| |
| WTF_EXPORT_PRIVATE void appendNumber(int); |
| WTF_EXPORT_PRIVATE void appendNumber(unsigned); |
| WTF_EXPORT_PRIVATE void appendNumber(long); |
| WTF_EXPORT_PRIVATE void appendNumber(unsigned long); |
| WTF_EXPORT_PRIVATE void appendNumber(long long); |
| WTF_EXPORT_PRIVATE void appendNumber(unsigned long long); |
| WTF_EXPORT_PRIVATE void appendNumber(float); |
| WTF_EXPORT_PRIVATE void appendNumber(double); |
| |
| template<typename... StringTypes> void append(StringTypes...); |
| |
| String toString() |
| { |
| if (!m_string.isNull()) { |
| ASSERT(!m_buffer || m_isReified); |
| ASSERT(!hasOverflowed()); |
| return m_string; |
| } |
| |
| RELEASE_ASSERT(!hasOverflowed()); |
| shrinkToFit(); |
| reifyString(); |
| return m_string; |
| } |
| |
| const String& toStringPreserveCapacity() const |
| { |
| RELEASE_ASSERT(!hasOverflowed()); |
| if (m_string.isNull()) |
| reifyString(); |
| return m_string; |
| } |
| |
| AtomString toAtomString() const |
| { |
| RELEASE_ASSERT(!hasOverflowed()); |
| if (!m_length) |
| return emptyAtom(); |
| |
| // If the buffer is sufficiently over-allocated, make a new AtomString from a copy so its buffer is not so large. |
| if (canShrink()) { |
| if (is8Bit()) |
| return AtomString(characters8(), length()); |
| return AtomString(characters16(), length()); |
| } |
| |
| if (!m_string.isNull()) |
| return AtomString(m_string); |
| |
| ASSERT(m_buffer); |
| return AtomString(m_buffer.get(), 0, m_length.unsafeGet()); |
| } |
| |
| unsigned length() const |
| { |
| RELEASE_ASSERT(!hasOverflowed()); |
| return m_length.unsafeGet(); |
| } |
| |
| bool isEmpty() const { return !m_length; } |
| |
| WTF_EXPORT_PRIVATE void reserveCapacity(unsigned newCapacity); |
| |
| unsigned capacity() const |
| { |
| RELEASE_ASSERT(!hasOverflowed()); |
| return m_buffer ? m_buffer->length() : m_length.unsafeGet(); |
| } |
| |
| WTF_EXPORT_PRIVATE void resize(unsigned newSize); |
| |
| WTF_EXPORT_PRIVATE bool canShrink() const; |
| |
| WTF_EXPORT_PRIVATE void shrinkToFit(); |
| |
| UChar operator[](unsigned i) const |
| { |
| RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!hasOverflowed() && i < m_length.unsafeGet<unsigned>()); |
| if (m_is8Bit) |
| return characters8()[i]; |
| return characters16()[i]; |
| } |
| |
| const LChar* characters8() const |
| { |
| ASSERT(m_is8Bit); |
| if (!m_length) |
| return nullptr; |
| if (!m_string.isNull()) |
| return m_string.characters8(); |
| ASSERT(m_buffer); |
| return m_buffer->characters8(); |
| } |
| |
| const UChar* characters16() const |
| { |
| ASSERT(!m_is8Bit); |
| if (!m_length) |
| return nullptr; |
| if (!m_string.isNull()) |
| return m_string.characters16(); |
| ASSERT(m_buffer); |
| return m_buffer->characters16(); |
| } |
| |
| bool is8Bit() const { return m_is8Bit; } |
| |
| void clear() |
| { |
| m_length = 0; |
| m_string = String(); |
| m_buffer = nullptr; |
| m_bufferCharacters8 = nullptr; |
| m_is8Bit = true; |
| } |
| |
| void swap(StringBuilder& stringBuilder) |
| { |
| std::swap(m_length, stringBuilder.m_length); |
| m_string.swap(stringBuilder.m_string); |
| m_buffer.swap(stringBuilder.m_buffer); |
| std::swap(m_is8Bit, stringBuilder.m_is8Bit); |
| std::swap(m_bufferCharacters8, stringBuilder.m_bufferCharacters8); |
| ASSERT(!m_buffer || hasOverflowed() || m_buffer->length() >= m_length.unsafeGet<unsigned>()); |
| } |
| |
| private: |
| void allocateBuffer(const LChar* currentCharacters, unsigned requiredLength); |
| void allocateBuffer(const UChar* currentCharacters, unsigned requiredLength); |
| void allocateBufferUpConvert(const LChar* currentCharacters, unsigned requiredLength); |
| template<typename CharacterType> void reallocateBuffer(unsigned requiredLength); |
| template<typename CharacterType> ALWAYS_INLINE CharacterType* extendBufferForAppending(unsigned additionalLength); |
| template<typename CharacterType> ALWAYS_INLINE CharacterType* extendBufferForAppendingWithoutOverflowCheck(CheckedInt32 requiredLength); |
| template<typename CharacterType> CharacterType* extendBufferForAppendingSlowCase(unsigned requiredLength); |
| WTF_EXPORT_PRIVATE LChar* extendBufferForAppending8(CheckedInt32 requiredLength); |
| WTF_EXPORT_PRIVATE UChar* extendBufferForAppending16(CheckedInt32 requiredLength); |
| |
| template<typename CharacterType> ALWAYS_INLINE CharacterType* getBufferCharacters(); |
| WTF_EXPORT_PRIVATE void reifyString() const; |
| |
| template<typename... StringTypeAdapters> void appendFromAdapters(StringTypeAdapters...); |
| |
| mutable String m_string; |
| RefPtr<StringImpl> m_buffer; |
| union { |
| LChar* m_bufferCharacters8; |
| UChar* m_bufferCharacters16; |
| }; |
| static_assert(String::MaxLength == std::numeric_limits<int32_t>::max(), ""); |
| Checked<int32_t, ConditionalCrashOnOverflow> m_length; |
| bool m_is8Bit { true }; |
| #if ASSERT_ENABLED |
| mutable bool m_isReified { false }; |
| #endif |
| }; |
| |
| template<> |
| ALWAYS_INLINE LChar* StringBuilder::getBufferCharacters<LChar>() |
| { |
| ASSERT(m_is8Bit); |
| return m_bufferCharacters8; |
| } |
| |
| template<> |
| ALWAYS_INLINE UChar* StringBuilder::getBufferCharacters<UChar>() |
| { |
| ASSERT(!m_is8Bit); |
| return m_bufferCharacters16; |
| } |
| |
| template<typename... StringTypeAdapters> |
| void StringBuilder::appendFromAdapters(StringTypeAdapters... adapters) |
| { |
| auto requiredLength = checkedSum<int32_t>(m_length, adapters.length()...); |
| if (m_is8Bit && are8Bit(adapters...)) { |
| LChar* destination = extendBufferForAppending8(requiredLength); |
| if (!destination) { |
| ASSERT(hasOverflowed()); |
| return; |
| } |
| stringTypeAdapterAccumulator(destination, adapters...); |
| } else { |
| UChar* destination = extendBufferForAppending16(requiredLength); |
| if (!destination) { |
| ASSERT(hasOverflowed()); |
| return; |
| } |
| stringTypeAdapterAccumulator(destination, adapters...); |
| } |
| } |
| |
| template<typename... StringTypes> |
| void StringBuilder::append(StringTypes... strings) |
| { |
| appendFromAdapters(StringTypeAdapter<StringTypes>(strings)...); |
| } |
| |
| template<typename CharacterType> |
| bool equal(const StringBuilder& s, const CharacterType* buffer, unsigned length) |
| { |
| if (s.length() != length) |
| return false; |
| |
| if (s.is8Bit()) |
| return equal(s.characters8(), buffer, length); |
| |
| return equal(s.characters16(), buffer, length); |
| } |
| |
| template<typename StringType> |
| bool equal(const StringBuilder& a, const StringType& b) |
| { |
| if (a.length() != b.length()) |
| return false; |
| |
| if (!a.length()) |
| return true; |
| |
| if (a.is8Bit()) { |
| if (b.is8Bit()) |
| return equal(a.characters8(), b.characters8(), a.length()); |
| return equal(a.characters8(), b.characters16(), a.length()); |
| } |
| |
| if (b.is8Bit()) |
| return equal(a.characters16(), b.characters8(), a.length()); |
| return equal(a.characters16(), b.characters16(), a.length()); |
| } |
| |
| inline bool operator==(const StringBuilder& a, const StringBuilder& b) { return equal(a, b); } |
| inline bool operator!=(const StringBuilder& a, const StringBuilder& b) { return !equal(a, b); } |
| inline bool operator==(const StringBuilder& a, const String& b) { return equal(a, b); } |
| inline bool operator!=(const StringBuilder& a, const String& b) { return !equal(a, b); } |
| inline bool operator==(const String& a, const StringBuilder& b) { return equal(b, a); } |
| inline bool operator!=(const String& a, const StringBuilder& b) { return !equal(b, a); } |
| |
| template<> struct IntegerToStringConversionTrait<StringBuilder> { |
| using ReturnType = void; |
| using AdditionalArgumentType = StringBuilder; |
| static void flush(LChar* characters, unsigned length, StringBuilder* stringBuilder) { stringBuilder->appendCharacters(characters, length); } |
| }; |
| |
| } // namespace WTF |
| |
| using WTF::StringBuilder; |