FakeString.h (9714B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_FakeString_h__ 8 #define mozilla_dom_FakeString_h__ 9 10 #include "js/String.h" 11 #include "mozilla/RefPtr.h" 12 #include "mozilla/Span.h" 13 #include "mozilla/StringBuffer.h" 14 #include "nsString.h" 15 #include "nsTStringRepr.h" 16 17 namespace mozilla::dom::binding_detail { 18 // A struct that has a layout compatible with nsAString, so that 19 // reinterpret-casting a FakeString as a const nsAString is safe, but much 20 // faster constructor and destructor behavior. FakeString uses inline storage 21 // for small strings and an StringBuffer for longer strings. It can also 22 // point to a literal (static-lifetime) string that's compiled into the binary, 23 // or point at the buffer of an nsAString whose lifetime is longer than that of 24 // the FakeString. 25 template <typename CharT> 26 struct MOZ_GSL_OWNER FakeString { 27 using char_type = CharT; 28 using string_type = nsTString<CharT>; 29 using size_type = typename string_type::size_type; 30 using DataFlags = typename string_type::DataFlags; 31 using ClassFlags = typename string_type::ClassFlags; 32 using AString = nsTSubstring<CharT>; 33 using LengthStorage = mozilla::detail::nsTStringLengthStorage<CharT>; 34 35 static const size_t kInlineCapacity = 64; 36 using AutoString = nsTAutoStringN<CharT, kInlineCapacity>; 37 38 FakeString() 39 : mDataFlags(DataFlags::TERMINATED), 40 mClassFlags(ClassFlags::INLINE), 41 mInlineCapacity(kInlineCapacity - 1) {} 42 43 ~FakeString() { 44 if (mDataFlags & DataFlags::REFCOUNTED) { 45 MOZ_ASSERT(mDataInitialized); 46 StringBuffer::FromData(mData)->Release(); 47 } 48 } 49 50 // Share aString's string buffer, if it has one; otherwise, make this string 51 // depend upon aString's data. aString should outlive this instance of 52 // FakeString. 53 void ShareOrDependUpon(const AString& aString) { 54 RefPtr<StringBuffer> sharedBuffer = aString.GetStringBuffer(); 55 if (!sharedBuffer) { 56 InitData(aString.BeginReading(), aString.Length()); 57 if (!aString.IsTerminated()) { 58 mDataFlags &= ~DataFlags::TERMINATED; 59 } 60 } else { 61 AssignFromStringBuffer(sharedBuffer.forget(), aString.Length()); 62 } 63 } 64 65 void Truncate() { InitData(string_type::char_traits::sEmptyBuffer, 0); } 66 67 void SetIsVoid(bool aValue) { 68 MOZ_ASSERT(aValue, "We don't support SetIsVoid(false) on FakeString!"); 69 Truncate(); 70 mDataFlags |= DataFlags::VOIDED; 71 } 72 73 char_type* BeginWriting() { 74 MOZ_ASSERT(IsMutable()); 75 MOZ_ASSERT(mDataInitialized); 76 return mData; 77 } 78 79 size_type Length() const { return mLength; } 80 81 operator mozilla::Span<const char_type>() const { 82 MOZ_ASSERT(mDataInitialized); 83 // Explicitly specify template argument here to avoid instantiating 84 // Span<char_type> first and then implicitly converting to Span<const 85 // char_type> 86 return mozilla::Span<const char_type>{mData, Length()}; 87 } 88 89 mozilla::Result<mozilla::BulkWriteHandle<CharT>, nsresult> BulkWrite( 90 size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) { 91 MOZ_ASSERT(!mDataInitialized); 92 InitData(mStorage, 0); 93 mDataFlags |= DataFlags::INLINE; 94 return ToAStringPtr()->BulkWrite(aCapacity, aPrefixToPreserve, 95 aAllowShrinking); 96 } 97 98 // Reserve space to write aLength chars, not including null-terminator. 99 bool SetLength(size_type aLength, mozilla::fallible_t const&) { 100 // Use mStorage for small strings. 101 if (aLength < kInlineCapacity) { 102 InitData(mStorage, aLength); 103 mDataFlags |= DataFlags::INLINE; 104 } else { 105 RefPtr<StringBuffer> buf = 106 StringBuffer::Alloc((aLength + 1) * sizeof(char_type)); 107 if (MOZ_UNLIKELY(!buf)) { 108 return false; 109 } 110 111 AssignFromStringBuffer(buf.forget(), aLength); 112 } 113 114 MOZ_ASSERT(mDataInitialized); 115 mData[mLength] = char_type(0); 116 return true; 117 } 118 119 // Returns false on allocation failure. 120 bool EnsureMutable() { 121 MOZ_ASSERT(mDataInitialized); 122 123 if (IsMutable()) { 124 return true; 125 } 126 127 RefPtr<StringBuffer> buffer; 128 if (mDataFlags & DataFlags::REFCOUNTED) { 129 // Make sure we'll drop it when we're done. 130 buffer = dont_AddRef(StringBuffer::FromData(mData)); 131 // And make sure we don't release it twice by accident. 132 } 133 const char_type* oldChars = mData; 134 135 mDataFlags = DataFlags::TERMINATED; 136 #ifdef DEBUG 137 // Reset mDataInitialized because we're explicitly reinitializing 138 // it via the SetLength call. 139 mDataInitialized = false; 140 #endif // DEBUG 141 // SetLength will make sure we have our own buffer to work with. Note that 142 // we may be transitioning from having a (short) readonly stringbuffer to 143 // our inline storage or whatnot. That's all fine; SetLength is responsible 144 // for setting up our flags correctly. 145 if (!SetLength(Length(), fallible)) { 146 return false; 147 } 148 MOZ_ASSERT(oldChars != mData, "Should have new chars now!"); 149 MOZ_ASSERT(IsMutable(), "Why are we still not mutable?"); 150 memcpy(mData, oldChars, Length() * sizeof(char_type)); 151 return true; 152 } 153 154 void AssignFromStringBuffer(already_AddRefed<StringBuffer> aBuffer, 155 size_t aLength) { 156 InitData(static_cast<char_type*>(aBuffer.take()->Data()), aLength); 157 mDataFlags |= DataFlags::REFCOUNTED; 158 } 159 160 // The preferred way to assign literals to a FakeString. This should only be 161 // called with actual C++ literal strings (i.e. u"stuff") or character arrays 162 // that originally come from passing such literal strings. 163 template <int N> 164 void AssignLiteral(const char_type (&aData)[N]) { 165 AssignLiteral(aData, N - 1); 166 } 167 168 // Assign a literal to a FakeString when it's not an actual literal 169 // in the code, but is known to be a literal somehow (e.g. it came 170 // from an nsAString that tested true for IsLiteral()). 171 void AssignLiteral(const char_type* aData, size_t aLength) { 172 InitData(aData, aLength); 173 mDataFlags |= DataFlags::LITERAL; 174 } 175 176 // If this ever changes, change the corresponding code in the 177 // Optional<nsA[C]String> specialization as well. 178 const AString* ToAStringPtr() const { 179 return reinterpret_cast<const string_type*>(this); 180 } 181 182 operator const AString&() const { return *ToAStringPtr(); } 183 184 private: 185 AString* ToAStringPtr() { return reinterpret_cast<string_type*>(this); } 186 187 // mData is left uninitialized for optimization purposes. 188 MOZ_INIT_OUTSIDE_CTOR char_type* mData; 189 // mLength is left uninitialized for optimization purposes. 190 MOZ_INIT_OUTSIDE_CTOR uint32_t mLength; 191 DataFlags mDataFlags; 192 const ClassFlags mClassFlags; 193 194 const uint32_t mInlineCapacity; 195 char_type mStorage[kInlineCapacity]; 196 #ifdef DEBUG 197 bool mDataInitialized = false; 198 #endif // DEBUG 199 200 FakeString(const FakeString& other) = delete; 201 void operator=(const FakeString& other) = delete; 202 203 void InitData(const char_type* aData, size_type aLength) { 204 MOZ_ASSERT(aLength <= LengthStorage::kMax, "string is too large"); 205 MOZ_ASSERT(mDataFlags == DataFlags::TERMINATED); 206 MOZ_ASSERT(!mDataInitialized); 207 mData = const_cast<char_type*>(aData); 208 mLength = uint32_t(aLength); 209 #ifdef DEBUG 210 mDataInitialized = true; 211 #endif // DEBUG 212 } 213 214 bool IsMutable() { 215 return (mDataFlags & DataFlags::INLINE) || 216 ((mDataFlags & DataFlags::REFCOUNTED) && 217 !StringBuffer::FromData(mData)->IsReadonly()); 218 } 219 220 friend class NonNull<AString>; 221 222 // A class to use for our static asserts to ensure our object layout 223 // matches that of nsString. 224 class StringAsserter; 225 friend class StringAsserter; 226 227 class StringAsserter : public AutoString { 228 public: 229 static void StaticAsserts() { 230 static_assert(sizeof(AutoString) == sizeof(FakeString), 231 "Should be binary compatible with nsTAutoString"); 232 static_assert( 233 offsetof(FakeString, mInlineCapacity) == sizeof(string_type), 234 "FakeString should include all nsString members"); 235 static_assert( 236 offsetof(FakeString, mData) == offsetof(StringAsserter, mData), 237 "Offset of mData should match"); 238 static_assert( 239 offsetof(FakeString, mLength) == offsetof(StringAsserter, mLength), 240 "Offset of mLength should match"); 241 static_assert(offsetof(FakeString, mDataFlags) == 242 offsetof(StringAsserter, mDataFlags), 243 "Offset of mDataFlags should match"); 244 static_assert(offsetof(FakeString, mClassFlags) == 245 offsetof(StringAsserter, mClassFlags), 246 "Offset of mClassFlags should match"); 247 static_assert(offsetof(FakeString, mInlineCapacity) == 248 offsetof(StringAsserter, mInlineCapacity), 249 "Offset of mInlineCapacity should match"); 250 static_assert( 251 offsetof(FakeString, mStorage) == offsetof(StringAsserter, mStorage), 252 "Offset of mStorage should match"); 253 static_assert(JS::MaxStringLength <= LengthStorage::kMax, 254 "JS::MaxStringLength fits in a nsTString"); 255 } 256 }; 257 }; 258 } // namespace mozilla::dom::binding_detail 259 260 namespace mozilla { 261 262 template <typename CharT> 263 inline void AssignFromStringBuffer( 264 StringBuffer* aBuffer, size_t aLength, 265 dom::binding_detail::FakeString<CharT>& aDest) { 266 aDest.AssignFromStringBuffer(do_AddRef(aBuffer), aLength); 267 } 268 269 } // namespace mozilla 270 271 #endif /* mozilla_dom_FakeString_h__ */