diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /xpcom/ds/nsTArray-inl.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/ds/nsTArray-inl.h')
-rw-r--r-- | xpcom/ds/nsTArray-inl.h | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/xpcom/ds/nsTArray-inl.h b/xpcom/ds/nsTArray-inl.h new file mode 100644 index 0000000000..801d3fab01 --- /dev/null +++ b/xpcom/ds/nsTArray-inl.h @@ -0,0 +1,691 @@ +/* -*- 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 nsTArray_h__ +# error "Don't include this file directly" +#endif + +// NOTE: We don't use MOZ_COUNT_CTOR/MOZ_COUNT_DTOR to perform leak checking of +// nsTArray_base objects intentionally for the following reasons: +// * The leak logging isn't as useful as other types of logging, as +// nsTArray_base is frequently relocated without invoking a constructor, such +// as when stored within another nsTArray. This means that +// XPCOM_MEM_LOG_CLASSES cannot be used to identify specific leaks of nsTArray +// objects. +// * The nsTArray type is layout compatible with the ThinVec crate with the +// correct flags, and ThinVec does not currently perform leak logging. +// This means that if a large number of arrays are transferred between Rust +// and C++ code using ThinVec, for example within another ThinVec, they +// will not be logged correctly and might appear as e.g. negative leaks. +// * Leaks which have been found thanks to the leak logging added by this +// type have often not been significant, and/or have needed to be +// circumvented using some other mechanism. Most leaks found with this type +// in them also include other types which will continue to be tracked. + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::nsTArray_base() : mHdr(EmptyHdr()) {} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::~nsTArray_base() { + if (!HasEmptyHeader() && !UsesAutoArrayBuffer()) { + Alloc::Free(mHdr); + } +} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::nsTArray_base(const nsTArray_base&) + : mHdr(EmptyHdr()) { + // Actual copying happens through nsTArray_CopyEnabler, we just need to do the + // initialization of mHdr. +} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>& +nsTArray_base<Alloc, RelocationStrategy>::operator=(const nsTArray_base&) { + // Actual copying happens through nsTArray_CopyEnabler, so do nothing here (do + // not copy mHdr). + return *this; +} + +template <class Alloc, class RelocationStrategy> +const nsTArrayHeader* +nsTArray_base<Alloc, RelocationStrategy>::GetAutoArrayBufferUnsafe( + size_t aElemAlign) const { + // Assuming |this| points to an nsAutoArray, we want to get a pointer to + // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf! + + const void* autoBuf = + &reinterpret_cast<const AutoTArray<nsTArray<uint32_t>, 1>*>(this) + ->mAutoBuf; + + // If we're on a 32-bit system and aElemAlign is 8, we need to adjust our + // pointer to take into account the extra alignment in the auto array. + + static_assert( + sizeof(void*) != 4 || (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 && + sizeof(AutoTArray<mozilla::AlignedElem<8>, 1>) == + sizeof(void*) + sizeof(nsTArrayHeader) + 4 + + sizeof(mozilla::AlignedElem<8>)), + "auto array padding wasn't what we expected"); + + // We don't support alignments greater than 8 bytes. + MOZ_ASSERT(aElemAlign <= 4 || aElemAlign == 8, "unsupported alignment."); + if (sizeof(void*) == 4 && aElemAlign == 8) { + autoBuf = reinterpret_cast<const char*>(autoBuf) + 4; + } + + return reinterpret_cast<const Header*>(autoBuf); +} + +template <class Alloc, class RelocationStrategy> +bool nsTArray_base<Alloc, RelocationStrategy>::UsesAutoArrayBuffer() const { + if (!mHdr->mIsAutoArray) { + return false; + } + + // This is nuts. If we were sane, we'd pass aElemAlign as a parameter to + // this function. Unfortunately this function is called in nsTArray_base's + // destructor, at which point we don't know value_type's alignment. + // + // We'll fall on our face and return true when we should say false if + // + // * we're not using our auto buffer, + // * aElemAlign == 4, and + // * mHdr == GetAutoArrayBuffer(8). + // + // This could happen if |*this| lives on the heap and malloc allocated our + // buffer on the heap adjacent to |*this|. + // + // However, we can show that this can't happen. If |this| is an auto array + // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8) + // always points to memory owned by |*this|, because (as we assert below) + // + // * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), + // and + // * sizeof(nsTArrayHeader) > 4. + // + // Since AutoTArray always contains an nsTArrayHeader, + // GetAutoArrayBuffer(8) will always point inside the auto array object, + // even if it doesn't point at the beginning of the header. + // + // Note that this means that we can't store elements with alignment 16 in an + // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory + // owned by this AutoTArray. We statically assert that value_type's + // alignment is 8 bytes or less in AutoTArray. + + static_assert(sizeof(nsTArrayHeader) > 4, "see comment above"); + +#ifdef DEBUG + ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) - + reinterpret_cast<const char*>(GetAutoArrayBuffer(4)); + MOZ_ASSERT(diff >= 0 && diff <= 4, + "GetAutoArrayBuffer doesn't do what we expect."); +#endif + + return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8); +} + +// defined in nsTArray.cpp +bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, + size_t aElemSize); + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::ExtendCapacity(size_type aLength, + size_type aCount, + size_type aElemSize) { + mozilla::CheckedInt<size_type> newLength = aLength; + newLength += aCount; + + if (!newLength.isValid()) { + return ActualAlloc::FailureResult(); + } + + return this->EnsureCapacity<ActualAlloc>(newLength.value(), aElemSize); +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::EnsureCapacity(size_type aCapacity, + size_type aElemSize) { + // This should be the most common case so test this first + if (aCapacity <= mHdr->mCapacity) { + return ActualAlloc::SuccessResult(); + } + + // If the requested memory allocation exceeds size_type(-1)/2, then + // our doubling algorithm may not be able to allocate it. + // Additionally, if it exceeds uint32_t(-1) then we couldn't fit in the + // Header::mCapacity member. Just bail out in cases like that. We don't want + // to be allocating 2 GB+ arrays anyway. + if (!IsTwiceTheRequiredBytesRepresentableAsUint32(aCapacity, aElemSize)) { + ActualAlloc::SizeTooBig((size_t)aCapacity * aElemSize); + return ActualAlloc::FailureResult(); + } + + size_t reqSize = sizeof(Header) + aCapacity * aElemSize; + + if (HasEmptyHeader()) { + // Malloc() new data + Header* header = static_cast<Header*>(ActualAlloc::Malloc(reqSize)); + if (!header) { + return ActualAlloc::FailureResult(); + } + header->mLength = 0; + header->mCapacity = aCapacity; + header->mIsAutoArray = 0; + mHdr = header; + + return ActualAlloc::SuccessResult(); + } + + // We increase our capacity so that the allocated buffer grows exponentially, + // which gives us amortized O(1) appending. Below the threshold, we use + // powers-of-two. Above the threshold, we grow by at least 1.125, rounding up + // to the nearest MiB. + const size_t slowGrowthThreshold = 8 * 1024 * 1024; + + size_t bytesToAlloc; + if (reqSize >= slowGrowthThreshold) { + size_t currSize = sizeof(Header) + Capacity() * aElemSize; + size_t minNewSize = currSize + (currSize >> 3); // multiply by 1.125 + bytesToAlloc = reqSize > minNewSize ? reqSize : minNewSize; + + // Round up to the next multiple of MiB. + const size_t MiB = 1 << 20; + bytesToAlloc = MiB * ((bytesToAlloc + MiB - 1) / MiB); + } else { + // Round up to the next power of two. + bytesToAlloc = mozilla::RoundUpPow2(reqSize); + } + + Header* header; + if (UsesAutoArrayBuffer() || !RelocationStrategy::allowRealloc) { + // Malloc() and copy + header = static_cast<Header*>(ActualAlloc::Malloc(bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + header, mHdr, Length(), aElemSize); + + if (!UsesAutoArrayBuffer()) { + ActualAlloc::Free(mHdr); + } + } else { + // Realloc() existing data + header = static_cast<Header*>(ActualAlloc::Realloc(mHdr, bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + } + + // How many elements can we fit in bytesToAlloc? + size_t newCapacity = (bytesToAlloc - sizeof(Header)) / aElemSize; + MOZ_ASSERT(newCapacity >= aCapacity, "Didn't enlarge the array enough!"); + header->mCapacity = newCapacity; + + mHdr = header; + + return ActualAlloc::SuccessResult(); +} + +// We don't need use Alloc template parameter specified here because failure to +// shrink the capacity will leave the array unchanged. +template <class Alloc, class RelocationStrategy> +void nsTArray_base<Alloc, RelocationStrategy>::ShrinkCapacity( + size_type aElemSize, size_t aElemAlign) { + if (HasEmptyHeader() || UsesAutoArrayBuffer()) { + return; + } + + if (mHdr->mLength >= mHdr->mCapacity) { // should never be greater than... + return; + } + + size_type length = Length(); + + if (IsAutoArray() && GetAutoArrayBuffer(aElemAlign)->mCapacity >= length) { + Header* header = GetAutoArrayBuffer(aElemAlign); + + // Move the data, but don't copy the header to avoid overwriting mCapacity. + header->mLength = length; + RelocationStrategy::RelocateNonOverlappingRegion(header + 1, mHdr + 1, + length, aElemSize); + + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = header; + return; + } + + if (length == 0) { + MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements"); + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = EmptyHdr(); + return; + } + + size_type newSize = sizeof(Header) + length * aElemSize; + + Header* newHeader; + if (!RelocationStrategy::allowRealloc) { + // Malloc() and copy. + newHeader = + static_cast<Header*>(nsTArrayFallibleAllocator::Malloc(newSize)); + if (!newHeader) { + return; + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + newHeader, mHdr, Length(), aElemSize); + + nsTArrayFallibleAllocator::Free(mHdr); + } else { + // Realloc() existing data. + newHeader = + static_cast<Header*>(nsTArrayFallibleAllocator::Realloc(mHdr, newSize)); + if (!newHeader) { + return; + } + } + + mHdr = newHeader; + mHdr->mCapacity = length; +} + +template <class Alloc, class RelocationStrategy> +void nsTArray_base<Alloc, RelocationStrategy>::ShrinkCapacityToZero( + size_type aElemSize, size_t aElemAlign) { + MOZ_ASSERT(mHdr->mLength == 0); + + if (HasEmptyHeader() || UsesAutoArrayBuffer()) { + return; + } + + const bool isAutoArray = IsAutoArray(); + + nsTArrayFallibleAllocator::Free(mHdr); + + if (isAutoArray) { + mHdr = GetAutoArrayBufferUnsafe(aElemAlign); + mHdr->mLength = 0; + } else { + mHdr = EmptyHdr(); + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +void nsTArray_base<Alloc, RelocationStrategy>::ShiftData(index_type aStart, + size_type aOldLen, + size_type aNewLen, + size_type aElemSize, + size_t aElemAlign) { + if (aOldLen == aNewLen) { + return; + } + + // Determine how many elements need to be shifted + size_type num = mHdr->mLength - (aStart + aOldLen); + + // Compute the resulting length of the array + mHdr->mLength += aNewLen - aOldLen; + if (mHdr->mLength == 0) { + ShrinkCapacityToZero(aElemSize, aElemAlign); + } else { + // Maybe nothing needs to be shifted + if (num == 0) { + return; + } + // Perform shift (change units to bytes first) + aStart *= aElemSize; + aNewLen *= aElemSize; + aOldLen *= aElemSize; + char* baseAddr = reinterpret_cast<char*>(mHdr + 1) + aStart; + RelocationStrategy::RelocateOverlappingRegion( + baseAddr + aNewLen, baseAddr + aOldLen, num, aElemSize); + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +void nsTArray_base<Alloc, RelocationStrategy>::SwapFromEnd(index_type aStart, + size_type aCount, + size_type aElemSize, + size_t aElemAlign) { + // This method is part of the implementation of + // nsTArray::SwapRemoveElement{s,}At. For more information, read the + // documentation on that method. + if (aCount == 0) { + return; + } + + // We are going to be removing aCount elements. Update our length to point to + // the new end of the array. + size_type oldLength = mHdr->mLength; + mHdr->mLength -= aCount; + + if (mHdr->mLength == 0) { + // If we have no elements remaining in the array, we can free our buffer. + ShrinkCapacityToZero(aElemSize, aElemAlign); + return; + } + + // Determine how many elements we need to move from the end of the array into + // the now-removed section. This will either be the number of elements which + // were removed (if there are more elements in the tail of the array), or the + // entire tail of the array, whichever is smaller. + size_type relocCount = std::min(aCount, mHdr->mLength - aStart); + if (relocCount == 0) { + return; + } + + // Move the elements which are now stranded after the end of the array back + // into the now-vacated memory. + index_type sourceBytes = (oldLength - relocCount) * aElemSize; + index_type destBytes = aStart * aElemSize; + + // Perform the final copy. This is guaranteed to be a non-overlapping copy + // as our source contains only still-valid entries, and the destination + // contains only invalid entries which need to be overwritten. + MOZ_ASSERT(sourceBytes >= destBytes, + "The source should be after the destination."); + MOZ_ASSERT(sourceBytes - destBytes >= relocCount * aElemSize, + "The range should be nonoverlapping"); + + char* baseAddr = reinterpret_cast<char*>(mHdr + 1); + RelocationStrategy::RelocateNonOverlappingRegion( + baseAddr + destBytes, baseAddr + sourceBytes, relocCount, aElemSize); +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::InsertSlotsAt(index_type aIndex, + size_type aCount, + size_type aElemSize, + size_t aElemAlign) { + if (MOZ_UNLIKELY(aIndex > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + + if (!ActualAlloc::Successful( + this->ExtendCapacity<ActualAlloc>(Length(), aCount, aElemSize))) { + return ActualAlloc::FailureResult(); + } + + // Move the existing elements as needed. Note that this will + // change our mLength, so no need to call IncrementLength. + ShiftData<ActualAlloc>(aIndex, 0, aCount, aElemSize, aElemAlign); + + return ActualAlloc::SuccessResult(); +} + +// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes +// |nsTArray_base &array| in its constructor. When it's destructed, it ensures +// that +// +// * array.mIsAutoArray has the same value as it did when we started, and +// * if array has an auto buffer and mHdr would otherwise point to +// sEmptyTArrayHeader, array.mHdr points to array's auto buffer. + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, RelocationStrategy>::IsAutoArrayRestorer:: + IsAutoArrayRestorer(nsTArray_base<Alloc, RelocationStrategy>& aArray, + size_t aElemAlign) + : mArray(aArray), mElemAlign(aElemAlign), mIsAuto(aArray.IsAutoArray()) {} + +template <class Alloc, class RelocationStrategy> +nsTArray_base<Alloc, + RelocationStrategy>::IsAutoArrayRestorer::~IsAutoArrayRestorer() { + // Careful: We don't want to set mIsAutoArray = 1 on sEmptyTArrayHeader. + if (mIsAuto && mArray.HasEmptyHeader()) { + // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts + // that mHdr->mIsAutoArray is true, which surely isn't the case here. + mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign); + mArray.mHdr->mLength = 0; + } else if (!mArray.HasEmptyHeader()) { + mArray.mHdr->mIsAutoArray = mIsAuto; + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc, class Allocator> +typename ActualAlloc::ResultTypeProxy +nsTArray_base<Alloc, RelocationStrategy>::SwapArrayElements( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign) { + // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we + // have an auto buffer. We need to point mHdr back to our auto buffer before + // we return, otherwise we'll forget that we have an auto buffer at all! + // IsAutoArrayRestorer takes care of this for us. + + IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign); + typename nsTArray_base<Allocator, RelocationStrategy>::IsAutoArrayRestorer + otherAutoRestorer(aOther, aElemAlign); + + // If neither array uses an auto buffer which is big enough to store the + // other array's elements, then ensure that both arrays use malloc'ed storage + // and swap their mHdr pointers. + if ((!UsesAutoArrayBuffer() || Capacity() < aOther.Length()) && + (!aOther.UsesAutoArrayBuffer() || aOther.Capacity() < Length())) { + if (!EnsureNotUsingAutoArrayBuffer<ActualAlloc>(aElemSize) || + !aOther.template EnsureNotUsingAutoArrayBuffer<ActualAlloc>( + aElemSize)) { + return ActualAlloc::FailureResult(); + } + + Header* temp = mHdr; + mHdr = aOther.mHdr; + aOther.mHdr = temp; + + return ActualAlloc::SuccessResult(); + } + + // Swap the two arrays by copying, since at least one is using an auto + // buffer which is large enough to hold all of the aOther's elements. We'll + // copy the shorter array into temporary storage. + // + // (We could do better than this in some circumstances. Suppose we're + // swapping arrays X and Y. X has space for 2 elements in its auto buffer, + // but currently has length 4, so it's using malloc'ed storage. Y has length + // 2. When we swap X and Y, we don't need to use a temporary buffer; we can + // write Y straight into X's auto buffer, write X's malloc'ed buffer on top + // of Y, and then switch X to using its auto buffer.) + + if (!ActualAlloc::Successful( + EnsureCapacity<ActualAlloc>(aOther.Length(), aElemSize)) || + !Allocator::Successful( + aOther.template EnsureCapacity<Allocator>(Length(), aElemSize))) { + return ActualAlloc::FailureResult(); + } + + // The EnsureCapacity calls above shouldn't have caused *both* arrays to + // switch from their auto buffers to malloc'ed space. + MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(), + "One of the arrays should be using its auto buffer."); + + size_type smallerLength = XPCOM_MIN(Length(), aOther.Length()); + size_type largerLength = XPCOM_MAX(Length(), aOther.Length()); + void* smallerElements; + void* largerElements; + if (Length() <= aOther.Length()) { + smallerElements = Hdr() + 1; + largerElements = aOther.Hdr() + 1; + } else { + smallerElements = aOther.Hdr() + 1; + largerElements = Hdr() + 1; + } + + // Allocate temporary storage for the smaller of the two arrays. We want to + // allocate this space on the stack, if it's not too large. Sounds like a + // job for AutoTArray! (One of the two arrays we're swapping is using an + // auto buffer, so we're likely not allocating a lot of space here. But one + // could, in theory, allocate a huge AutoTArray on the heap.) + AutoTArray<uint8_t, 64 * sizeof(void*)> temp; + if (!ActualAlloc::Successful(temp.template EnsureCapacity<ActualAlloc>( + smallerLength * aElemSize, sizeof(uint8_t)))) { + return ActualAlloc::FailureResult(); + } + + RelocationStrategy::RelocateNonOverlappingRegion( + temp.Elements(), smallerElements, smallerLength, aElemSize); + RelocationStrategy::RelocateNonOverlappingRegion( + smallerElements, largerElements, largerLength, aElemSize); + RelocationStrategy::RelocateNonOverlappingRegion( + largerElements, temp.Elements(), smallerLength, aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) && + (Length() == 0 || !aOther.HasEmptyHeader()), + "Don't set sEmptyTArrayHeader's length."); + size_type tempLength = Length(); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (!HasEmptyHeader()) { + mHdr->mLength = aOther.Length(); + } + if (!aOther.HasEmptyHeader()) { + aOther.mHdr->mLength = tempLength; + } + + return ActualAlloc::SuccessResult(); +} + +template <class Alloc, class RelocationStrategy> +template <class Allocator> +void nsTArray_base<Alloc, RelocationStrategy>::MoveInit( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign) { + // This method is similar to SwapArrayElements, but specialized for the case + // where the target array is empty with no allocated heap storage. It is + // provided and used to simplify template instantiation and enable better code + // generation. + + MOZ_ASSERT(Length() == 0); + MOZ_ASSERT(Capacity() == 0 || (IsAutoArray() && UsesAutoArrayBuffer())); + + // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we + // have an auto buffer. We need to point mHdr back to our auto buffer before + // we return, otherwise we'll forget that we have an auto buffer at all! + // IsAutoArrayRestorer takes care of this for us. + + IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign); + typename nsTArray_base<Allocator, RelocationStrategy>::IsAutoArrayRestorer + otherAutoRestorer(aOther, aElemAlign); + + // If neither array uses an auto buffer which is big enough to store the + // other array's elements, then ensure that both arrays use malloc'ed storage + // and swap their mHdr pointers. + if ((!IsAutoArray() || Capacity() < aOther.Length()) && + !aOther.UsesAutoArrayBuffer()) { + mHdr = aOther.mHdr; + + aOther.mHdr = EmptyHdr(); + + return; + } + + // Move the data by copying, since at least one has an auto + // buffer which is large enough to hold all of the aOther's elements. + + EnsureCapacity<nsTArrayInfallibleAllocator>(aOther.Length(), aElemSize); + + // The EnsureCapacity calls above shouldn't have caused *both* arrays to + // switch from their auto buffers to malloc'ed space. + MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(), + "One of the arrays should be using its auto buffer."); + + RelocationStrategy::RelocateNonOverlappingRegion(Hdr() + 1, aOther.Hdr() + 1, + aOther.Length(), aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) && + (Length() == 0 || !aOther.HasEmptyHeader()), + "Don't set sEmptyTArrayHeader's length."); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (!HasEmptyHeader()) { + mHdr->mLength = aOther.Length(); + } + if (!aOther.HasEmptyHeader()) { + aOther.mHdr->mLength = 0; + } +} + +template <class Alloc, class RelocationStrategy> +template <class Allocator> +void nsTArray_base<Alloc, RelocationStrategy>::MoveConstructNonAutoArray( + nsTArray_base<Allocator, RelocationStrategy>& aOther, size_type aElemSize, + size_t aElemAlign) { + // We know that we are not an (Copyable)AutoTArray and we know that we are + // empty, so don't use SwapArrayElements which doesn't know either of these + // facts and is very complex. + + if (aOther.IsEmpty()) { + return; + } + + // aOther might be an (Copyable)AutoTArray though, and it might use its inline + // buffer. + const bool otherUsesAutoArrayBuffer = aOther.UsesAutoArrayBuffer(); + if (otherUsesAutoArrayBuffer) { + // Use nsTArrayInfallibleAllocator regardless of Alloc because this is + // called from a move constructor, which cannot report an error to the + // caller. + aOther.template EnsureNotUsingAutoArrayBuffer<nsTArrayInfallibleAllocator>( + aElemSize); + } + + const bool otherIsAuto = otherUsesAutoArrayBuffer || aOther.IsAutoArray(); + mHdr = aOther.mHdr; + // We might write to mHdr, so ensure it's not the static empty header. aOther + // shouldn't have been empty if we get here anyway. + MOZ_ASSERT(!HasEmptyHeader()); + + if (otherIsAuto) { + mHdr->mIsAutoArray = false; + aOther.mHdr = aOther.GetAutoArrayBufferUnsafe(aElemAlign); + aOther.mHdr->mLength = 0; + } else { + aOther.mHdr = aOther.EmptyHdr(); + } +} + +template <class Alloc, class RelocationStrategy> +template <typename ActualAlloc> +bool nsTArray_base<Alloc, RelocationStrategy>::EnsureNotUsingAutoArrayBuffer( + size_type aElemSize) { + if (UsesAutoArrayBuffer()) { + // If you call this on a 0-length array, we'll set that array's mHdr to + // sEmptyTArrayHeader, in flagrant violation of the AutoTArray invariants. + // It's up to you to set it back! (If you don't, the AutoTArray will + // forget that it has an auto buffer.) + if (Length() == 0) { + mHdr = EmptyHdr(); + return true; + } + + size_type size = sizeof(Header) + Length() * aElemSize; + + Header* header = static_cast<Header*>(ActualAlloc::Malloc(size)); + if (!header) { + return false; + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + header, mHdr, Length(), aElemSize); + header->mCapacity = Length(); + mHdr = header; + } + + return true; +} |