xpcom/glue/nsTArray-inl.h

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #ifndef nsTArray_h__
michael@0 8 # error "Don't include this file directly"
michael@0 9 #endif
michael@0 10
michael@0 11 template<class Alloc, class Copy>
michael@0 12 nsTArray_base<Alloc, Copy>::nsTArray_base()
michael@0 13 : mHdr(EmptyHdr()) {
michael@0 14 MOZ_COUNT_CTOR(nsTArray_base);
michael@0 15 }
michael@0 16
michael@0 17 template<class Alloc, class Copy>
michael@0 18 nsTArray_base<Alloc, Copy>::~nsTArray_base() {
michael@0 19 if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) {
michael@0 20 Alloc::Free(mHdr);
michael@0 21 }
michael@0 22 MOZ_COUNT_DTOR(nsTArray_base);
michael@0 23 }
michael@0 24
michael@0 25 template<class Alloc, class Copy>
michael@0 26 const nsTArrayHeader* nsTArray_base<Alloc, Copy>::GetAutoArrayBufferUnsafe(size_t elemAlign) const {
michael@0 27 // Assuming |this| points to an nsAutoArray, we want to get a pointer to
michael@0 28 // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf!
michael@0 29
michael@0 30 const void* autoBuf = &reinterpret_cast<const nsAutoArrayBase<nsTArray<uint32_t>, 1>*>(this)->mAutoBuf;
michael@0 31
michael@0 32 // If we're on a 32-bit system and elemAlign is 8, we need to adjust our
michael@0 33 // pointer to take into account the extra alignment in the auto array.
michael@0 34
michael@0 35 static_assert(sizeof(void*) != 4 ||
michael@0 36 (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 &&
michael@0 37 sizeof(nsAutoTArray<mozilla::AlignedElem<8>, 1>) ==
michael@0 38 sizeof(void*) + sizeof(nsTArrayHeader) +
michael@0 39 4 + sizeof(mozilla::AlignedElem<8>)),
michael@0 40 "auto array padding wasn't what we expected");
michael@0 41
michael@0 42 // We don't support alignments greater than 8 bytes.
michael@0 43 NS_ABORT_IF_FALSE(elemAlign <= 4 || elemAlign == 8, "unsupported alignment.");
michael@0 44 if (sizeof(void*) == 4 && elemAlign == 8) {
michael@0 45 autoBuf = reinterpret_cast<const char*>(autoBuf) + 4;
michael@0 46 }
michael@0 47
michael@0 48 return reinterpret_cast<const Header*>(autoBuf);
michael@0 49 }
michael@0 50
michael@0 51 template<class Alloc, class Copy>
michael@0 52 bool nsTArray_base<Alloc, Copy>::UsesAutoArrayBuffer() const {
michael@0 53 if (!mHdr->mIsAutoArray) {
michael@0 54 return false;
michael@0 55 }
michael@0 56
michael@0 57 // This is nuts. If we were sane, we'd pass elemAlign as a parameter to
michael@0 58 // this function. Unfortunately this function is called in nsTArray_base's
michael@0 59 // destructor, at which point we don't know elem_type's alignment.
michael@0 60 //
michael@0 61 // We'll fall on our face and return true when we should say false if
michael@0 62 //
michael@0 63 // * we're not using our auto buffer,
michael@0 64 // * elemAlign == 4, and
michael@0 65 // * mHdr == GetAutoArrayBuffer(8).
michael@0 66 //
michael@0 67 // This could happen if |*this| lives on the heap and malloc allocated our
michael@0 68 // buffer on the heap adjacent to |*this|.
michael@0 69 //
michael@0 70 // However, we can show that this can't happen. If |this| is an auto array
michael@0 71 // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8)
michael@0 72 // always points to memory owned by |*this|, because (as we assert below)
michael@0 73 //
michael@0 74 // * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), and
michael@0 75 // * sizeof(nsTArrayHeader) > 4.
michael@0 76 //
michael@0 77 // Since nsAutoTArray always contains an nsTArrayHeader,
michael@0 78 // GetAutoArrayBuffer(8) will always point inside the auto array object,
michael@0 79 // even if it doesn't point at the beginning of the header.
michael@0 80 //
michael@0 81 // Note that this means that we can't store elements with alignment 16 in an
michael@0 82 // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory
michael@0 83 // owned by this nsAutoTArray. We statically assert that elem_type's
michael@0 84 // alignment is 8 bytes or less in nsAutoArrayBase.
michael@0 85
michael@0 86 static_assert(sizeof(nsTArrayHeader) > 4,
michael@0 87 "see comment above");
michael@0 88
michael@0 89 #ifdef DEBUG
michael@0 90 ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) -
michael@0 91 reinterpret_cast<const char*>(GetAutoArrayBuffer(4));
michael@0 92 NS_ABORT_IF_FALSE(diff >= 0 && diff <= 4, "GetAutoArrayBuffer doesn't do what we expect.");
michael@0 93 #endif
michael@0 94
michael@0 95 return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8);
michael@0 96 }
michael@0 97
michael@0 98
michael@0 99 template<class Alloc, class Copy>
michael@0 100 typename Alloc::ResultTypeProxy
michael@0 101 nsTArray_base<Alloc, Copy>::EnsureCapacity(size_type capacity, size_type elemSize) {
michael@0 102 // This should be the most common case so test this first
michael@0 103 if (capacity <= mHdr->mCapacity)
michael@0 104 return Alloc::SuccessResult();
michael@0 105
michael@0 106 // If the requested memory allocation exceeds size_type(-1)/2, then
michael@0 107 // our doubling algorithm may not be able to allocate it.
michael@0 108 // Additionally we couldn't fit in the Header::mCapacity
michael@0 109 // member. Just bail out in cases like that. We don't want to be
michael@0 110 // allocating 2 GB+ arrays anyway.
michael@0 111 if ((uint64_t)capacity * elemSize > size_type(-1)/2) {
michael@0 112 Alloc::SizeTooBig((size_t)capacity * elemSize);
michael@0 113 return Alloc::FailureResult();
michael@0 114 }
michael@0 115
michael@0 116 if (mHdr == EmptyHdr()) {
michael@0 117 // Malloc() new data
michael@0 118 Header *header = static_cast<Header*>
michael@0 119 (Alloc::Malloc(sizeof(Header) + capacity * elemSize));
michael@0 120 if (!header)
michael@0 121 return Alloc::FailureResult();
michael@0 122 header->mLength = 0;
michael@0 123 header->mCapacity = capacity;
michael@0 124 header->mIsAutoArray = 0;
michael@0 125 mHdr = header;
michael@0 126
michael@0 127 return Alloc::SuccessResult();
michael@0 128 }
michael@0 129
michael@0 130 // We increase our capacity so |capacity * elemSize + sizeof(Header)| is the
michael@0 131 // next power of two, if this value is less than pageSize bytes, or otherwise
michael@0 132 // so it's the next multiple of pageSize.
michael@0 133 const uint32_t pageSizeBytes = 12;
michael@0 134 const uint32_t pageSize = 1 << pageSizeBytes;
michael@0 135
michael@0 136 uint32_t minBytes = capacity * elemSize + sizeof(Header);
michael@0 137 uint32_t bytesToAlloc;
michael@0 138 if (minBytes >= pageSize) {
michael@0 139 // Round up to the next multiple of pageSize.
michael@0 140 bytesToAlloc = pageSize * ((minBytes + pageSize - 1) / pageSize);
michael@0 141 }
michael@0 142 else {
michael@0 143 // Round up to the next power of two. See
michael@0 144 // http://graphics.stanford.edu/~seander/bithacks.html
michael@0 145 bytesToAlloc = minBytes - 1;
michael@0 146 bytesToAlloc |= bytesToAlloc >> 1;
michael@0 147 bytesToAlloc |= bytesToAlloc >> 2;
michael@0 148 bytesToAlloc |= bytesToAlloc >> 4;
michael@0 149 bytesToAlloc |= bytesToAlloc >> 8;
michael@0 150 bytesToAlloc |= bytesToAlloc >> 16;
michael@0 151 bytesToAlloc++;
michael@0 152
michael@0 153 MOZ_ASSERT((bytesToAlloc & (bytesToAlloc - 1)) == 0,
michael@0 154 "nsTArray's allocation size should be a power of two!");
michael@0 155 }
michael@0 156
michael@0 157 Header *header;
michael@0 158 if (UsesAutoArrayBuffer() || !Copy::allowRealloc) {
michael@0 159 // Malloc() and copy
michael@0 160 header = static_cast<Header*>(Alloc::Malloc(bytesToAlloc));
michael@0 161 if (!header)
michael@0 162 return Alloc::FailureResult();
michael@0 163
michael@0 164 Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize);
michael@0 165
michael@0 166 if (!UsesAutoArrayBuffer())
michael@0 167 Alloc::Free(mHdr);
michael@0 168 } else {
michael@0 169 // Realloc() existing data
michael@0 170 header = static_cast<Header*>(Alloc::Realloc(mHdr, bytesToAlloc));
michael@0 171 if (!header)
michael@0 172 return Alloc::FailureResult();
michael@0 173 }
michael@0 174
michael@0 175 // How many elements can we fit in bytesToAlloc?
michael@0 176 uint32_t newCapacity = (bytesToAlloc - sizeof(Header)) / elemSize;
michael@0 177 MOZ_ASSERT(newCapacity >= capacity, "Didn't enlarge the array enough!");
michael@0 178 header->mCapacity = newCapacity;
michael@0 179
michael@0 180 mHdr = header;
michael@0 181
michael@0 182 return Alloc::SuccessResult();
michael@0 183 }
michael@0 184
michael@0 185 template<class Alloc, class Copy>
michael@0 186 void
michael@0 187 nsTArray_base<Alloc, Copy>::ShrinkCapacity(size_type elemSize, size_t elemAlign) {
michael@0 188 if (mHdr == EmptyHdr() || UsesAutoArrayBuffer())
michael@0 189 return;
michael@0 190
michael@0 191 if (mHdr->mLength >= mHdr->mCapacity) // should never be greater than...
michael@0 192 return;
michael@0 193
michael@0 194 size_type length = Length();
michael@0 195
michael@0 196 if (IsAutoArray() && GetAutoArrayBuffer(elemAlign)->mCapacity >= length) {
michael@0 197 Header* header = GetAutoArrayBuffer(elemAlign);
michael@0 198
michael@0 199 // Copy data, but don't copy the header to avoid overwriting mCapacity
michael@0 200 header->mLength = length;
michael@0 201 Copy::CopyElements(header + 1, mHdr + 1, length, elemSize);
michael@0 202
michael@0 203 Alloc::Free(mHdr);
michael@0 204 mHdr = header;
michael@0 205 return;
michael@0 206 }
michael@0 207
michael@0 208 if (length == 0) {
michael@0 209 MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements");
michael@0 210 Alloc::Free(mHdr);
michael@0 211 mHdr = EmptyHdr();
michael@0 212 return;
michael@0 213 }
michael@0 214
michael@0 215 size_type size = sizeof(Header) + length * elemSize;
michael@0 216 void *ptr = Alloc::Realloc(mHdr, size);
michael@0 217 if (!ptr)
michael@0 218 return;
michael@0 219 mHdr = static_cast<Header*>(ptr);
michael@0 220 mHdr->mCapacity = length;
michael@0 221 }
michael@0 222
michael@0 223 template<class Alloc, class Copy>
michael@0 224 void
michael@0 225 nsTArray_base<Alloc, Copy>::ShiftData(index_type start,
michael@0 226 size_type oldLen, size_type newLen,
michael@0 227 size_type elemSize, size_t elemAlign) {
michael@0 228 if (oldLen == newLen)
michael@0 229 return;
michael@0 230
michael@0 231 // Determine how many elements need to be shifted
michael@0 232 size_type num = mHdr->mLength - (start + oldLen);
michael@0 233
michael@0 234 // Compute the resulting length of the array
michael@0 235 mHdr->mLength += newLen - oldLen;
michael@0 236 if (mHdr->mLength == 0) {
michael@0 237 ShrinkCapacity(elemSize, elemAlign);
michael@0 238 } else {
michael@0 239 // Maybe nothing needs to be shifted
michael@0 240 if (num == 0)
michael@0 241 return;
michael@0 242 // Perform shift (change units to bytes first)
michael@0 243 start *= elemSize;
michael@0 244 newLen *= elemSize;
michael@0 245 oldLen *= elemSize;
michael@0 246 char *base = reinterpret_cast<char*>(mHdr + 1) + start;
michael@0 247 Copy::MoveElements(base + newLen, base + oldLen, num, elemSize);
michael@0 248 }
michael@0 249 }
michael@0 250
michael@0 251 template<class Alloc, class Copy>
michael@0 252 bool
michael@0 253 nsTArray_base<Alloc, Copy>::InsertSlotsAt(index_type index, size_type count,
michael@0 254 size_type elementSize, size_t elemAlign) {
michael@0 255 MOZ_ASSERT(index <= Length(), "Bogus insertion index");
michael@0 256 size_type newLen = Length() + count;
michael@0 257
michael@0 258 EnsureCapacity(newLen, elementSize);
michael@0 259
michael@0 260 // Check for out of memory conditions
michael@0 261 if (Capacity() < newLen)
michael@0 262 return false;
michael@0 263
michael@0 264 // Move the existing elements as needed. Note that this will
michael@0 265 // change our mLength, so no need to call IncrementLength.
michael@0 266 ShiftData(index, 0, count, elementSize, elemAlign);
michael@0 267
michael@0 268 return true;
michael@0 269 }
michael@0 270
michael@0 271 // nsTArray_base::IsAutoArrayRestorer is an RAII class which takes
michael@0 272 // |nsTArray_base &array| in its constructor. When it's destructed, it ensures
michael@0 273 // that
michael@0 274 //
michael@0 275 // * array.mIsAutoArray has the same value as it did when we started, and
michael@0 276 // * if array has an auto buffer and mHdr would otherwise point to sEmptyHdr,
michael@0 277 // array.mHdr points to array's auto buffer.
michael@0 278
michael@0 279 template<class Alloc, class Copy>
michael@0 280 nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::IsAutoArrayRestorer(
michael@0 281 nsTArray_base<Alloc, Copy> &array,
michael@0 282 size_t elemAlign)
michael@0 283 : mArray(array),
michael@0 284 mElemAlign(elemAlign),
michael@0 285 mIsAuto(array.IsAutoArray())
michael@0 286 {
michael@0 287 }
michael@0 288
michael@0 289 template<class Alloc, class Copy>
michael@0 290 nsTArray_base<Alloc, Copy>::IsAutoArrayRestorer::~IsAutoArrayRestorer() {
michael@0 291 // Careful: We don't want to set mIsAutoArray = 1 on sEmptyHdr.
michael@0 292 if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) {
michael@0 293 // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts
michael@0 294 // that mHdr->mIsAutoArray is true, which surely isn't the case here.
michael@0 295 mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign);
michael@0 296 mArray.mHdr->mLength = 0;
michael@0 297 }
michael@0 298 else if (mArray.mHdr != mArray.EmptyHdr()) {
michael@0 299 mArray.mHdr->mIsAutoArray = mIsAuto;
michael@0 300 }
michael@0 301 }
michael@0 302
michael@0 303 template<class Alloc, class Copy>
michael@0 304 template<class Allocator>
michael@0 305 typename Alloc::ResultTypeProxy
michael@0 306 nsTArray_base<Alloc, Copy>::SwapArrayElements(nsTArray_base<Allocator, Copy>& other,
michael@0 307 size_type elemSize, size_t elemAlign) {
michael@0 308
michael@0 309 // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyHdr even if we have an
michael@0 310 // auto buffer. We need to point mHdr back to our auto buffer before we
michael@0 311 // return, otherwise we'll forget that we have an auto buffer at all!
michael@0 312 // IsAutoArrayRestorer takes care of this for us.
michael@0 313
michael@0 314 IsAutoArrayRestorer ourAutoRestorer(*this, elemAlign);
michael@0 315 typename nsTArray_base<Allocator, Copy>::IsAutoArrayRestorer otherAutoRestorer(other, elemAlign);
michael@0 316
michael@0 317 // If neither array uses an auto buffer which is big enough to store the
michael@0 318 // other array's elements, then ensure that both arrays use malloc'ed storage
michael@0 319 // and swap their mHdr pointers.
michael@0 320 if ((!UsesAutoArrayBuffer() || Capacity() < other.Length()) &&
michael@0 321 (!other.UsesAutoArrayBuffer() || other.Capacity() < Length())) {
michael@0 322
michael@0 323 if (!EnsureNotUsingAutoArrayBuffer(elemSize) ||
michael@0 324 !other.EnsureNotUsingAutoArrayBuffer(elemSize)) {
michael@0 325 return Alloc::FailureResult();
michael@0 326 }
michael@0 327
michael@0 328 Header *temp = mHdr;
michael@0 329 mHdr = other.mHdr;
michael@0 330 other.mHdr = temp;
michael@0 331
michael@0 332 return Alloc::SuccessResult();
michael@0 333 }
michael@0 334
michael@0 335 // Swap the two arrays by copying, since at least one is using an auto
michael@0 336 // buffer which is large enough to hold all of the other's elements. We'll
michael@0 337 // copy the shorter array into temporary storage.
michael@0 338 //
michael@0 339 // (We could do better than this in some circumstances. Suppose we're
michael@0 340 // swapping arrays X and Y. X has space for 2 elements in its auto buffer,
michael@0 341 // but currently has length 4, so it's using malloc'ed storage. Y has length
michael@0 342 // 2. When we swap X and Y, we don't need to use a temporary buffer; we can
michael@0 343 // write Y straight into X's auto buffer, write X's malloc'ed buffer on top
michael@0 344 // of Y, and then switch X to using its auto buffer.)
michael@0 345
michael@0 346 if (!Alloc::Successful(EnsureCapacity(other.Length(), elemSize)) ||
michael@0 347 !Allocator::Successful(other.EnsureCapacity(Length(), elemSize))) {
michael@0 348 return Alloc::FailureResult();
michael@0 349 }
michael@0 350
michael@0 351 // The EnsureCapacity calls above shouldn't have caused *both* arrays to
michael@0 352 // switch from their auto buffers to malloc'ed space.
michael@0 353 NS_ABORT_IF_FALSE(UsesAutoArrayBuffer() ||
michael@0 354 other.UsesAutoArrayBuffer(),
michael@0 355 "One of the arrays should be using its auto buffer.");
michael@0 356
michael@0 357 size_type smallerLength = XPCOM_MIN(Length(), other.Length());
michael@0 358 size_type largerLength = XPCOM_MAX(Length(), other.Length());
michael@0 359 void *smallerElements, *largerElements;
michael@0 360 if (Length() <= other.Length()) {
michael@0 361 smallerElements = Hdr() + 1;
michael@0 362 largerElements = other.Hdr() + 1;
michael@0 363 }
michael@0 364 else {
michael@0 365 smallerElements = other.Hdr() + 1;
michael@0 366 largerElements = Hdr() + 1;
michael@0 367 }
michael@0 368
michael@0 369 // Allocate temporary storage for the smaller of the two arrays. We want to
michael@0 370 // allocate this space on the stack, if it's not too large. Sounds like a
michael@0 371 // job for AutoTArray! (One of the two arrays we're swapping is using an
michael@0 372 // auto buffer, so we're likely not allocating a lot of space here. But one
michael@0 373 // could, in theory, allocate a huge AutoTArray on the heap.)
michael@0 374 nsAutoArrayBase<nsTArray_Impl<uint8_t, Alloc>, 64> temp;
michael@0 375 if (!Alloc::Successful(temp.EnsureCapacity(smallerLength, elemSize))) {
michael@0 376 return Alloc::FailureResult();
michael@0 377 }
michael@0 378
michael@0 379 Copy::CopyElements(temp.Elements(), smallerElements, smallerLength, elemSize);
michael@0 380 Copy::CopyElements(smallerElements, largerElements, largerLength, elemSize);
michael@0 381 Copy::CopyElements(largerElements, temp.Elements(), smallerLength, elemSize);
michael@0 382
michael@0 383 // Swap the arrays' lengths.
michael@0 384 NS_ABORT_IF_FALSE((other.Length() == 0 || mHdr != EmptyHdr()) &&
michael@0 385 (Length() == 0 || other.mHdr != EmptyHdr()),
michael@0 386 "Don't set sEmptyHdr's length.");
michael@0 387 size_type tempLength = Length();
michael@0 388 mHdr->mLength = other.Length();
michael@0 389 other.mHdr->mLength = tempLength;
michael@0 390
michael@0 391 return Alloc::SuccessResult();
michael@0 392 }
michael@0 393
michael@0 394 template<class Alloc, class Copy>
michael@0 395 bool
michael@0 396 nsTArray_base<Alloc, Copy>::EnsureNotUsingAutoArrayBuffer(size_type elemSize) {
michael@0 397 if (UsesAutoArrayBuffer()) {
michael@0 398
michael@0 399 // If you call this on a 0-length array, we'll set that array's mHdr to
michael@0 400 // sEmptyHdr, in flagrant violation of the nsAutoTArray invariants. It's
michael@0 401 // up to you to set it back! (If you don't, the nsAutoTArray will forget
michael@0 402 // that it has an auto buffer.)
michael@0 403 if (Length() == 0) {
michael@0 404 mHdr = EmptyHdr();
michael@0 405 return true;
michael@0 406 }
michael@0 407
michael@0 408 size_type size = sizeof(Header) + Length() * elemSize;
michael@0 409
michael@0 410 Header* header = static_cast<Header*>(Alloc::Malloc(size));
michael@0 411 if (!header)
michael@0 412 return false;
michael@0 413
michael@0 414 Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize);
michael@0 415 header->mCapacity = Length();
michael@0 416 mHdr = header;
michael@0 417 }
michael@0 418
michael@0 419 return true;
michael@0 420 }

mercurial