1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
/* -*- 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 StringBuffer_h__
#define StringBuffer_h__
#include <atomic>
#include <cstring>
#include "mozilla/CheckedInt.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Assertions.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefCounted.h"
#include "mozmemory.h"
namespace mozilla {
/**
* This structure precedes the string buffers "we" allocate. It may be the
* case that nsTAString::mData does not point to one of these special
* buffers. The mDataFlags member variable distinguishes the buffer type.
*
* When this header is in use, it enables reference counting, and capacity
* tracking. NOTE: A string buffer can be modified only if its reference
* count is 1.
*/
class StringBuffer {
private:
std::atomic<uint32_t> mRefCount;
uint32_t mStorageSize;
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(StringBuffer)
/**
* Allocates a new string buffer, with given size in bytes and a
* reference count of one. When the string buffer is no longer needed,
* it should be released via Release.
*
* It is up to the caller to set the bytes corresponding to the string
* buffer by calling the Data method to fetch the raw data pointer. Care
* must be taken to properly null terminate the character array. The
* storage size can be greater than the length of the actual string
* (i.e., it is not required that the null terminator appear in the last
* storage unit of the string buffer's data).
*
* This guarantees that StorageSize() returns aSize if the returned
* buffer is non-null. Some callers like nsAttrValue rely on it.
*
* @return new string buffer or null if out of memory.
*/
static already_AddRefed<StringBuffer> Alloc(
size_t aSize, mozilla::Maybe<arena_id_t> aArena = mozilla::Nothing()) {
MOZ_ASSERT(aSize != 0, "zero capacity allocation not allowed");
MOZ_ASSERT(sizeof(StringBuffer) + aSize <= size_t(uint32_t(-1)) &&
sizeof(StringBuffer) + aSize > aSize,
"mStorageSize will truncate");
size_t bytes = sizeof(StringBuffer) + aSize;
void* hdr = aArena ? moz_arena_malloc(*aArena, bytes) : malloc(bytes);
if (!hdr) {
return nullptr;
}
return ConstructInPlace(hdr, aSize);
}
/**
* Like Alloc, but use aBuffer instead of allocating a new buffer. This can
* be used when the caller already has a malloced buffer of the right size and
* allocating a new one would be too expensive.
*
* aStorageSize must be the string's length in bytes (including the null
* terminator). The caller must initialize all of these bytes either before or
* after calling this function.
*
* @return the new StringBuffer header.
*/
static already_AddRefed<StringBuffer> ConstructInPlace(void* aBuffer,
size_t aStorageSize) {
MOZ_ASSERT(aBuffer, "must have a valid buffer");
MOZ_ASSERT(aStorageSize != 0, "zero capacity StringBuffer not allowed");
auto* hdr = new (aBuffer) StringBuffer();
hdr->mRefCount = 1;
hdr->mStorageSize = aStorageSize;
detail::RefCountLogger::logAddRef(hdr, 1);
return already_AddRefed(hdr);
}
/**
* Returns true if (aLength + 1) * sizeof(CharT) is a valid allocation size
* for Alloc. Adds +1 to aLength for the null-terminator.
*/
template <typename CharT>
static constexpr bool IsValidLength(size_t aLength) {
auto checkedSize =
(CheckedUint32(aLength) + 1) * sizeof(CharT) + sizeof(StringBuffer);
return checkedSize.isValid();
}
/**
* Returns a string buffer initialized with the given string on it, or null on
* OOM.
* Note that this will allocate extra space for the trailing null byte, which
* this method will add.
*/
static already_AddRefed<StringBuffer> Create(const char16_t* aData,
size_t aLength) {
return DoCreate(aData, aLength);
}
static already_AddRefed<StringBuffer> Create(const char* aData,
size_t aLength) {
return DoCreate(aData, aLength);
}
static already_AddRefed<StringBuffer> Create(const unsigned char* aData,
size_t aLength) {
return DoCreate(aData, aLength);
}
/**
* Resizes the given string buffer to the specified storage size. This
* method must not be called on a readonly string buffer. Use this API
* carefully!!
*
* This method behaves like the ANSI-C realloc function. (i.e., If the
* allocation fails, null will be returned and the given string buffer
* will remain unmodified.)
*
* @see IsReadonly
*/
static StringBuffer* Realloc(
StringBuffer* aHdr, size_t aSize,
mozilla::Maybe<arena_id_t> aArena = mozilla::Nothing()) {
MOZ_ASSERT(aSize != 0, "zero capacity allocation not allowed");
MOZ_ASSERT(sizeof(StringBuffer) + aSize <= size_t(uint32_t(-1)) &&
sizeof(StringBuffer) + aSize > aSize,
"mStorageSize will truncate");
// no point in trying to save ourselves if we hit this assertion
MOZ_ASSERT(!aHdr->IsReadonly(), "|Realloc| attempted on readonly string");
// Treat this as a release and addref for refcounting purposes, since we
// just asserted that the refcount is 1. If we don't do that, refcount
// logging will claim we've leaked all sorts of stuff.
{
detail::RefCountLogger::ReleaseLogger logger(aHdr);
logger.logRelease(0);
}
size_t bytes = sizeof(StringBuffer) + aSize;
aHdr = aArena ? (StringBuffer*)moz_arena_realloc(*aArena, aHdr, bytes)
: (StringBuffer*)realloc(aHdr, bytes);
if (aHdr) {
detail::RefCountLogger::logAddRef(aHdr, 1);
aHdr->mStorageSize = aSize;
}
return aHdr;
}
void AddRef() {
// Memory synchronization is not required when incrementing a
// reference count. The first increment of a reference count on a
// thread is not important, since the first use of the object on a
// thread can happen before it. What is important is the transfer
// of the pointer to that thread, which may happen prior to the
// first increment on that thread. The necessary memory
// synchronization is done by the mechanism that transfers the
// pointer between threads.
uint32_t count = mRefCount.fetch_add(1, std::memory_order_relaxed) + 1;
detail::RefCountLogger::logAddRef(this, count);
}
void Release() {
// Since this may be the last release on this thread, we need release
// semantics so that prior writes on this thread are visible to the thread
// that destroys the object when it reads mValue with acquire semantics.
detail::RefCountLogger::ReleaseLogger logger(this);
uint32_t count = mRefCount.fetch_sub(1, std::memory_order_release) - 1;
logger.logRelease(count);
if (count == 0) {
// We're going to destroy the object on this thread, so we need acquire
// semantics to synchronize with the memory released by the last release
// on other threads, that is, to ensure that writes prior to that release
// are now visible on this thread.
count = mRefCount.load(std::memory_order_acquire);
free(this); // We were allocated with malloc.
}
}
/**
* This method returns the string buffer corresponding to the given data
* pointer. The data pointer must have been returned previously by a
* call to the StringBuffer::Data method.
*/
static StringBuffer* FromData(void* aData) {
return reinterpret_cast<StringBuffer*>(aData) - 1;
}
/**
* This method returns the data pointer for this string buffer.
*/
void* Data() const {
return const_cast<char*>(reinterpret_cast<const char*>(this + 1));
}
/**
* This function returns the storage size of a string buffer in bytes.
* This value is the same value that was originally passed to Alloc (or
* Realloc).
*/
uint32_t StorageSize() const { return mStorageSize; }
/**
* This function returns the allocation size of a string buffer in bytes.
* This includes the size of the StringBuffer header.
*/
uint32_t AllocationSize() const {
return sizeof(StringBuffer) + StorageSize();
}
/**
* If this method returns false, then the caller can be sure that their
* reference to the string buffer is the only reference to the string
* buffer, and therefore it has exclusive access to the string buffer and
* associated data. However, if this function returns true, then other
* consumers may rely on the data in this buffer being immutable and
* other threads may access this buffer simultaneously.
*/
bool IsReadonly() const {
// This doesn't lead to the destruction of the buffer, so we don't
// need to perform acquire memory synchronization for the normal
// reason that a reference count needs acquire synchronization
// (ensuring that all writes to the object made on other threads are
// visible to the thread destroying the object).
//
// We then need to consider the possibility that there were prior
// writes to the buffer on a different thread: one that has either
// since released its reference count, or one that also has access
// to this buffer through the same reference. There are two ways
// for that to happen: either the buffer pointer or a data structure
// (e.g., string object) pointing to the buffer was transferred from
// one thread to another, or the data structure pointing to the
// buffer was already visible on both threads. In the first case
// (transfer), the transfer of data from one thread to another would
// have handled the memory synchronization. In the latter case
// (data structure visible on both threads), the caller needed some
// sort of higher level memory synchronization to protect against
// the string object being mutated at the same time on multiple
// threads.
// See bug 1603504. TSan might complain about a race when using
// memory_order_relaxed, so use memory_order_acquire for making TSan
// happy.
#if defined(MOZ_TSAN)
return mRefCount.load(std::memory_order_acquire) > 1;
#else
return mRefCount.load(std::memory_order_relaxed) > 1;
#endif
}
/**
* Alias for IsReadOnly.
*/
bool HasMultipleReferences() const { return IsReadonly(); }
#ifdef DEBUG
/**
* Returns the buffer's reference count. This is only exposed for logging and
* testing purposes.
*/
uint32_t RefCount() const {
return mRefCount.load(std::memory_order_acquire);
}
#endif
/**
* This measures the size only if the StringBuffer is unshared.
*/
size_t SizeOfIncludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const {
return IsReadonly() ? 0 : aMallocSizeOf(this);
}
/**
* This measures the size regardless of whether the StringBuffer is
* unshared.
*
* WARNING: Only use this if you really know what you are doing, because
* it can easily lead to double-counting strings. If you do use them,
* please explain clearly in a comment why it's safe and won't lead to
* double-counting.
*/
size_t SizeOfIncludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) const {
return aMallocSizeOf(this);
}
private:
template <typename CharT>
static already_AddRefed<StringBuffer> DoCreate(const CharT* aData,
size_t aLength) {
StringBuffer* buffer = Alloc((aLength + 1) * sizeof(CharT)).take();
if (MOZ_LIKELY(buffer)) {
auto* data = reinterpret_cast<CharT*>(buffer->Data());
memcpy(data, aData, aLength * sizeof(CharT));
data[aLength] = 0;
}
return already_AddRefed(buffer);
}
};
} // namespace mozilla
#endif
|