/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_FakeString_h__ #define mozilla_dom_FakeString_h__ #include "nsString.h" #include "nsStringBuffer.h" #include "mozilla/RefPtr.h" #include "mozilla/Span.h" #include "js/String.h" #include "nsTStringRepr.h" namespace mozilla::dom::binding_detail { // A struct that has a layout compatible with nsAString, so that // reinterpret-casting a FakeString as a const nsAString is safe, but much // faster constructor and destructor behavior. FakeString uses inline storage // for small strings and an nsStringBuffer for longer strings. It can also // point to a literal (static-lifetime) string that's compiled into the binary, // or point at the buffer of an nsAString whose lifetime is longer than that of // the FakeString. template struct FakeString { using char_type = CharT; using string_type = nsTString; using size_type = typename string_type::size_type; using DataFlags = typename string_type::DataFlags; using ClassFlags = typename string_type::ClassFlags; using AString = nsTSubstring; using LengthStorage = mozilla::detail::nsTStringLengthStorage; static const size_t kInlineCapacity = 64; using AutoString = nsTAutoStringN; FakeString() : mDataFlags(DataFlags::TERMINATED), mClassFlags(ClassFlags::INLINE), mInlineCapacity(kInlineCapacity - 1) {} ~FakeString() { if (mDataFlags & DataFlags::REFCOUNTED) { MOZ_ASSERT(mDataInitialized); nsStringBuffer::FromData(mData)->Release(); } } // Share aString's string buffer, if it has one; otherwise, make this string // depend upon aString's data. aString should outlive this instance of // FakeString. void ShareOrDependUpon(const AString& aString) { RefPtr sharedBuffer = nsStringBuffer::FromString(aString); if (!sharedBuffer) { InitData(aString.BeginReading(), aString.Length()); if (!aString.IsTerminated()) { mDataFlags &= ~DataFlags::TERMINATED; } } else { AssignFromStringBuffer(sharedBuffer.forget(), aString.Length()); } } void Truncate() { InitData(string_type::char_traits::sEmptyBuffer, 0); } void SetIsVoid(bool aValue) { MOZ_ASSERT(aValue, "We don't support SetIsVoid(false) on FakeString!"); Truncate(); mDataFlags |= DataFlags::VOIDED; } char_type* BeginWriting() { MOZ_ASSERT(IsMutable()); MOZ_ASSERT(mDataInitialized); return mData; } size_type Length() const { return mLength; } operator mozilla::Span() const { MOZ_ASSERT(mDataInitialized); // Explicitly specify template argument here to avoid instantiating // Span first and then implicitly converting to Span return mozilla::Span{mData, Length()}; } mozilla::Result, nsresult> BulkWrite( size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) { MOZ_ASSERT(!mDataInitialized); InitData(mStorage, 0); mDataFlags |= DataFlags::INLINE; return ToAStringPtr()->BulkWrite(aCapacity, aPrefixToPreserve, aAllowShrinking); } // Reserve space to write aLength chars, not including null-terminator. bool SetLength(size_type aLength, mozilla::fallible_t const&) { // Use mStorage for small strings. if (aLength < kInlineCapacity) { InitData(mStorage, aLength); mDataFlags |= DataFlags::INLINE; } else { RefPtr buf = nsStringBuffer::Alloc((aLength + 1) * sizeof(char_type)); if (MOZ_UNLIKELY(!buf)) { return false; } AssignFromStringBuffer(buf.forget(), aLength); } MOZ_ASSERT(mDataInitialized); mData[mLength] = char_type(0); return true; } // Returns false on allocation failure. bool EnsureMutable() { MOZ_ASSERT(mDataInitialized); if (IsMutable()) { return true; } RefPtr buffer; if (mDataFlags & DataFlags::REFCOUNTED) { // Make sure we'll drop it when we're done. buffer = dont_AddRef(nsStringBuffer::FromData(mData)); // And make sure we don't release it twice by accident. } const char_type* oldChars = mData; mDataFlags = DataFlags::TERMINATED; #ifdef DEBUG // Reset mDataInitialized because we're explicitly reinitializing // it via the SetLength call. mDataInitialized = false; #endif // DEBUG // SetLength will make sure we have our own buffer to work with. Note that // we may be transitioning from having a (short) readonly stringbuffer to // our inline storage or whatnot. That's all fine; SetLength is responsible // for setting up our flags correctly. if (!SetLength(Length(), fallible)) { return false; } MOZ_ASSERT(oldChars != mData, "Should have new chars now!"); MOZ_ASSERT(IsMutable(), "Why are we still not mutable?"); memcpy(mData, oldChars, Length() * sizeof(char_type)); return true; } void AssignFromStringBuffer(already_AddRefed aBuffer, size_t aLength) { InitData(static_cast(aBuffer.take()->Data()), aLength); mDataFlags |= DataFlags::REFCOUNTED; } // The preferred way to assign literals to a FakeString. This should only be // called with actual C++ literal strings (i.e. u"stuff") or character arrays // that originally come from passing such literal strings. template void AssignLiteral(const char_type (&aData)[N]) { AssignLiteral(aData, N - 1); } // Assign a literal to a FakeString when it's not an actual literal // in the code, but is known to be a literal somehow (e.g. it came // from an nsAString that tested true for IsLiteral()). void AssignLiteral(const char_type* aData, size_t aLength) { InitData(aData, aLength); mDataFlags |= DataFlags::LITERAL; } // If this ever changes, change the corresponding code in the // Optional specialization as well. const AString* ToAStringPtr() const { return reinterpret_cast(this); } operator const AString&() const { return *ToAStringPtr(); } private: AString* ToAStringPtr() { return reinterpret_cast(this); } // mData is left uninitialized for optimization purposes. MOZ_INIT_OUTSIDE_CTOR char_type* mData; // mLength is left uninitialized for optimization purposes. MOZ_INIT_OUTSIDE_CTOR uint32_t mLength; DataFlags mDataFlags; const ClassFlags mClassFlags; const uint32_t mInlineCapacity; char_type mStorage[kInlineCapacity]; #ifdef DEBUG bool mDataInitialized = false; #endif // DEBUG FakeString(const FakeString& other) = delete; void operator=(const FakeString& other) = delete; void InitData(const char_type* aData, size_type aLength) { MOZ_ASSERT(aLength <= LengthStorage::kMax, "string is too large"); MOZ_ASSERT(mDataFlags == DataFlags::TERMINATED); MOZ_ASSERT(!mDataInitialized); mData = const_cast(aData); mLength = uint32_t(aLength); #ifdef DEBUG mDataInitialized = true; #endif // DEBUG } bool IsMutable() { return (mDataFlags & DataFlags::INLINE) || ((mDataFlags & DataFlags::REFCOUNTED) && !nsStringBuffer::FromData(mData)->IsReadonly()); } friend class NonNull; // A class to use for our static asserts to ensure our object layout // matches that of nsString. class StringAsserter; friend class StringAsserter; class StringAsserter : public AutoString { public: static void StaticAsserts() { static_assert(sizeof(AutoString) == sizeof(FakeString), "Should be binary compatible with nsTAutoString"); static_assert( offsetof(FakeString, mInlineCapacity) == sizeof(string_type), "FakeString should include all nsString members"); static_assert( offsetof(FakeString, mData) == offsetof(StringAsserter, mData), "Offset of mData should match"); static_assert( offsetof(FakeString, mLength) == offsetof(StringAsserter, mLength), "Offset of mLength should match"); static_assert(offsetof(FakeString, mDataFlags) == offsetof(StringAsserter, mDataFlags), "Offset of mDataFlags should match"); static_assert(offsetof(FakeString, mClassFlags) == offsetof(StringAsserter, mClassFlags), "Offset of mClassFlags should match"); static_assert(offsetof(FakeString, mInlineCapacity) == offsetof(StringAsserter, mInlineCapacity), "Offset of mInlineCapacity should match"); static_assert( offsetof(FakeString, mStorage) == offsetof(StringAsserter, mStorage), "Offset of mStorage should match"); static_assert(JS::MaxStringLength <= LengthStorage::kMax, "JS::MaxStringLength fits in a nsTString"); } }; }; } // namespace mozilla::dom::binding_detail template inline void AssignFromStringBuffer( nsStringBuffer* aBuffer, size_t aLength, mozilla::dom::binding_detail::FakeString& aDest) { aDest.AssignFromStringBuffer(do_AddRef(aBuffer), aLength); } #endif /* mozilla_dom_FakeString_h__ */