/* -*- 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__ #define nsTArray_h__ #include #include #include #include #include #include #include #include #include #include "mozilla/ArrayIterator.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/BinarySearch.h" #include "mozilla/CheckedInt.h" #include "mozilla/FunctionTypeTraits.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/MemoryReporting.h" #include "mozilla/NotNull.h" #include "mozilla/Span.h" #include "mozilla/fallible.h" #include "mozilla/mozalloc.h" #include "nsAlgorithm.h" #include "nsDebug.h" #include "nsISupports.h" #include "nsRegionFwd.h" #include "nsTArrayForwardDeclare.h" namespace JS { template class Heap; } /* namespace JS */ class nsCycleCollectionTraversalCallback; struct TraceCallbacks; class nsRegion; namespace mozilla::a11y { class BatchData; } namespace mozilla { namespace layers { class Animation; class FrameStats; struct PropertyAnimationGroup; struct TileClient; } // namespace layers } // namespace mozilla namespace mozilla { struct SerializedStructuredCloneBuffer; class SourceBufferTask; } // namespace mozilla namespace mozilla::dom::binding_detail { template class RecordEntry; } namespace mozilla::dom::ipc { class StructuredCloneData; } // namespace mozilla::dom::ipc namespace mozilla::dom { class ClonedMessageData; class MessageData; class MessagePortIdentifier; struct MozPluginParameter; template struct Nullable; class OwningFileOrDirectory; class OwningStringOrBooleanOrObject; class OwningUTF8StringOrDouble; class Pref; class RefMessageData; class ResponsiveImageCandidate; class ServiceWorkerRegistrationData; namespace indexedDB { class SerializedStructuredCloneReadInfo; class ObjectStoreCursorResponse; class IndexCursorResponse; } // namespace indexedDB } // namespace mozilla::dom namespace mozilla::ipc { class ContentSecurityPolicy; template class Endpoint; } // namespace mozilla::ipc class JSStructuredCloneData; template class RefPtr; // // nsTArray is a resizable array class, like std::vector. // // Unlike std::vector, which follows C++'s construction/destruction rules, // By default, nsTArray assumes that instances of E can be relocated safely // using memory utils (memcpy/memmove). // // The public classes defined in this header are // // nsTArray, // CopyableTArray, // FallibleTArray, // AutoTArray, // CopyableAutoTArray // // nsTArray, CopyableTArray, AutoTArray and CopyableAutoTArray are infallible by // default. To opt-in to fallible behaviour, use the `mozilla::fallible` // parameter and check the return value. // // CopyableTArray and CopyableAutoTArray< are copy-constructible and // copy-assignable. Use these only when syntactically necessary to avoid implcit // unintentional copies. nsTArray/AutoTArray can be conveniently copied using // the Clone() member function. Consider using std::move where possible. // // If you just want to declare the nsTArray types (e.g., if you're in a header // file and don't need the full nsTArray definitions) consider including // nsTArrayForwardDeclare.h instead of nsTArray.h. // // The template parameter E specifies the type of the elements and has the // following requirements: // // E MUST be safely memmove()'able. // E MUST define a copy-constructor. // E MAY define operator< for sorting. // E MAY define operator== for searching. // // (Note that the memmove requirement may be relaxed for certain types - see // nsTArray_RelocationStrategy below.) // // There is a public type value_type defined as E within each array class, and // we reference the type under this name below. // // For member functions taking a Comparator instance, Comparator must be either // a functor with a tri-state comparison function with a signature compatible to // // /** @return negative iff a < b, 0 iff a == b, positive iff a > b */ // int (const value_type& a, const value_type& b); // // or a class defining member functions with signatures compatible to: // // class Comparator { // public: // /** @return True if the elements are equals; false otherwise. */ // bool Equals(const value_type& a, const value_type& b) const; // // /** @return True if (a < b); false otherwise. */ // bool LessThan(const value_type& a, const value_type& b) const; // }; // // The Equals member function is used for searching, and the LessThan member // function is used for searching and sorting. Note that some member functions, // e.g. Compare, are templates where a different type Item can be used for the // element to compare to. In that case, the signatures must be compatible to // allow those comparisons, but the details are not documented here. // // // nsTArrayFallibleResult and nsTArrayInfallibleResult types are proxy types // which are used because you cannot use a templated type which is bound to // void as an argument to a void function. In order to work around that, we // encode either a void or a boolean inside these proxy objects, and pass them // to the aforementioned function instead, and then use the type information to // decide what to do in the function. // // Note that public nsTArray methods should never return a proxy type. Such // types are only meant to be used in the internal nsTArray helper methods. // Public methods returning non-proxy types cannot be called from other // nsTArray members. // struct nsTArrayFallibleResult { // Note: allows implicit conversions from and to bool MOZ_IMPLICIT constexpr nsTArrayFallibleResult(bool aResult) : mResult(aResult) {} MOZ_IMPLICIT constexpr operator bool() { return mResult; } private: bool mResult; }; struct nsTArrayInfallibleResult {}; // // nsTArray*Allocators must all use the same |free()|, to allow swap()'ing // between fallible and infallible variants. // struct nsTArrayFallibleAllocatorBase { typedef bool ResultType; typedef nsTArrayFallibleResult ResultTypeProxy; static constexpr ResultType Result(ResultTypeProxy aResult) { return aResult; } static constexpr bool Successful(ResultTypeProxy aResult) { return aResult; } static constexpr ResultTypeProxy SuccessResult() { return true; } static constexpr ResultTypeProxy FailureResult() { return false; } static constexpr ResultType ConvertBoolToResultType(bool aValue) { return aValue; } }; struct nsTArrayInfallibleAllocatorBase { typedef void ResultType; typedef nsTArrayInfallibleResult ResultTypeProxy; static constexpr ResultType Result(ResultTypeProxy aResult) {} static constexpr bool Successful(ResultTypeProxy) { return true; } static constexpr ResultTypeProxy SuccessResult() { return ResultTypeProxy(); } [[noreturn]] static ResultTypeProxy FailureResult() { MOZ_CRASH("Infallible nsTArray should never fail"); } template static constexpr ResultType ConvertBoolToResultType(T aValue) { if (!aValue) { MOZ_CRASH("infallible nsTArray should never convert false to ResultType"); } } template static constexpr ResultType ConvertBoolToResultType( const mozilla::NotNull& aValue) {} }; struct nsTArrayFallibleAllocator : nsTArrayFallibleAllocatorBase { static void* Malloc(size_t aSize) { return malloc(aSize); } static void* Realloc(void* aPtr, size_t aSize) { return realloc(aPtr, aSize); } static void Free(void* aPtr) { free(aPtr); } static void SizeTooBig(size_t) {} }; struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase { static void* Malloc(size_t aSize) MOZ_NONNULL_RETURN { return moz_xmalloc(aSize); } static void* Realloc(void* aPtr, size_t aSize) MOZ_NONNULL_RETURN { return moz_xrealloc(aPtr, aSize); } static void Free(void* aPtr) { free(aPtr); } static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); } }; // nsTArray_base stores elements into the space allocated beyond // sizeof(*this). This is done to minimize the size of the nsTArray // object when it is empty. struct nsTArrayHeader { uint32_t mLength; uint32_t mCapacity : 31; uint32_t mIsAutoArray : 1; }; // Given an nsTArray which is an AutoTArray, this is the offset from the `this` // pointer to the `mAutoBuf` member. We guarantee this by forcing mAutoBuf to be // 8-byte aligned, which makes the location independent of the alignment of T // (since we don't support over-aligned values in nsTArray). constexpr static size_t kAutoTArrayHeaderOffset = 8; extern "C" { extern const nsTArrayHeader sEmptyTArrayHeader; } template class nsCOMPtr; namespace mozilla { template class OwningNonNull; } // namespace mozilla namespace detail { template void AssertStrictWeakOrder(Iter aBegin, Iter aEnd, const Comparator& aCmp) { // This check is present in newer libc++ versions, and it is a useful check, // so check for it ourselves if we're not using libc++. Ported from: // https://github.com/llvm/llvm-project/blob/cf0efb31880dab5f5b2f20bda6634c68a42d6908/libcxx/include/__cxx03/__debug_utils/strict_weak_ordering_check.h#L28 #if defined(DEBUG) && !defined(_LIBCPP_VERSION) MOZ_ASSERT(std::is_sorted(aBegin, aEnd, aCmp), "Invalid strict-weak ordering comparator"); // Limit the number of elements we need to check. auto size = std::min(size_t(aEnd - aBegin), size_t(100)); size_t p = 0; while (p < size) { size_t q = p + size_t(1); // Find first element that is greater than *(aBegin+p). while (q < size && !aCmp(*(aBegin + p), *(aBegin + q))) { ++q; } // Check that the elements from p to q are equal between each other. for (size_t b = p; b < q; ++b) { for (size_t a = p; a <= b; ++a) { MOZ_ASSERT(!aCmp(*(aBegin + a), *(aBegin + b)), "Your comparator is not a valid strict-weak ordering"); MOZ_ASSERT(!aCmp(*(aBegin + b), *(aBegin + a)), "Your comparator is not a valid strict-weak ordering"); } } // Check that elements between p and q are less than between q and size. for (size_t a = p; a < q; ++a) { for (size_t b = q; b < size; ++b) { MOZ_ASSERT(aCmp(*(aBegin + a), *(aBegin + b)), "Your comparator is not a valid strict-weak ordering"); MOZ_ASSERT(!aCmp(*(aBegin + b), *(aBegin + a)), "Your comparator is not a valid strict-weak ordering"); } } // Skip these equal elements. p = q; } #endif } template struct SafeElementAtPointerValue; template struct SafeElementAtPointerValue { using type = T*; }; template struct SafeElementAtPointerValue> { using type = T*; }; template struct SafeElementAtPointerValue> { using type = T*; }; template struct SafeElementAtPointerValue> { using type = T*; }; } // namespace detail // This class serves as a base class for nsTArray. It shouldn't be used // directly. It holds common implementation code that does not depend on the // element type of the nsTArray. // template class nsTArray_base { // Needed for AppendElements from an array with a different allocator, which // calls ShiftData. template friend class nsTArray_Impl; protected: using Header = nsTArrayHeader; public: using size_type = size_t; using index_type = size_t; // @return The number of elements in the array. size_type Length() const { return mHdr->mLength; } // @return True if the array is empty or false otherwise. bool IsEmpty() const { return Length() == 0; } // @return The number of elements that can fit in the array without forcing // the array to be re-allocated. The length of an array is always less // than or equal to its capacity. size_type Capacity() const { return mHdr->mCapacity; } #ifdef DEBUG void* DebugGetHeader() const { return mHdr; } #endif nsTArray_base(const nsTArray_base&) = delete; nsTArray_base& operator=(const nsTArray_base&) = delete; protected: nsTArray_base() = default; ~nsTArray_base(); // Resize the storage if necessary to achieve the requested capacity. // @param aCapacity The requested number of array elements. // @param aElemSize The size of an array element. // @return False if insufficient memory is available; true otherwise. template typename Alloc::ResultTypeProxy EnsureCapacity(size_type aCapacity, size_type aElemSize) { // Do this check here so that our callers can inline it. if (aCapacity <= mHdr->mCapacity) { return Alloc::SuccessResult(); } return EnsureCapacityImpl(aCapacity, aElemSize); } // The rest of EnsureCapacity. Should only be called if aCapacity > // mHdr->mCapacity. template typename Alloc::ResultTypeProxy EnsureCapacityImpl(size_type aCapacity, size_type aElemSize); // Extend the storage to accommodate aCount extra elements. // @param aLength The current size of the array. // @param aCount The number of elements to add. // @param aElemSize The size of an array element. // @return False if insufficient memory is available or the new length // would overflow; true otherwise. template typename Alloc::ResultTypeProxy ExtendCapacity(size_type aLength, size_type aCount, size_type aElemSize); // Tries to resize the storage to the minimum required amount. If this fails, // the array is left as-is. // @param aElemSize The size of an array element. void ShrinkCapacity(size_type aElemSize); // Resizes the storage to 0. This may only be called when Length() is already // 0. void ShrinkCapacityToZero(); // This method may be called to resize a "gap" in the array by shifting // elements around. It updates mLength appropriately. If the resulting // array has zero elements, then the array's memory is free'd. // @param aStart The starting index of the gap. // @param aOldLen The current length of the gap. // @param aNewLen The desired length of the gap. // @param aElemSize The size of an array element. template void ShiftData(index_type aStart, size_type aOldLen, size_type aNewLen, size_type aElemSize); // This method may be called to swap elements from the end of the array to // fill a "gap" in the array. If the resulting array has zero elements, then // the array's memory is free'd. // @param aStart The starting index of the gap. // @param aCount The length of the gap. // @param aElemSize The size of an array element. template void SwapFromEnd(index_type aStart, size_type aCount, size_type aElemSize); // This method increments the length member of the array's header. // Note that mHdr may actually be sEmptyTArrayHeader in the case where a // zero-length array is inserted into our array. But then aNum should // always be 0. void IncrementLength(size_t aNum) { if (HasEmptyHeader()) { if (MOZ_UNLIKELY(aNum != 0)) { // Writing a non-zero length to the empty header would be extremely bad. MOZ_CRASH(); } } else { mHdr->mLength += aNum; } } // This method inserts blank slots into the array. // @param aIndex the place to insert the new elements. This must be no // greater than the current length of the array. // @param aCount the number of slots to insert // @param aElementSize the size of an array element. template typename Alloc::ResultTypeProxy InsertSlotsAt(index_type aIndex, size_type aCount, size_type aElementSize); template typename Alloc::ResultTypeProxy SwapArrayElements( nsTArray_base& aOther, size_type aElemSize); void MoveConstructNonAutoArray(nsTArray_base& aOther, size_type aElemSize); void MoveInit(nsTArray_base& aOther, size_type aElemSize); // Helper function for move construction and SwapArrayElements. // Takes the storage from the nsTArray as a non-auto Header pointer. // If the array is holding a reference to an AutoTArray buffer, // it will be moved to the heap before being returned. template Header* TakeHeaderForMove(size_type aElemSize); // Returns whether we're using our auto-array inline buffer. bool UsesAutoArrayBuffer() const { return mHdr == GetAutoArrayHeader(); } Header* GetAutoArrayHeader() const { if (!mHdr->mIsAutoArray) { return nullptr; } return const_cast(reinterpret_cast( reinterpret_cast(this) + kAutoTArrayHeaderOffset)); } // The array's elements (prefixed with a Header). This pointer is never // null. If the array is empty, then this will point to sEmptyTArrayHeader. Header* mHdr{EmptyHdr()}; Header* Hdr() const MOZ_NONNULL_RETURN { return mHdr; } Header** PtrToHdr() MOZ_NONNULL_RETURN { return &mHdr; } static constexpr Header* EmptyHdr() MOZ_NONNULL_RETURN { return const_cast(&sEmptyTArrayHeader); } [[nodiscard]] bool HasEmptyHeader() const { return mHdr == EmptyHdr(); } }; namespace detail { // Used for argument checking in nsTArrayElementTraits::Emplace. template struct ChooseFirst; template <> struct ChooseFirst<> { // Choose a default type that is guaranteed to not match E* for any // nsTArray. typedef void Type; }; template struct ChooseFirst { typedef A Type; }; } // namespace detail // // This class defines convenience functions for element specific operations. // Specialize this template if necessary. // template class nsTArrayElementTraits { public: // Invoke the default constructor in place. static inline void Construct(E* aE) { // Do NOT call "E()"! That triggers C++ "default initialization" // which zeroes out POD ("plain old data") types such as regular // ints. We don't want that because it can be a performance issue // and people don't expect it; nsTArray should work like a regular // C/C++ array in this respect. new (static_cast(aE)) E; } // Invoke the copy-constructor in place. template static inline void Construct(E* aE, A&& aArg) { using E_NoCV = std::remove_cv_t; using A_NoCV = std::remove_cv_t; static_assert(!std::is_same_v, "For safety, we disallow constructing nsTArray elements " "from E* pointers. See bug 960591."); new (static_cast(aE)) E(std::forward(aArg)); } // Construct in place. template static inline void Emplace(E* aE, Args&&... aArgs) { using E_NoCV = std::remove_cv_t; using A_NoCV = std::remove_cv_t::Type>; static_assert(!std::is_same_v, "For safety, we disallow constructing nsTArray elements " "from E* pointers. See bug 960591."); new (static_cast(aE)) E(std::forward(aArgs)...); } // Invoke the destructor in place. static inline void Destruct(E* aE) { aE->~E(); } }; // The default comparator used by nsTArray template class nsDefaultComparator { public: bool Equals(const A& aA, const B& aB) const { return aA == aB; } bool LessThan(const A& aA, const B& aB) const { return aA < aB; } }; // // Normally elements are copied with memcpy and memmove, but for some element // types that is problematic. The nsTArray_RelocationStrategy template class // can be specialized to ensure that copying calls constructors and destructors // instead, as is done below for JS::Heap elements. // // // A class that defines how to copy elements using memcpy/memmove. // struct nsTArray_RelocateUsingMemutils { const static bool allowRealloc = true; static void RelocateNonOverlappingRegionWithHeader(void* aDest, const void* aSrc, size_t aCount, size_t aElemSize) { memcpy(aDest, aSrc, sizeof(nsTArrayHeader) + aCount * aElemSize); } static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount, size_t aElemSize) { memmove(aDest, aSrc, aCount * aElemSize); } static void RelocateNonOverlappingRegion(void* aDest, void* aSrc, size_t aCount, size_t aElemSize) { memcpy(aDest, aSrc, aCount * aElemSize); } }; // // A template class that defines how to relocate elements using the type's move // constructor and destructor appropriately. // template struct nsTArray_RelocateUsingMoveConstructor { typedef nsTArrayElementTraits traits; const static bool allowRealloc = false; static void RelocateNonOverlappingRegionWithHeader(void* aDest, void* aSrc, size_t aCount, size_t aElemSize) { nsTArrayHeader* destHeader = static_cast(aDest); nsTArrayHeader* srcHeader = static_cast(aSrc); *destHeader = *srcHeader; RelocateNonOverlappingRegion( static_cast(aDest) + sizeof(nsTArrayHeader), static_cast(aSrc) + sizeof(nsTArrayHeader), aCount, aElemSize); } // RelocateNonOverlappingRegion and RelocateOverlappingRegion are defined by // analogy with memmove and memcpy that are used for relocation of // trivially-relocatable types through nsTArray_RelocateUsingMemutils. What // they actually do is slightly different: RelocateOverlappingRegion checks to // see which direction the movement needs to take place, whether from // back-to-front of the range to be moved or from front-to-back. // RelocateNonOverlappingRegion assumes that relocating front-to-back is // always valid. They use RelocateRegionForward and RelocateRegionBackward, // which are analogous to std::move and std::move_backward respectively, // except they don't move-assign the destination from the source but // move-construct the destination from the source and destroy the source. static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount, size_t aElemSize) { ElemType* destBegin = static_cast(aDest); ElemType* srcBegin = static_cast(aSrc); // If destination and source are the same, this is a no-op. // In practice, we don't do this. if (destBegin == srcBegin) { return; } ElemType* srcEnd = srcBegin + aCount; ElemType* destEnd = destBegin + aCount; // Figure out whether to relocate back-to-front or front-to-back. if (srcEnd > destBegin && srcEnd < destEnd) { RelocateRegionBackward(srcBegin, srcEnd, destEnd); } else { RelocateRegionForward(srcBegin, srcEnd, destBegin); } } static void RelocateNonOverlappingRegion(void* aDest, void* aSrc, size_t aCount, size_t aElemSize) { ElemType* destBegin = static_cast(aDest); ElemType* srcBegin = static_cast(aSrc); ElemType* srcEnd = srcBegin + aCount; #ifdef DEBUG ElemType* destEnd = destBegin + aCount; MOZ_ASSERT(srcEnd <= destBegin || srcBegin >= destEnd); #endif RelocateRegionForward(srcBegin, srcEnd, destBegin); } private: static void RelocateRegionForward(ElemType* srcBegin, ElemType* srcEnd, ElemType* destBegin) { ElemType* srcElem = srcBegin; ElemType* destElem = destBegin; while (srcElem != srcEnd) { RelocateElement(srcElem, destElem); ++destElem; ++srcElem; } } static void RelocateRegionBackward(ElemType* srcBegin, ElemType* srcEnd, ElemType* destEnd) { ElemType* srcElem = srcEnd; ElemType* destElem = destEnd; while (srcElem != srcBegin) { --destElem; --srcElem; RelocateElement(srcElem, destElem); } } static void RelocateElement(ElemType* srcElem, ElemType* destElem) { traits::Construct(destElem, std::move(*srcElem)); traits::Destruct(srcElem); } }; // // The default behaviour is to use memcpy/memmove for everything. // template struct MOZ_NEEDS_MEMMOVABLE_TYPE nsTArray_RelocationStrategy { using Type = nsTArray_RelocateUsingMemutils; }; // // Some classes require constructors/destructors to be called, so they are // specialized here. // #define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(E) \ template <> \ struct nsTArray_RelocationStrategy { \ using Type = nsTArray_RelocateUsingMoveConstructor; \ }; #define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(T) \ template \ struct nsTArray_RelocationStrategy> { \ using Type = nsTArray_RelocateUsingMoveConstructor>; \ }; MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(JS::Heap) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(std::function) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(mozilla::ipc::Endpoint) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsRegion) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsIntRegion) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::layers::TileClient) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( mozilla::SerializedStructuredCloneBuffer) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( mozilla::dom::ipc::StructuredCloneData) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::ClonedMessageData) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( mozilla::dom::indexedDB::ObjectStoreCursorResponse) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( mozilla::dom::indexedDB::IndexCursorResponse) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo); MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(JSStructuredCloneData) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::MessageData) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::RefMessageData) MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::SourceBufferTask) namespace detail { // These helpers allow us to differentiate between tri-state comparator // functions and classes with LessThan() and Equal() methods. If an object, when // called as a function with two instances of our element type, returns an int, // we treat it as a tri-state comparator. // // T is the type of the comparator object we want to check. L and R are the // types that we'll be comparing. // // V is never passed, and is only used to allow us to specialize on the return // value of the comparator function. template struct IsCompareMethod : std::false_type {}; template struct IsCompareMethod< T, L, R, decltype(std::declval()(std::declval(), std::declval()))> : std::true_type {}; // These two wrappers allow us to use either a tri-state comparator, or an // object with Equals() and LessThan() methods interchangeably. They provide a // tri-state Compare() method, and Equals() method, and a LessThan() method. // // Depending on the type of the underlying comparator, they either pass these // through directly, or synthesize them from the methods available on the // comparator. // // Callers should always use the most-specific of these methods that match their // purpose. // Comparator wrapper for a tri-state comparator function template ::value> struct CompareWrapper { #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable : 4180) /* Silence "qualifier applied to function \ type has no meaning" warning */ #endif MOZ_IMPLICIT CompareWrapper(const T& aComparator) : mComparator(aComparator) {} template int Compare(A& aLeft, B& aRight) const { return mComparator(aLeft, aRight); } template bool Equals(A& aLeft, B& aRight) const { return Compare(aLeft, aRight) == 0; } template bool LessThan(A& aLeft, B& aRight) const { return Compare(aLeft, aRight) < 0; } const T& mComparator; #ifdef _MSC_VER # pragma warning(pop) #endif }; // Comparator wrapper for a class with Equals() and LessThan() methods. template struct CompareWrapper { MOZ_IMPLICIT CompareWrapper(const T& aComparator) : mComparator(aComparator) {} template int Compare(A& aLeft, B& aRight) const { if (LessThan(aLeft, aRight)) { return -1; } if (Equals(aLeft, aRight)) { return 0; } return 1; } template bool Equals(A& aLeft, B& aRight) const { return mComparator.Equals(aLeft, aRight); } template bool LessThan(A& aLeft, B& aRight) const { return mComparator.LessThan(aLeft, aRight); } const T& mComparator; }; } // namespace detail enum class SortBoundsCheck { Enable, Disable }; // // nsTArray_Impl contains most of the guts supporting nsTArray, FallibleTArray, // AutoTArray. // // The only situation in which you might need to use nsTArray_Impl in your code // is if you're writing code which mutates a TArray which may or may not be // infallible. // // Code which merely reads from a TArray which may or may not be infallible can // simply cast the TArray to |const nsTArray&|; both fallible and infallible // TArrays can be cast to |const nsTArray&|. // template class nsTArray_Impl : public nsTArray_base::Type> { private: friend class nsTArray; typedef nsTArrayFallibleAllocator FallibleAlloc; typedef nsTArrayInfallibleAllocator InfallibleAlloc; public: typedef typename nsTArray_RelocationStrategy::Type relocation_type; typedef nsTArray_base base_type; typedef typename base_type::size_type size_type; typedef typename base_type::index_type index_type; typedef E value_type; typedef nsTArray_Impl self_type; typedef nsTArrayElementTraits elem_traits; typedef mozilla::ArrayIterator iterator; typedef mozilla::ArrayIterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; using base_type::EmptyHdr; // A special value that is used to indicate an invalid or unknown index // into the array. static const index_type NoIndex = index_type(-1); using base_type::Length; // // Finalization method // ~nsTArray_Impl() { if (!base_type::IsEmpty()) { ClearAndRetainStorage(); } // mHdr cleanup will be handled by base destructor } // // Initialization methods // nsTArray_Impl() = default; // Initialize this array and pre-allocate some number of elements. explicit nsTArray_Impl(size_type aCapacity) { SetCapacity(aCapacity); } // Initialize this array with an r-value. // Allow different types of allocators, since the allocator doesn't matter. template explicit nsTArray_Impl(nsTArray_Impl&& aOther) noexcept { // We cannot be a (Copyable)AutoTArray because that overrides this ctor. MOZ_ASSERT(!this->UsesAutoArrayBuffer()); // This does not use SwapArrayElements because that's unnecessarily complex. this->MoveConstructNonAutoArray(aOther, sizeof(value_type)); } // The array's copy-constructor performs a 'deep' copy of the given array. // @param aOther The array object to copy. // // It's very important that we declare this method as taking |const // self_type&| as opposed to taking |const nsTArray_Impl| for // an arbitrary OtherAlloc. // // If we don't declare a constructor taking |const self_type&|, C++ generates // a copy-constructor for this class which merely copies the object's // members, which is obviously wrong. // // You can pass an nsTArray_Impl to this method because // nsTArray_Impl can be cast to const nsTArray_Impl&. So the // effect on the API is the same as if we'd declared this method as taking // |const nsTArray_Impl&|. nsTArray_Impl(const nsTArray_Impl&) = default; // Allow converting to a const array with a different kind of allocator, // Since the allocator doesn't matter for const arrays template [[nodiscard]] operator const nsTArray_Impl&() const& { return *reinterpret_cast*>(this); } // And we have to do this for our subclasses too [[nodiscard]] operator const nsTArray&() const& { return *reinterpret_cast*>(this); } [[nodiscard]] operator const FallibleTArray&() const& { return *reinterpret_cast*>(this); } // The array's assignment operator performs a 'deep' copy of the given // array. It is optimized to reuse existing storage if possible. // @param aOther The array object to copy. nsTArray_Impl& operator=(const nsTArray_Impl&) = default; // The array's move assignment operator steals the underlying data from // the other array. // @param other The array object to move from. self_type& operator=(self_type&& aOther) { if (this != &aOther) { Clear(); this->MoveInit(aOther, sizeof(value_type)); } return *this; } // Return true if this array has the same length and the same // elements as |aOther|. template [[nodiscard]] bool operator==( const nsTArray_Impl& aOther) const { size_type len = Length(); if (len != aOther.Length()) { return false; } // XXX std::equal would be as fast or faster here for (index_type i = 0; i < len; ++i) { if (!(operator[](i) == aOther[i])) { return false; } } return true; } // Return true if this array does not have the same length and the same // elements as |aOther|. [[nodiscard]] bool operator!=(const self_type& aOther) const { return !operator==(aOther); } // If Alloc == FallibleAlloc, ReplaceElementsAt might fail, without a way to // signal this to the caller, so we disallow copying via operator=. Callers // should use ReplaceElementsAt with a fallible argument instead, and check // the result. template , Allocator>> self_type& operator=(const nsTArray_Impl& aOther) { AssignInternal(aOther.Elements(), aOther.Length()); return *this; } template self_type& operator=(nsTArray_Impl&& aOther) { Clear(); this->MoveInit(aOther, sizeof(value_type)); return *this; } // @return The amount of memory used by this nsTArray_Impl, excluding // sizeof(*this). If you want to measure anything hanging off the array, you // must iterate over the elements and measure them individually; hence the // "Shallow" prefix. [[nodiscard]] size_t ShallowSizeOfExcludingThis( mozilla::MallocSizeOf aMallocSizeOf) const { if (this->UsesAutoArrayBuffer() || this->HasEmptyHeader()) { return 0; } return aMallocSizeOf(this->Hdr()); } // @return The amount of memory used by this nsTArray_Impl, including // sizeof(*this). If you want to measure anything hanging off the array, you // must iterate over the elements and measure them individually; hence the // "Shallow" prefix. [[nodiscard]] size_t ShallowSizeOfIncludingThis( mozilla::MallocSizeOf aMallocSizeOf) const { return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); } // // Accessor methods // // This method provides direct access to the array elements. // @return A pointer to the first element of the array. If the array is // empty, then this pointer must not be dereferenced. [[nodiscard]] value_type* Elements() MOZ_NONNULL_RETURN { return reinterpret_cast(Hdr() + 1); } // This method provides direct, readonly access to the array elements. // @return A pointer to the first element of the array. If the array is // empty, then this pointer must not be dereferenced. [[nodiscard]] const value_type* Elements() const MOZ_NONNULL_RETURN { return reinterpret_cast(Hdr() + 1); } // This method provides direct access to an element of the array. The given // index must be within the array bounds. // @param aIndex The index of an element in the array. // @return A reference to the i'th element of the array. [[nodiscard]] value_type& ElementAt(index_type aIndex) { if (MOZ_UNLIKELY(aIndex >= Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); } return Elements()[aIndex]; } // This method provides direct, readonly access to an element of the array // The given index must be within the array bounds. // @param aIndex The index of an element in the array. // @return A const reference to the i'th element of the array. [[nodiscard]] const value_type& ElementAt(index_type aIndex) const { if (MOZ_UNLIKELY(aIndex >= Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); } return Elements()[aIndex]; } // This method provides direct access to an element of the array in a bounds // safe manner. If the requested index is out of bounds the provided default // value is returned. // @param aIndex The index of an element in the array. // @param aDef The value to return if the index is out of bounds. [[nodiscard]] value_type& SafeElementAt(index_type aIndex, value_type& aDef) { return aIndex < Length() ? Elements()[aIndex] : aDef; } // This method provides direct access to an element of the array in a bounds // safe manner. If the requested index is out of bounds the provided default // value is returned. // @param aIndex The index of an element in the array. // @param aDef The value to return if the index is out of bounds. [[nodiscard]] const value_type& SafeElementAt(index_type aIndex, const value_type& aDef) const { return aIndex < Length() ? Elements()[aIndex] : aDef; } [[nodiscard]] auto SafeElementAt(index_type aIndex) const { typename ::detail::SafeElementAtPointerValue::type result; if (aIndex < Length()) { result = Elements()[aIndex]; } else { result = nullptr; } return result; } // Shorthand for ElementAt(aIndex) [[nodiscard]] value_type& operator[](index_type aIndex) { return ElementAt(aIndex); } // Shorthand for ElementAt(aIndex) [[nodiscard]] const value_type& operator[](index_type aIndex) const { return ElementAt(aIndex); } // Shorthand for ElementAt(length - 1) [[nodiscard]] value_type& LastElement() { return ElementAt(Length() - 1); } // Shorthand for ElementAt(length - 1) [[nodiscard]] const value_type& LastElement() const { return ElementAt(Length() - 1); } // Shorthand for SafeElementAt(length - 1, def) [[nodiscard]] value_type& SafeLastElement(value_type& aDef) { return SafeElementAt(Length() - 1, aDef); } // Shorthand for SafeElementAt(length - 1, def) [[nodiscard]] const value_type& SafeLastElement( const value_type& aDef) const { return SafeElementAt(Length() - 1, aDef); } // Methods for range-based for loops. [[nodiscard]] iterator begin() { return iterator(*this, 0); } [[nodiscard]] const_iterator begin() const { return const_iterator(*this, 0); } [[nodiscard]] const_iterator cbegin() const { return begin(); } [[nodiscard]] iterator end() { return iterator(*this, Length()); } [[nodiscard]] const_iterator end() const { return const_iterator(*this, Length()); } [[nodiscard]] const_iterator cend() const { return end(); } // Methods for reverse iterating. [[nodiscard]] reverse_iterator rbegin() { return reverse_iterator(end()); } [[nodiscard]] const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } [[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); } [[nodiscard]] reverse_iterator rend() { return reverse_iterator(begin()); } [[nodiscard]] const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } [[nodiscard]] const_reverse_iterator crend() const { return rend(); } // Span integration [[nodiscard]] operator mozilla::Span() { return mozilla::Span(Elements(), Length()); } [[nodiscard]] operator mozilla::Span() const { return mozilla::Span(Elements(), Length()); } // // Search methods // // This method searches for the first element in this array that is equal // to the given element. // @param aItem The item to search for. // @param aComp The Comparator used to determine element equality. // @return true if the element was found. template [[nodiscard]] bool Contains(const Item& aItem, const Comparator& aComp) const { return ApplyIf( aItem, 0, aComp, []() { return true; }, []() { return false; }); } // Like Contains(), but assumes a sorted array. template [[nodiscard]] bool ContainsSorted(const Item& aItem, const Comparator& aComp) const { return BinaryIndexOf(aItem, aComp) != NoIndex; } // This method searches for the first element in this array that is equal // to the given element. This method assumes that 'operator==' is defined // for value_type. // @param aItem The item to search for. // @return true if the element was found. template [[nodiscard]] bool Contains(const Item& aItem) const { return Contains(aItem, nsDefaultComparator()); } // Like Contains(), but assumes a sorted array. template [[nodiscard]] bool ContainsSorted(const Item& aItem) const { return BinaryIndexOf(aItem) != NoIndex; } // This method searches for the offset of the first element in this // array that is equal to the given element. // @param aItem The item to search for. // @param aStart The index to start from. // @param aComp The Comparator used to determine element equality. // @return The index of the found element or NoIndex if not found. template [[nodiscard]] index_type IndexOf(const Item& aItem, index_type aStart, const Comparator& aComp) const { ::detail::CompareWrapper comp(aComp); const value_type* iter = Elements() + aStart; const value_type* iend = Elements() + Length(); for (; iter != iend; ++iter) { if (comp.Equals(*iter, aItem)) { return index_type(iter - Elements()); } } return NoIndex; } // This method searches for the offset of the first element in this // array that is equal to the given element. This method assumes // that 'operator==' is defined for value_type. // @param aItem The item to search for. // @param aStart The index to start from. // @return The index of the found element or NoIndex if not found. template [[nodiscard]] index_type IndexOf(const Item& aItem, index_type aStart = 0) const { return IndexOf(aItem, aStart, nsDefaultComparator()); } // This method searches for the offset of the last element in this // array that is equal to the given element. // @param aItem The item to search for. // @param aStart The index to start from. If greater than or equal to the // length of the array, then the entire array is searched. // @param aComp The Comparator used to determine element equality. // @return The index of the found element or NoIndex if not found. template [[nodiscard]] index_type LastIndexOf(const Item& aItem, index_type aStart, const Comparator& aComp) const { ::detail::CompareWrapper comp(aComp); size_type endOffset = aStart >= Length() ? Length() : aStart + 1; const value_type* iend = Elements() - 1; const value_type* iter = iend + endOffset; for (; iter != iend; --iter) { if (comp.Equals(*iter, aItem)) { return index_type(iter - Elements()); } } return NoIndex; } // This method searches for the offset of the last element in this // array that is equal to the given element. This method assumes // that 'operator==' is defined for value_type. // @param aItem The item to search for. // @param aStart The index to start from. If greater than or equal to the // length of the array, then the entire array is searched. // @return The index of the found element or NoIndex if not found. template [[nodiscard]] index_type LastIndexOf(const Item& aItem, index_type aStart = NoIndex) const { return LastIndexOf(aItem, aStart, nsDefaultComparator()); } // This method searches for the offset for the element in this array // that is equal to the given element. The array is assumed to be sorted. // If there is more than one equivalent element, there is no guarantee // on which one will be returned. // @param aItem The item to search for. // @param aComp The Comparator used. // @return The index of the found element or NoIndex if not found. template [[nodiscard]] index_type BinaryIndexOf(const Item& aItem, const Comparator& aComp) const { using mozilla::BinarySearchIf; ::detail::CompareWrapper comp(aComp); size_t index; bool found = BinarySearchIf( Elements(), 0, Length(), // Note: We pass the Compare() args here in reverse order and negate the // results for compatibility reasons. Some existing callers use Equals() // functions with first arguments which match aElement but not aItem, or // second arguments that match aItem but not aElement. To accommodate // those callers, we preserve the argument order of the older version of // this API. These callers, however, should be fixed, and this special // case removed. [&](const value_type& aElement) { return -comp.Compare(aElement, aItem); }, &index); return found ? index : NoIndex; } // This method searches for the offset for the element in this array // that is equal to the given element. The array is assumed to be sorted. // This method assumes that 'operator==' and 'operator<' are defined. // @param aItem The item to search for. // @return The index of the found element or NoIndex if not found. template [[nodiscard]] index_type BinaryIndexOf(const Item& aItem) const { return BinaryIndexOf(aItem, nsDefaultComparator()); } // // Mutation methods // private: template typename ActualAlloc::ResultType AssignInternal(const Item* aArray, size_type aArrayLen); public: template [[nodiscard]] typename ActualAlloc::ResultType Assign( const nsTArray_Impl& aOther) { return AssignInternal(aOther.Elements(), aOther.Length()); } template [[nodiscard]] bool Assign(const nsTArray_Impl& aOther, const mozilla::fallible_t&) { return Assign(aOther); } template void Assign(nsTArray_Impl&& aOther) { Clear(); this->MoveInit(aOther, sizeof(value_type)); } // This method call the destructor on each element of the array, empties it, // but does not shrink the array's capacity. // See also SetLengthAndRetainStorage. // Make sure to call Compact() if needed to avoid keeping a huge array // around. void ClearAndRetainStorage() { if (this->HasEmptyHeader()) { return; } DestructRange(0, Length()); base_type::mHdr->mLength = 0; } // This method modifies the length of the array, but unlike SetLength // it doesn't deallocate/reallocate the current internal storage. // The new length MUST be shorter than or equal to the current capacity. // If the new length is larger than the existing length of the array, // then new elements will be constructed using value_type's default // constructor. If shorter, elements will be destructed and removed. // See also ClearAndRetainStorage. // @param aNewLen The desired length of this array. void SetLengthAndRetainStorage(size_type aNewLen) { MOZ_ASSERT(aNewLen <= base_type::Capacity()); size_type oldLen = Length(); if (aNewLen > oldLen) { /// XXX(Bug 1631367) SetLengthAndRetainStorage should be disabled for /// FallibleTArray. InsertElementsAtInternal(oldLen, aNewLen - oldLen); return; } if (aNewLen < oldLen) { DestructRange(aNewLen, oldLen - aNewLen); base_type::mHdr->mLength = aNewLen; } } // This method replaces a range of elements in this array. // @param aStart The starting index of the elements to replace. // @param aCount The number of elements to replace. This may be zero to // insert elements without removing any existing elements. // @param aArray The values to copy into this array. Must be non-null, // and these elements must not already exist in the array // being modified. // @param aArrayLen The number of values to copy into this array. // @return A pointer to the new elements in the array, or null if // the operation failed due to insufficient memory. private: template value_type* ReplaceElementsAtInternal(index_type aStart, size_type aCount, const Item* aArray, size_type aArrayLen); public: template [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, size_type aCount, const Item* aArray, size_type aArrayLen, const mozilla::fallible_t&) { return ReplaceElementsAtInternal(aStart, aCount, aArray, aArrayLen); } // A variation on the ReplaceElementsAt method defined above. template [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, size_type aCount, const nsTArray& aArray, const mozilla::fallible_t&) { return ReplaceElementsAtInternal(aStart, aCount, aArray); } template [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, size_type aCount, mozilla::Span aSpan, const mozilla::fallible_t&) { return ReplaceElementsAtInternal(aStart, aCount, aSpan); } // A variation on the ReplaceElementsAt method defined above. template [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, size_type aCount, const Item& aItem, const mozilla::fallible_t&) { return ReplaceElementsAtInternal(aStart, aCount, aItem); } // A variation on the ReplaceElementsAt method defined above. template mozilla::NotNull ReplaceElementAt(index_type aIndex, Item&& aItem) { value_type* const elem = &ElementAt(aIndex); elem_traits::Destruct(elem); elem_traits::Construct(elem, std::forward(aItem)); return mozilla::WrapNotNullUnchecked(elem); } // InsertElementsAt is ReplaceElementsAt with 0 elements to replace. // XXX Provide a proper documentation of InsertElementsAt. template [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, const Item* aArray, size_type aArrayLen, const mozilla::fallible_t&) { return ReplaceElementsAtInternal(aIndex, 0, aArray, aArrayLen); } template [[nodiscard]] value_type* InsertElementsAt( index_type aIndex, const nsTArray_Impl& aArray, const mozilla::fallible_t&) { return ReplaceElementsAtInternal( aIndex, 0, aArray.Elements(), aArray.Length()); } template [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, mozilla::Span aSpan, const mozilla::fallible_t&) { return ReplaceElementsAtInternal(aIndex, 0, aSpan.Elements(), aSpan.Length()); } private: template value_type* InsertElementAtInternal(index_type aIndex); // Insert a new element without copy-constructing. This is useful to avoid // temporaries. // @return A pointer to the newly inserted element, or null on OOM. public: [[nodiscard]] value_type* InsertElementAt(index_type aIndex, const mozilla::fallible_t&) { return InsertElementAtInternal(aIndex); } private: template value_type* InsertElementAtInternal(index_type aIndex, Item&& aItem); // Insert a new element, move constructing if possible. public: template [[nodiscard]] value_type* InsertElementAt(index_type aIndex, Item&& aItem, const mozilla::fallible_t&) { return InsertElementAtInternal(aIndex, std::forward(aItem)); } // Reconstruct the element at the given index, and return a pointer to the // reconstructed element. This will destroy the existing element and // default-construct a new one, giving you a state much like what single-arg // InsertElementAt(), or no-arg AppendElement() does, but without changing the // length of the array. // // array[idx] = value_type() // // would accomplish the same thing as long as value_type has the appropriate // moving operator=, but some types don't for various reasons. mozilla::NotNull ReconstructElementAt(index_type aIndex) { value_type* elem = &ElementAt(aIndex); elem_traits::Destruct(elem); elem_traits::Construct(elem); return mozilla::WrapNotNullUnchecked(elem); } // This method searches for the smallest index of an element that is strictly // greater than |aItem|. If |aItem| is inserted at this index, the array will // remain sorted and |aItem| would come after all elements that are equal to // it. If |aItem| is greater than or equal to all elements in the array, the // array length is returned. // // Note that consumers who want to know whether there are existing items equal // to |aItem| in the array can just check that the return value here is > 0 // and indexing into the previous slot gives something equal to |aItem|. // // // @param aItem The item to search for. // @param aComp The Comparator used. // @return The index of greatest element <= to |aItem| // @precondition The array is sorted template [[nodiscard]] index_type IndexOfFirstElementGt( const Item& aItem, const Comparator& aComp) const { using mozilla::BinarySearchIf; ::detail::CompareWrapper comp(aComp); size_t index; BinarySearchIf( Elements(), 0, Length(), [&](const value_type& aElement) { return comp.Compare(aElement, aItem) <= 0 ? 1 : -1; }, &index); return index; } // A variation on the IndexOfFirstElementGt method defined above. template [[nodiscard]] index_type IndexOfFirstElementGt(const Item& aItem) const { return IndexOfFirstElementGt(aItem, nsDefaultComparator()); } private: template value_type* InsertElementSortedInternal(Item&& aItem, const Comparator& aComp) { index_type index = IndexOfFirstElementGt(aItem, aComp); return InsertElementAtInternal(index, std::forward(aItem)); } // Inserts |aItem| at such an index to guarantee that if the array // was previously sorted, it will remain sorted after this // insertion. public: template [[nodiscard]] value_type* InsertElementSorted(Item&& aItem, const Comparator& aComp, const mozilla::fallible_t&) { return InsertElementSortedInternal(std::forward(aItem), aComp); } // A variation on the InsertElementSorted method defined above. public: template [[nodiscard]] value_type* InsertElementSorted(Item&& aItem, const mozilla::fallible_t&) { return InsertElementSortedInternal( std::forward(aItem), nsDefaultComparator{}); } private: template value_type* AppendElementsInternal(const Item* aArray, size_type aArrayLen); // This method appends elements to the end of this array. // @param aArray The elements to append to this array. // @param aArrayLen The number of elements to append to this array. // @return A pointer to the new elements in the array, or null if // the operation failed due to insufficient memory. public: template [[nodiscard]] value_type* AppendElements(const Item* aArray, size_type aArrayLen, const mozilla::fallible_t&) { return AppendElementsInternal(aArray, aArrayLen); } template [[nodiscard]] value_type* AppendElements(mozilla::Span aSpan, const mozilla::fallible_t&) { return AppendElementsInternal(aSpan.Elements(), aSpan.Length()); } // A variation on the AppendElements method defined above. template [[nodiscard]] value_type* AppendElements( const nsTArray_Impl& aArray, const mozilla::fallible_t&) { return AppendElementsInternal(aArray.Elements(), aArray.Length()); } private: template value_type* AppendElementsInternal(nsTArray_Impl&& aArray); // Move all elements from another array to the end of this array. // @return A pointer to the newly appended elements, or null on OOM. public: template [[nodiscard]] value_type* AppendElements( nsTArray_Impl&& aArray, const mozilla::fallible_t&) { return AppendElementsInternal(std::move(aArray)); } // Append a new element, constructed in place from the provided arguments. protected: template value_type* EmplaceBackInternal(Args&&... aItem); public: template [[nodiscard]] value_type* EmplaceBack(const mozilla::fallible_t&, Args&&... aArgs) { return EmplaceBackInternal( std::forward(aArgs)...); } private: template value_type* AppendElementInternal(Item&& aItem); // Append a new element, move constructing if possible. public: template [[nodiscard]] value_type* AppendElement(Item&& aItem, const mozilla::fallible_t&) { return AppendElementInternal(std::forward(aItem)); } private: template value_type* AppendElementsInternal(size_type aCount) { if (!ActualAlloc::Successful(this->template ExtendCapacity( Length(), aCount, sizeof(value_type)))) { return nullptr; } value_type* elems = Elements() + Length(); size_type i; for (i = 0; i < aCount; ++i) { elem_traits::Construct(elems + i); } this->IncrementLength(aCount); return elems; } // Append new elements without copy-constructing. This is useful to avoid // temporaries. // @return A pointer to the newly appended elements, or null on OOM. public: [[nodiscard]] value_type* AppendElements(size_type aCount, const mozilla::fallible_t&) { return AppendElementsInternal(aCount); } private: // Append a new element without copy-constructing. This is useful to avoid // temporaries. // @return A pointer to the newly appended element, or null on OOM. public: [[nodiscard]] value_type* AppendElement(const mozilla::fallible_t&) { return AppendElements(1, mozilla::fallible); } // This method removes a single element from this array, like // std::vector::erase. // @param pos to the element to remove const_iterator RemoveElementAt(const_iterator pos) { MOZ_ASSERT(pos.GetArray() == this); RemoveElementAt(pos.GetIndex()); return pos; } // This method removes a range of elements from this array, like // std::vector::erase. // @param first iterator to the first of elements to remove // @param last iterator to the last of elements to remove const_iterator RemoveElementsRange(const_iterator first, const_iterator last) { MOZ_ASSERT(first.GetArray() == this); MOZ_ASSERT(last.GetArray() == this); MOZ_ASSERT(last.GetIndex() >= first.GetIndex()); RemoveElementsAt(first.GetIndex(), last.GetIndex() - first.GetIndex()); return first; } // This method removes a range of elements from this array. // @param aStart The starting index of the elements to remove. // @param aCount The number of elements to remove. void RemoveElementsAt(index_type aStart, size_type aCount); private: // Remove a range of elements from this array, but do not check that // the range is in bounds. // @param aStart The starting index of the elements to remove. // @param aCount The number of elements to remove. void RemoveElementsAtUnsafe(index_type aStart, size_type aCount); public: // Similar to the above, but it removes just one element. This does bounds // checking only in debug builds. void RemoveElementAtUnsafe(index_type aIndex) { MOZ_ASSERT(aIndex < Length(), "Trying to remove an invalid element"); RemoveElementsAtUnsafe(aIndex, 1); } // A variation on the RemoveElementsAt method defined above. void RemoveElementAt(index_type aIndex) { RemoveElementsAt(aIndex, 1); } // A variation on RemoveElementAt that removes the last element. void RemoveLastElement() { RemoveLastElements(1); } // A variation on RemoveElementsAt that removes the last 'aCount' elements. void RemoveLastElements(const size_type aCount) { // This assertion is redundant, but produces a better error message than the // release assertion within TruncateLength. MOZ_ASSERT(aCount <= Length()); TruncateLength(Length() - aCount); } // Removes the last element of the array and returns a copy of it. [[nodiscard]] value_type PopLastElement() { // This function intentionally does not call ElementsAt and calls // TruncateLengthUnsafe directly to avoid multiple release checks for // non-emptiness. // This debug assertion is redundant, but produces a better error message // than the release assertion below. MOZ_ASSERT(!base_type::IsEmpty()); const size_type oldLen = Length(); if (MOZ_UNLIKELY(0 == oldLen)) { mozilla::detail::InvalidArrayIndex_CRASH(1, 0); } value_type elem = std::move(Elements()[oldLen - 1]); TruncateLengthUnsafe(oldLen - 1); return elem; } // This method performs index-based removals from an array without preserving // the order of the array. This is useful if you are using the array as a // set-like data structure. // // These removals are efficient, as they move as few elements as possible. At // most N elements, where N is the number of removed elements, will have to // be relocated. // // ## Examples // // When removing an element from the end of the array, it can be removed in // place, by destroying it and decrementing the length. // // [ 1, 2, 3 ] => [ 1, 2 ] // ^ // // When removing any other single element, it is removed by swapping it with // the last element, and then decrementing the length as before. // // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ] // ^ // // This method also supports efficiently removing a range of elements. If they // are at the end, then they can all be removed like in the one element case. // // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ] // ^--------^ // // If more elements are removed than exist after the removed section, the // remaining elements will be shifted down like in a normal removal. // // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ] // ^--------^ // // And if fewer elements are removed than exist after the removed section, // elements will be moved from the end of the array to fill the vacated space. // // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ] // ^--^ // // @param aStart The starting index of the elements to remove. @param aCount // The number of elements to remove. void UnorderedRemoveElementsAt(index_type aStart, size_type aCount); // A variation on the UnorderedRemoveElementsAt method defined above to remove // a single element. This operation is sometimes called `SwapRemove`. // // This method is O(1), but does not preserve the order of the elements. void UnorderedRemoveElementAt(index_type aIndex) { UnorderedRemoveElementsAt(aIndex, 1); } void Clear() { ClearAndRetainStorage(); base_type::ShrinkCapacityToZero(); } // This method removes elements based on the return value of the // callback function aPredicate. If the function returns true for // an element, the element is removed. aPredicate will be called // for each element in order. It is not safe to access the array // inside aPredicate. // // Returns the number of elements removed. template size_type RemoveElementsBy(Predicate aPredicate); // This helper function combines IndexOf with RemoveElementAt to "search // and destroy" the first element that is equal to the given element. // @param aItem The item to search for. // @param aComp The Comparator used to determine element equality. // @return true if the element was found template bool RemoveElement(const Item& aItem, const Comparator& aComp) { index_type i = IndexOf(aItem, 0, aComp); if (i == NoIndex) { return false; } RemoveElementsAtUnsafe(i, 1); return true; } // A variation on the RemoveElement method defined above that assumes // that 'operator==' is defined for value_type. template bool RemoveElement(const Item& aItem) { return RemoveElement(aItem, nsDefaultComparator()); } // Variations for RemoveElement that uses unordered removal. template bool UnorderedRemoveElement(const Item& aItem, const Comparator& aComp) { index_type i = IndexOf(aItem, 0, aComp); if (i == NoIndex) { return false; } UnorderedRemoveElementAt(i); return true; } template bool UnorderedRemoveElement(const Item& aItem) { return UnorderedRemoveElement(aItem, nsDefaultComparator()); } // This helper function combines IndexOfFirstElementGt with // RemoveElementAt to "search and destroy" the last element that // is equal to the given element. // @param aItem The item to search for. // @param aComp The Comparator used to determine element equality. // @return true if the element was found template bool RemoveElementSorted(const Item& aItem, const Comparator& aComp) { index_type index = IndexOfFirstElementGt(aItem, aComp); if (index > 0 && aComp.Equals(ElementAt(index - 1), aItem)) { RemoveElementsAtUnsafe(index - 1, 1); return true; } return false; } // A variation on the RemoveElementSorted method defined above. template bool RemoveElementSorted(const Item& aItem) { return RemoveElementSorted(aItem, nsDefaultComparator()); } // This method causes the elements contained in this array and the given // array to be swapped. template void SwapElements(nsTArray_Impl& aOther) { // The only case this might fail were if someone called this with a // AutoTArray upcast to nsTArray_Impl, under the conditions mentioned in the // overload for AutoTArray below. this->template SwapArrayElements(aOther, sizeof(value_type)); } template void SwapElements(AutoTArray& aOther) { // Allocation might fail if Alloc==FallibleAlloc and // Allocator==InfallibleAlloc and aOther uses auto storage. Allow this for // small inline sizes, and crash in the rare case of a small OOM error. static_assert(!std::is_same_v || sizeof(E) * N <= 1024); this->template SwapArrayElements(aOther, sizeof(value_type)); } template [[nodiscard]] auto SwapElements(nsTArray_Impl& aOther, const mozilla::fallible_t&) { // Allocation might fail if Alloc==FallibleAlloc and // Allocator==InfallibleAlloc and aOther uses auto storage. return FallibleAlloc::Result( this->template SwapArrayElements(aOther, sizeof(value_type))); } private: // Used by ApplyIf functions to invoke a callable that takes either: // - Nothing: F(void) // - Index only: F(size_t) // - Reference to element only: F(maybe-const value_type&) // - Both index and reference: F(size_t, maybe-const value_type&) // `value_type` must be const when called from const method. template struct InvokeWithIndexAndOrReferenceHelper { static constexpr bool valid = false; }; template struct InvokeWithIndexAndOrReferenceHelper { static constexpr bool valid = true; template static auto Invoke(F&& f, size_t, T&) { return f(); } }; template struct InvokeWithIndexAndOrReferenceHelper { static constexpr bool valid = true; template static auto Invoke(F&& f, size_t i, T&) { return f(i); } }; template struct InvokeWithIndexAndOrReferenceHelper { static constexpr bool valid = true; template static auto Invoke(F&& f, size_t, T& e) { return f(e); } }; template struct InvokeWithIndexAndOrReferenceHelper { static constexpr bool valid = true; template static auto Invoke(F&& f, size_t, T& e) { return f(e); } }; template struct InvokeWithIndexAndOrReferenceHelper { static constexpr bool valid = true; template static auto Invoke(F&& f, size_t i, T& e) { return f(i, e); } }; template struct InvokeWithIndexAndOrReferenceHelper { static constexpr bool valid = true; template static auto Invoke(F&& f, size_t i, T& e) { return f(i, e); } }; template static auto InvokeWithIndexAndOrReference(F&& f, size_t i, T& e) { using Invoker = InvokeWithIndexAndOrReferenceHelper< T, typename mozilla::FunctionTypeTraits::template ParameterType<0>, typename mozilla::FunctionTypeTraits::template ParameterType<1>>; static_assert(Invoker::valid, "ApplyIf's Function parameters must match either: (void), " "(size_t), (maybe-const value_type&), or " "(size_t, maybe-const value_type&)"); return Invoker::Invoke(std::forward(f), i, e); } public: // 'Apply' family of methods. // // The advantages of using Apply methods with lambdas include: // - Safety of accessing elements from within the call, when the array cannot // have been modified between the iteration and the subsequent access. // - Avoiding moot conversions: pointer->index during a search, followed by // index->pointer after the search when accessing the element. // - Embedding your code into the algorithm, giving the compiler more chances // to optimize. // Search for the first element comparing equal to aItem with the given // comparator (`==` by default). // If such an element exists, return the result of evaluating either: // - `aFunction()` // - `aFunction(index_type)` // - `aFunction(maybe-const? value_type&)` // - `aFunction(index_type, maybe-const? value_type&)` // (`aFunction` must have one of the above signatures with these exact types, // including references; implicit conversions or generic types not allowed. // If `this` array is const, the referenced `value_type` must be const too; // otherwise it may be either const or non-const.) // But if the element is not found, return the result of evaluating // `aFunctionElse()`. template auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp, Function&& aFunction, FunctionElse&& aFunctionElse) const { static_assert( std::is_same_v< typename mozilla::FunctionTypeTraits::ReturnType, typename mozilla::FunctionTypeTraits::ReturnType>, "ApplyIf's `Function` and `FunctionElse` must return the same type."); ::detail::CompareWrapper comp(aComp); const value_type* const elements = Elements(); const value_type* const iend = elements + Length(); for (const value_type* iter = elements + aStart; iter != iend; ++iter) { if (comp.Equals(*iter, aItem)) { return InvokeWithIndexAndOrReference( std::forward(aFunction), iter - elements, *iter); } } return aFunctionElse(); } template auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp, Function&& aFunction, FunctionElse&& aFunctionElse) { static_assert( std::is_same_v< typename mozilla::FunctionTypeTraits::ReturnType, typename mozilla::FunctionTypeTraits::ReturnType>, "ApplyIf's `Function` and `FunctionElse` must return the same type."); ::detail::CompareWrapper comp(aComp); value_type* const elements = Elements(); value_type* const iend = elements + Length(); for (value_type* iter = elements + aStart; iter != iend; ++iter) { if (comp.Equals(*iter, aItem)) { return InvokeWithIndexAndOrReference( std::forward(aFunction), iter - elements, *iter); } } return aFunctionElse(); } template auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction, FunctionElse&& aFunctionElse) const { return ApplyIf(aItem, aStart, nsDefaultComparator(), std::forward(aFunction), std::forward(aFunctionElse)); } template auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction, FunctionElse&& aFunctionElse) { return ApplyIf(aItem, aStart, nsDefaultComparator(), std::forward(aFunction), std::forward(aFunctionElse)); } template auto ApplyIf(const Item& aItem, Function&& aFunction, FunctionElse&& aFunctionElse) const { return ApplyIf(aItem, 0, std::forward(aFunction), std::forward(aFunctionElse)); } template auto ApplyIf(const Item& aItem, Function&& aFunction, FunctionElse&& aFunctionElse) { return ApplyIf(aItem, 0, std::forward(aFunction), std::forward(aFunctionElse)); } // // Allocation // // This method may increase the capacity of this array object to the // specified amount. This method may be called in advance of several // AppendElement operations to minimize heap re-allocations. This method // will not reduce the number of elements in this array. // @param aCapacity The desired capacity of this array. // @return True if the operation succeeded; false if we ran out of memory protected: template typename ActualAlloc::ResultType SetCapacity(size_type aCapacity) { return ActualAlloc::Result(this->template EnsureCapacity( aCapacity, sizeof(value_type))); } public: [[nodiscard]] bool SetCapacity(size_type aCapacity, const mozilla::fallible_t&) { return SetCapacity(aCapacity); } // This method modifies the length of the array. If the new length is // larger than the existing length of the array, then new elements will be // constructed using value_type's default constructor. Otherwise, this call // removes elements from the array (see also RemoveElementsAt). // @param aNewLen The desired length of this array. // @return True if the operation succeeded; false otherwise. // See also TruncateLength for a more efficient variant if the new length is // guaranteed to be smaller than the old. protected: template typename ActualAlloc::ResultType SetLength(size_type aNewLen) { const size_type oldLen = Length(); if (aNewLen > oldLen) { return ActualAlloc::ConvertBoolToResultType( InsertElementsAtInternal(oldLen, aNewLen - oldLen) != nullptr); } TruncateLengthUnsafe(aNewLen); return ActualAlloc::ConvertBoolToResultType(true); } public: [[nodiscard]] bool SetLength(size_type aNewLen, const mozilla::fallible_t&) { return SetLength(aNewLen); } // This method modifies the length of the array, but may only be // called when the new length is shorter than the old. It can // therefore be called when value_type has no default constructor, // unlike SetLength. It removes elements from the array (see also // RemoveElementsAt). // @param aNewLen The desired length of this array. void TruncateLength(size_type aNewLen) { // This assertion is redundant, but produces a better error message than the // release assertion below. MOZ_ASSERT(aNewLen <= Length(), "caller should use SetLength instead"); if (MOZ_UNLIKELY(aNewLen > Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aNewLen, Length()); } TruncateLengthUnsafe(aNewLen); } private: void TruncateLengthUnsafe(size_type aNewLen) { const size_type oldLen = Length(); if (oldLen) { DestructRange(aNewLen, oldLen - aNewLen); base_type::mHdr->mLength = aNewLen; } } // This method ensures that the array has length at least the given // length. If the current length is shorter than the given length, // then new elements will be constructed using value_type's default // constructor. // @param aMinLen The desired minimum length of this array. // @return True if the operation succeeded; false otherwise. protected: template typename ActualAlloc::ResultType EnsureLengthAtLeast(size_type aMinLen) { size_type oldLen = Length(); if (aMinLen > oldLen) { return ActualAlloc::ConvertBoolToResultType( !!InsertElementsAtInternal(oldLen, aMinLen - oldLen)); } return ActualAlloc::ConvertBoolToResultType(true); } public: [[nodiscard]] bool EnsureLengthAtLeast(size_type aMinLen, const mozilla::fallible_t&) { return EnsureLengthAtLeast(aMinLen); } // This method inserts elements into the array, constructing // them using value_type's default constructor. // @param aIndex the place to insert the new elements. This must be no // greater than the current length of the array. // @param aCount the number of elements to insert private: template value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount) { if (!ActualAlloc::Successful(this->template InsertSlotsAt( aIndex, aCount, sizeof(value_type)))) { return nullptr; } // Initialize the extra array elements value_type* iter = Elements() + aIndex; value_type* iend = iter + aCount; for (; iter != iend; ++iter) { elem_traits::Construct(iter); } return Elements() + aIndex; } public: [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, size_type aCount, const mozilla::fallible_t&) { return InsertElementsAtInternal(aIndex, aCount); } // This method inserts elements into the array, constructing them // value_type's copy constructor (or whatever one-arg constructor // happens to match the Item type). // @param aIndex the place to insert the new elements. This must be no // greater than the current length of the array. // @param aCount the number of elements to insert. // @param aItem the value to use when constructing the new elements. private: template value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount, const Item& aItem); public: template [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, size_type aCount, const Item& aItem, const mozilla::fallible_t&) { return InsertElementsAt(aIndex, aCount, aItem); } // This method may be called to minimize the memory used by this array. void Compact() { ShrinkCapacity(sizeof(value_type)); } // // Sorting // // This method sorts the elements of the array. It uses the LessThan // method defined on the given Comparator object to collate elements or // it wraps a tri-state comparison lambda into such a comparator. // It uses std::sort. It expects value_type to be move assignable and // constructible and uses those always, regardless of the chosen // nsTArray_RelocationStrategy. // // @param aComp The Comparator used to collate elements. template void Sort(const Comparator& aComp) { static_assert(std::is_move_assignable_v); static_assert(std::is_move_constructible_v); ::detail::CompareWrapper comp(aComp); auto compFn = [&comp](const auto& left, const auto& right) { return comp.LessThan(left, right); }; if constexpr (Check == SortBoundsCheck::Enable) { std::sort(begin(), end(), compFn); } else { std::sort(Elements(), Elements() + Length(), compFn); } ::detail::AssertStrictWeakOrder(Elements(), Elements() + Length(), compFn); } // A variation on the Sort method defined above that assumes that // 'operator<' is defined for 'value_type'. template void Sort() { Sort(nsDefaultComparator()); } // This method sorts the elements of the array in a stable way (i.e. not // changing the relative order of elements considered equal by the // Comparator). It uses the LessThan method defined on the given Comparator // object to collate elements. // It uses std::stable_sort. It expects value_type to be move assignable and // constructible and uses those always, regardless of the chosen // nsTArray_RelocationStrategy. // // @param aComp The Comparator used to collate elements. template void StableSort(const Comparator& aComp) { static_assert(std::is_move_assignable_v); static_assert(std::is_move_constructible_v); const ::detail::CompareWrapper comp( aComp); auto compFn = [&comp](const auto& lhs, const auto& rhs) { return comp.LessThan(lhs, rhs); }; if constexpr (Check == SortBoundsCheck::Enable) { std::stable_sort(begin(), end(), compFn); } else { std::stable_sort(Elements(), Elements() + Length(), compFn); } ::detail::AssertStrictWeakOrder(Elements(), Elements() + Length(), compFn); } // A variation on the StableSort method defined above that assumes that // 'operator<' is defined for 'value_type'. template void StableSort() { StableSort(nsDefaultComparator()); } // This method reverses the array in place. void Reverse() { value_type* elements = Elements(); const size_type len = Length(); for (index_type i = 0, iend = len / 2; i < iend; ++i) { std::swap(elements[i], elements[len - i - 1]); } } protected: using base_type::Hdr; using base_type::ShrinkCapacity; // This method invokes value_type's destructor on a range of elements. // @param aStart The index of the first element to destroy. // @param aCount The number of elements to destroy. void DestructRange(index_type aStart, size_type aCount) { value_type* iter = Elements() + aStart; value_type* iend = iter + aCount; for (; iter != iend; ++iter) { elem_traits::Destruct(iter); } } // This method invokes value_type's copy-constructor on a range of elements. // @param aStart The index of the first element to construct. // @param aCount The number of elements to construct. // @param aValues The array of elements to copy. template void AssignRange(index_type aStart, size_type aCount, const Item* aValues) { std::uninitialized_copy(aValues, aValues + aCount, Elements() + aStart); } }; template template auto nsTArray_Impl::AssignInternal(const Item* aArray, size_type aArrayLen) -> typename ActualAlloc::ResultType { static_assert(std::is_same_v || std::is_same_v); if constexpr (std::is_same_v) { ClearAndRetainStorage(); } // Adjust memory allocation up-front to catch errors in the fallible case. // We might relocate the elements to be destroyed unnecessarily. This could be // optimized, but would make things more complicated. if (!ActualAlloc::Successful(this->template EnsureCapacity( aArrayLen, sizeof(value_type)))) { return ActualAlloc::ConvertBoolToResultType(false); } MOZ_ASSERT_IF(this->HasEmptyHeader(), aArrayLen == 0); if (!this->HasEmptyHeader()) { if constexpr (std::is_same_v) { ClearAndRetainStorage(); } AssignRange(0, aArrayLen, aArray); base_type::mHdr->mLength = aArrayLen; } return ActualAlloc::ConvertBoolToResultType(true); } template template auto nsTArray_Impl::ReplaceElementsAtInternal(index_type aStart, size_type aCount, const Item* aArray, size_type aArrayLen) -> value_type* { if (MOZ_UNLIKELY(aStart > Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); } if (MOZ_UNLIKELY(aCount > Length() - aStart)) { mozilla::detail::InvalidArrayIndex_CRASH(aStart + aCount, Length()); } // Adjust memory allocation up-front to catch errors. if (!ActualAlloc::Successful(this->template EnsureCapacity( Length() + aArrayLen - aCount, sizeof(value_type)))) { return nullptr; } DestructRange(aStart, aCount); this->template ShiftData(aStart, aCount, aArrayLen, sizeof(value_type)); AssignRange(aStart, aArrayLen, aArray); return Elements() + aStart; } template void nsTArray_Impl::RemoveElementsAt(index_type aStart, size_type aCount) { MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index"); mozilla::CheckedInt rangeEnd = aStart; rangeEnd += aCount; if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); } RemoveElementsAtUnsafe(aStart, aCount); } template void nsTArray_Impl::RemoveElementsAtUnsafe(index_type aStart, size_type aCount) { DestructRange(aStart, aCount); this->template ShiftData(aStart, aCount, 0, sizeof(value_type)); } template void nsTArray_Impl::UnorderedRemoveElementsAt(index_type aStart, size_type aCount) { MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index"); mozilla::CheckedInt rangeEnd = aStart; rangeEnd += aCount; if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); } // Destroy the elements which are being removed, and then swap elements in to // replace them from the end. See the docs on the declaration of this // function. DestructRange(aStart, aCount); this->template SwapFromEnd(aStart, aCount, sizeof(value_type)); } template template auto nsTArray_Impl::RemoveElementsBy(Predicate aPredicate) -> size_type { if (this->HasEmptyHeader()) { return 0; } index_type j = 0; const index_type len = Length(); value_type* const elements = Elements(); for (index_type i = 0; i < len; ++i) { const bool result = aPredicate(elements[i]); // Check that the array has not been modified by the predicate. MOZ_DIAGNOSTIC_ASSERT(len == base_type::mHdr->mLength && elements == Elements()); if (result) { elem_traits::Destruct(elements + i); } else { if (j < i) { relocation_type::RelocateNonOverlappingRegion( elements + j, elements + i, 1, sizeof(value_type)); } ++j; } } base_type::mHdr->mLength = j; return len - j; } template template auto nsTArray_Impl::InsertElementsAtInternal(index_type aIndex, size_type aCount, const Item& aItem) -> value_type* { if (!ActualAlloc::Successful(this->template InsertSlotsAt( aIndex, aCount, sizeof(value_type)))) { return nullptr; } // Initialize the extra array elements value_type* iter = Elements() + aIndex; value_type* iend = iter + aCount; for (; iter != iend; ++iter) { elem_traits::Construct(iter, aItem); } return Elements() + aIndex; } template template auto nsTArray_Impl::InsertElementAtInternal(index_type aIndex) -> value_type* { if (MOZ_UNLIKELY(aIndex > Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); } // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. if (!ActualAlloc::Successful(this->template EnsureCapacity( Length() + 1, sizeof(value_type)))) { return nullptr; } this->template ShiftData(aIndex, 0, 1, sizeof(value_type)); value_type* elem = Elements() + aIndex; elem_traits::Construct(elem); return elem; } template template auto nsTArray_Impl::InsertElementAtInternal(index_type aIndex, Item&& aItem) -> value_type* { if (MOZ_UNLIKELY(aIndex > Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); } // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. if (!ActualAlloc::Successful(this->template EnsureCapacity( Length() + 1, sizeof(value_type)))) { return nullptr; } this->template ShiftData(aIndex, 0, 1, sizeof(value_type)); value_type* elem = Elements() + aIndex; elem_traits::Construct(elem, std::forward(aItem)); return elem; } template template auto nsTArray_Impl::AppendElementsInternal(const Item* aArray, size_type aArrayLen) -> value_type* { if (!ActualAlloc::Successful(this->template ExtendCapacity( Length(), aArrayLen, sizeof(value_type)))) { return nullptr; } index_type len = Length(); AssignRange(len, aArrayLen, aArray); this->IncrementLength(aArrayLen); return Elements() + len; } template template auto nsTArray_Impl::AppendElementsInternal( nsTArray_Impl&& aArray) -> value_type* { if constexpr (std::is_same_v) { MOZ_ASSERT(&aArray != this, "argument must be different aArray"); } index_type len = Length(); if (len == 0) { // XXX This might still be optimized. If aArray uses auto-storage but we // won't, we might better retain our storage if it's sufficiently large. this->ShrinkCapacityToZero(); this->MoveInit(aArray, sizeof(value_type)); return Elements(); } index_type otherLen = aArray.Length(); if (!ActualAlloc::Successful(this->template ExtendCapacity( len, otherLen, sizeof(value_type)))) { return nullptr; } relocation_type::RelocateNonOverlappingRegion( Elements() + len, aArray.Elements(), otherLen, sizeof(value_type)); this->IncrementLength(otherLen); aArray.template ShiftData(0, otherLen, 0, sizeof(value_type)); return Elements() + len; } template template auto nsTArray_Impl::AppendElementInternal(Item&& aItem) -> value_type* { // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. if (!ActualAlloc::Successful(this->template EnsureCapacity( Length() + 1, sizeof(value_type)))) { return nullptr; } value_type* elem = Elements() + Length(); elem_traits::Construct(elem, std::forward(aItem)); this->mHdr->mLength += 1; return elem; } template template auto nsTArray_Impl::EmplaceBackInternal(Args&&... aArgs) -> value_type* { // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. if (!ActualAlloc::Successful(this->template EnsureCapacity( Length() + 1, sizeof(value_type)))) { return nullptr; } value_type* elem = Elements() + Length(); elem_traits::Emplace(elem, std::forward(aArgs)...); this->mHdr->mLength += 1; return elem; } template inline void ImplCycleCollectionUnlink(nsTArray_Impl& aField) { aField.Clear(); } template inline void ImplCycleCollectionIndexedContainer(nsTArray_Impl& aField, Callback&& aCallback) { for (auto& value : aField) { aCallback(value); } } // // nsTArray is an infallible vector class. See the comment at the top of this // file for more details. // template class MOZ_GSL_OWNER nsTArray : public nsTArray_Impl { public: using InfallibleAlloc = nsTArrayInfallibleAllocator; using base_type = nsTArray_Impl; using self_type = nsTArray; using typename base_type::index_type; using typename base_type::size_type; using typename base_type::value_type; constexpr nsTArray() {} explicit nsTArray(size_type aCapacity) : base_type(aCapacity) {} MOZ_IMPLICIT nsTArray(std::initializer_list aIL) { AppendElements(aIL.begin(), aIL.size()); } template nsTArray(const Item* aArray, size_type aArrayLen) { AppendElements(aArray, aArrayLen); } template explicit nsTArray(mozilla::Span aSpan) { AppendElements(aSpan); } template explicit nsTArray(const nsTArray_Impl& aOther) : base_type(aOther) {} template MOZ_IMPLICIT nsTArray(nsTArray_Impl&& aOther) : base_type(std::move(aOther)) {} template self_type& operator=(const nsTArray_Impl& aOther) { base_type::operator=(aOther); return *this; } template self_type& operator=(nsTArray_Impl&& aOther) { // This is quite complex, since we don't know if we are an AutoTArray. While // AutoTArray overrides this operator=, this might be called on a nsTArray& // bound to an AutoTArray. base_type::operator=(std::move(aOther)); return *this; } using base_type::AppendElement; using base_type::AppendElements; using base_type::EmplaceBack; using base_type::EnsureLengthAtLeast; using base_type::InsertElementAt; using base_type::InsertElementsAt; using base_type::InsertElementSorted; using base_type::ReplaceElementsAt; using base_type::SetCapacity; using base_type::SetLength; template mozilla::NotNull AppendElements(const Item* aArray, size_type aArrayLen) { return mozilla::WrapNotNullUnchecked( this->template AppendElementsInternal(aArray, aArrayLen)); } template mozilla::NotNull AppendElements(mozilla::Span aSpan) { return mozilla::WrapNotNullUnchecked( this->template AppendElementsInternal(aSpan.Elements(), aSpan.Length())); } template mozilla::NotNull AppendElements( const nsTArray_Impl& aArray) { return mozilla::WrapNotNullUnchecked( this->template AppendElementsInternal( aArray.Elements(), aArray.Length())); } template mozilla::NotNull AppendElements( nsTArray_Impl&& aArray) { return mozilla::WrapNotNullUnchecked( this->template AppendElementsInternal( std::move(aArray))); } template mozilla::NotNull AppendElement(Item&& aItem) { return mozilla::WrapNotNullUnchecked( this->template AppendElementInternal( std::forward(aItem))); } mozilla::NotNull AppendElements(size_type aCount) { return mozilla::WrapNotNullUnchecked( this->template AppendElementsInternal(aCount)); } mozilla::NotNull AppendElement() { return mozilla::WrapNotNullUnchecked( this->template AppendElementsInternal(1)); } self_type Clone() const { self_type result; result.Assign(*this); return result; } mozilla::NotNull InsertElementsAt(index_type aIndex, size_type aCount) { return mozilla::WrapNotNullUnchecked( this->template InsertElementsAtInternal(aIndex, aCount)); } template mozilla::NotNull InsertElementsAt(index_type aIndex, size_type aCount, const Item& aItem) { return mozilla::WrapNotNullUnchecked( this->template InsertElementsAtInternal(aIndex, aCount, aItem)); } template mozilla::NotNull InsertElementsAt(index_type aIndex, const Item* aArray, size_type aArrayLen) { return mozilla::WrapNotNullUnchecked( this->template ReplaceElementsAtInternal( aIndex, 0, aArray, aArrayLen)); } template mozilla::NotNull InsertElementsAt( index_type aIndex, const nsTArray_Impl& aArray) { return mozilla::WrapNotNullUnchecked( this->template ReplaceElementsAtInternal( aIndex, 0, aArray.Elements(), aArray.Length())); } template mozilla::NotNull InsertElementsAt(index_type aIndex, mozilla::Span aSpan) { return mozilla::WrapNotNullUnchecked( this->template ReplaceElementsAtInternal( aIndex, 0, aSpan.Elements(), aSpan.Length())); } mozilla::NotNull InsertElementAt(index_type aIndex) { return mozilla::WrapNotNullUnchecked( this->template InsertElementAtInternal(aIndex)); } template mozilla::NotNull InsertElementAt(index_type aIndex, Item&& aItem) { return mozilla::WrapNotNullUnchecked( this->template InsertElementAtInternal( aIndex, std::forward(aItem))); } template mozilla::NotNull ReplaceElementsAt(index_type aStart, size_type aCount, const Item* aArray, size_type aArrayLen) { return mozilla::WrapNotNullUnchecked( this->template ReplaceElementsAtInternal( aStart, aCount, aArray, aArrayLen)); } template mozilla::NotNull ReplaceElementsAt( index_type aStart, size_type aCount, const nsTArray& aArray) { return ReplaceElementsAt(aStart, aCount, aArray.Elements(), aArray.Length()); } template mozilla::NotNull ReplaceElementsAt(index_type aStart, size_type aCount, mozilla::Span aSpan) { return ReplaceElementsAt(aStart, aCount, aSpan.Elements(), aSpan.Length()); } template mozilla::NotNull ReplaceElementsAt(index_type aStart, size_type aCount, const Item& aItem) { return ReplaceElementsAt(aStart, aCount, &aItem, 1); } template mozilla::NotNull InsertElementSorted(Item&& aItem, const Comparator& aComp) { return mozilla::WrapNotNullUnchecked( this->template InsertElementSortedInternal( std::forward(aItem), aComp)); } template mozilla::NotNull InsertElementSorted(Item&& aItem) { return mozilla::WrapNotNullUnchecked( this->template InsertElementSortedInternal( std::forward(aItem), nsDefaultComparator{})); } template mozilla::NotNull EmplaceBack(Args&&... aArgs) { return mozilla::WrapNotNullUnchecked( this->template EmplaceBackInternal( std::forward(aArgs)...)); } }; template class MOZ_GSL_OWNER CopyableTArray : public nsTArray { public: using nsTArray::nsTArray; CopyableTArray(const CopyableTArray& aOther) : nsTArray() { this->Assign(aOther); } CopyableTArray& operator=(const CopyableTArray& aOther) { if (this != &aOther) { this->Assign(aOther); } return *this; } template MOZ_IMPLICIT CopyableTArray(const nsTArray_Impl& aOther) { this->Assign(aOther); } template CopyableTArray& operator=(const nsTArray_Impl& aOther) { if constexpr (std::is_same_v) { if (this == &aOther) { return *this; } } this->Assign(aOther); return *this; } template MOZ_IMPLICIT CopyableTArray(nsTArray_Impl&& aOther) : nsTArray{std::move(aOther)} {} template CopyableTArray& operator=(nsTArray_Impl&& aOther) { static_cast&>(*this) = std::move(aOther); return *this; } CopyableTArray(CopyableTArray&&) = default; CopyableTArray& operator=(CopyableTArray&&) = default; }; // // FallibleTArray is a fallible vector class. // template class MOZ_GSL_OWNER FallibleTArray : public nsTArray_Impl { public: typedef nsTArray_Impl base_type; typedef FallibleTArray self_type; typedef typename base_type::size_type size_type; FallibleTArray() = default; explicit FallibleTArray(size_type aCapacity) : base_type(aCapacity) {} template explicit FallibleTArray(const nsTArray_Impl& aOther) : base_type(aOther) {} template explicit FallibleTArray(nsTArray_Impl&& aOther) : base_type(std::move(aOther)) {} template self_type& operator=(const nsTArray_Impl& aOther) { base_type::operator=(aOther); return *this; } template self_type& operator=(nsTArray_Impl&& aOther) { base_type::operator=(std::move(aOther)); return *this; } }; // // AutoTArray is like nsTArray, but with N elements of inline storage. // Storing more than N elements is fine, but it will cause a heap allocation. // template class MOZ_NON_MEMMOVABLE MOZ_GSL_OWNER AutoTArray : public nsTArray { static_assert(N != 0, "AutoTArray should be specialized"); public: typedef AutoTArray self_type; typedef nsTArray base_type; typedef typename base_type::Header Header; typedef typename base_type::value_type value_type; AutoTArray() { static_assert(alignof(value_type) <= 8, "can't handle alignments greater than 8, " "see nsTArray_base::UsesAutoArrayBuffer()"); static_assert(offsetof(AutoTArray, mAutoBuf) == kAutoTArrayHeaderOffset); this->mHdr = &mAutoBuf.mHdr; } AutoTArray(self_type&& aOther) : AutoTArray() { this->MoveInit(aOther, sizeof(value_type)); MOZ_ASSERT(!this->HasEmptyHeader()); } explicit AutoTArray(base_type&& aOther) : AutoTArray() { this->MoveInit(aOther, sizeof(value_type)); } template explicit AutoTArray(nsTArray_Impl&& aOther) : AutoTArray() { this->MoveInit(aOther, sizeof(value_type)); } MOZ_IMPLICIT AutoTArray(std::initializer_list aIL) : AutoTArray() { this->AppendElements(aIL.begin(), aIL.size()); } self_type& operator=(self_type&& aOther) { base_type::operator=(std::move(aOther)); return *this; } template self_type& operator=(nsTArray_Impl&& aOther) { base_type::operator=(std::move(aOther)); return *this; } // Intentionally hides nsTArray_Impl::Clone to make clones usually be // AutoTArray as well. self_type Clone() const { self_type result; result.Assign(*this); return result; } private: // Declare mAutoBuf aligned to 8 bytes so we have a constant offset to get // from the nsTArray to the buffer. This can create 4 otherwise-unnecessary // bytes of padding on 32-bit builds if value_type is not 8-byte aligned, but // that seems acceptable. struct alignas(8) AutoBuffer { nsTArrayHeader mHdr; union alignas(value_type) { // FIXME: Conceptually `value_type mElements[N]`, but that upsets the // hazard analysis, see bug 1880185. char mStorage[sizeof(value_type) * N]; }; AutoBuffer() : mHdr{.mLength = 0, .mCapacity = N, .mIsAutoArray = true} {} ~AutoBuffer() = default; } mAutoBuf; static_assert(offsetof(AutoBuffer, mStorage) == sizeof(nsTArrayHeader), "Shouldn't have padding"); }; // // Specialization of AutoTArray for the case where N == 0. // AutoTArray behaves exactly like nsTArray, but without this // specialization, it stores a useless inline header. // // We do have many AutoTArray objects in memory: about 2,000 per tab as // of May 2014. These are typically not explicitly AutoTArray but rather // AutoTArray for some value N depending on template parameters, in // generic code. // // For that reason, we optimize this case with the below partial specialization, // which ensures that AutoTArray is just like nsTArray, without any // inline header overhead. // template class AutoTArray : public nsTArray { using nsTArray::nsTArray; }; template struct nsTArray_RelocationStrategy> { using Type = nsTArray_RelocateUsingMoveConstructor>; }; template class CopyableAutoTArray : public AutoTArray { public: typedef CopyableAutoTArray self_type; using AutoTArray::AutoTArray; CopyableAutoTArray(const CopyableAutoTArray& aOther) : AutoTArray() { this->Assign(aOther); } CopyableAutoTArray& operator=(const CopyableAutoTArray& aOther) { if (this != &aOther) { this->Assign(aOther); } return *this; } template MOZ_IMPLICIT CopyableAutoTArray(const nsTArray_Impl& aOther) { this->Assign(aOther); } template CopyableAutoTArray& operator=(const nsTArray_Impl& aOther) { if constexpr (std::is_same_v) { if (this == &aOther) { return *this; } } this->Assign(aOther); return *this; } template MOZ_IMPLICIT CopyableAutoTArray(nsTArray_Impl&& aOther) : AutoTArray{std::move(aOther)} {} template CopyableAutoTArray& operator=(nsTArray_Impl&& aOther) { static_cast&>(*this) = std::move(aOther); return *this; } // CopyableTArray exists for cases where an explicit Clone is not possible. // These uses should not be mixed, so we delete Clone() here. self_type Clone() const = delete; CopyableAutoTArray(CopyableAutoTArray&&) = default; CopyableAutoTArray& operator=(CopyableAutoTArray&&) = default; }; // 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 nsTArray_base::~nsTArray_base() { if (!HasEmptyHeader() && !UsesAutoArrayBuffer()) { nsTArrayInfallibleAllocator::Free(mHdr); } } // defined in nsTArray.cpp bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, size_t aElemSize); template template typename Alloc::ResultTypeProxy nsTArray_base::ExtendCapacity(size_type aLength, size_type aCount, size_type aElemSize) { mozilla::CheckedInt newLength = aLength; newLength += aCount; if (!newLength.isValid()) { return Alloc::FailureResult(); } return this->EnsureCapacity(newLength.value(), aElemSize); } template template typename Alloc::ResultTypeProxy nsTArray_base::EnsureCapacityImpl(size_type aCapacity, size_type aElemSize) { MOZ_ASSERT(aCapacity > mHdr->mCapacity, "Should have been checked by caller (EnsureCapacity)"); // 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)) { Alloc::SizeTooBig((size_t)aCapacity * aElemSize); return Alloc::FailureResult(); } size_t reqSize = sizeof(Header) + aCapacity * aElemSize; if (HasEmptyHeader()) { // Malloc() new data Header* header = static_cast(Alloc::Malloc(reqSize)); if (!header) { return Alloc::FailureResult(); } header->mLength = 0; header->mCapacity = aCapacity; header->mIsAutoArray = false; mHdr = header; return Alloc::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(Alloc::Malloc(bytesToAlloc)); if (!header) { return Alloc::FailureResult(); } RelocationStrategy::RelocateNonOverlappingRegionWithHeader( header, mHdr, Length(), aElemSize); if (!UsesAutoArrayBuffer()) { Alloc::Free(mHdr); } } else { // Realloc() existing data header = static_cast(Alloc::Realloc(mHdr, bytesToAlloc)); if (!header) { return Alloc::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 Alloc::SuccessResult(); } // We don't need use Alloc template parameter specified here because failure to // shrink the capacity will leave the array unchanged. template void nsTArray_base::ShrinkCapacity(size_type aElemSize) { if (HasEmptyHeader()) { return; } size_type length = Length(); // Try to switch to our auto-buffer if possible. if (auto* autoHdr = GetAutoArrayHeader()) { if (mHdr == autoHdr) { return; } if (autoHdr->mCapacity >= length) { RelocationStrategy::RelocateNonOverlappingRegion(autoHdr + 1, mHdr + 1, length, aElemSize); autoHdr->mLength = length; nsTArrayFallibleAllocator::Free(mHdr); mHdr = autoHdr; return; } } if (length == 0) { MOZ_ASSERT(!mHdr->mIsAutoArray, "Should've been dealt with above."); nsTArrayFallibleAllocator::Free(mHdr); mHdr = EmptyHdr(); return; } if (length >= mHdr->mCapacity) { // should never be greater than... return; } size_type newSize = sizeof(Header) + length * aElemSize; Header* newHeader; if (!RelocationStrategy::allowRealloc) { // Malloc() and copy. newHeader = static_cast(nsTArrayFallibleAllocator::Malloc(newSize)); if (!newHeader) { return; } RelocationStrategy::RelocateNonOverlappingRegionWithHeader( newHeader, mHdr, Length(), aElemSize); nsTArrayFallibleAllocator::Free(mHdr); } else { // Realloc() existing data. newHeader = static_cast(nsTArrayFallibleAllocator::Realloc(mHdr, newSize)); if (!newHeader) { return; } } mHdr = newHeader; mHdr->mCapacity = length; } template void nsTArray_base::ShrinkCapacityToZero() { MOZ_ASSERT(mHdr->mLength == 0); if (HasEmptyHeader()) { return; } Header* newHdr = EmptyHdr(); if (auto* autoBuf = GetAutoArrayHeader()) { if (mHdr == autoBuf) { return; } newHdr = autoBuf; newHdr->mLength = 0; } nsTArrayFallibleAllocator::Free(mHdr); mHdr = newHdr; } template template void nsTArray_base::ShiftData(index_type aStart, size_type aOldLen, size_type aNewLen, size_type aElemSize) { 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(); return; } // 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(mHdr + 1) + aStart; RelocationStrategy::RelocateOverlappingRegion( baseAddr + aNewLen, baseAddr + aOldLen, num, aElemSize); } template template void nsTArray_base::SwapFromEnd(index_type aStart, size_type aCount, size_type aElemSize) { // 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(); 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(mHdr + 1); RelocationStrategy::RelocateNonOverlappingRegion( baseAddr + destBytes, baseAddr + sourceBytes, relocCount, aElemSize); } template template typename Alloc::ResultTypeProxy nsTArray_base::InsertSlotsAt(index_type aIndex, size_type aCount, size_type aElemSize) { if (MOZ_UNLIKELY(aIndex > Length())) { mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); } if (!Alloc::Successful( this->ExtendCapacity(Length(), aCount, aElemSize))) { return Alloc::FailureResult(); } // Move the existing elements as needed. Note that this will // change our mLength, so no need to call IncrementLength. ShiftData(aIndex, 0, aCount, aElemSize); return Alloc::SuccessResult(); } template template typename Alloc::ResultTypeProxy nsTArray_base::SwapArrayElements( nsTArray_base& aOther, size_type aElemSize) { // 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())) { auto* thisHdr = TakeHeaderForMove(aElemSize); if (MOZ_UNLIKELY(!thisHdr)) { return Alloc::FailureResult(); } auto* otherHdr = aOther.template TakeHeaderForMove(aElemSize); if (MOZ_UNLIKELY(!otherHdr)) { // Ensure thisHdr and the elements inside it are safely // cleaned up in this error case, by returning it to // being owned by this. MOZ_ASSERT(UsesAutoArrayBuffer() || HasEmptyHeader()); thisHdr->mIsAutoArray = mHdr->mIsAutoArray; mHdr = thisHdr; return Alloc::FailureResult(); } // Avoid replacing the potentially auto-buffer with the empty header if // we're empty. if (otherHdr != EmptyHdr()) { otherHdr->mIsAutoArray = mHdr->mIsAutoArray; mHdr = otherHdr; } if (thisHdr != EmptyHdr()) { thisHdr->mIsAutoArray = aOther.mHdr->mIsAutoArray; aOther.mHdr = thisHdr; } return Alloc::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 (!Alloc::Successful(EnsureCapacity(aOther.Length(), aElemSize)) || !Alloc::Successful( aOther.template EnsureCapacity(Length(), aElemSize))) { return Alloc::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 temp; if (!Alloc::Successful(temp.template EnsureCapacity( smallerLength * aElemSize, sizeof(uint8_t)))) { return Alloc::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 Alloc::SuccessResult(); } template void nsTArray_base::MoveInit( nsTArray_base& aOther, size_type aElemSize) { // 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 || UsesAutoArrayBuffer()); const auto newLength = aOther.Length(); if (aOther.HasEmptyHeader()) { return; } // If this array doesn't use an auto buffer which is big enough to store the // other array's elements, and the other array is using malloc'ed storage, // take their mHdr pointer. if (!aOther.UsesAutoArrayBuffer() && (!mHdr->mIsAutoArray || Capacity() < newLength)) { const bool thisIsAuto = mHdr->mIsAutoArray; Header* otherAutoHeader = aOther.GetAutoArrayHeader(); mHdr = aOther.mHdr; mHdr->mIsAutoArray = thisIsAuto; if (otherAutoHeader) { aOther.mHdr = otherAutoHeader; otherAutoHeader->mLength = 0; } else { aOther.mHdr = EmptyHdr(); } return; } if (newLength) { // 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(newLength, 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, newLength, aElemSize); // Swap the arrays' lengths. MOZ_ASSERT(!HasEmptyHeader() && !aOther.HasEmptyHeader(), "Both arrays should have capacity"); // Update our buffer's length, and reduce the other buffer's length. mHdr->mLength = newLength; aOther.mHdr->mLength = 0; } aOther.ShrinkCapacityToZero(); } template void nsTArray_base::MoveConstructNonAutoArray( nsTArray_base& aOther, size_type aElemSize) { // 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. Use nsTArrayInfallibleAllocator regardless of // Alloc because this is called from a move constructor, which cannot report // an error to the caller. mHdr = aOther.template TakeHeaderForMove(aElemSize); MOZ_ASSERT(!mHdr->mIsAutoArray); } template template auto nsTArray_base::TakeHeaderForMove(size_type aElemSize) -> Header* { auto* autoHdr = GetAutoArrayHeader(); if (!autoHdr) { return std::exchange(mHdr, EmptyHdr()); } if (mHdr != autoHdr) { MOZ_ASSERT(mHdr->mIsAutoArray); MOZ_ASSERT(autoHdr->mIsAutoArray); autoHdr->mLength = 0; mHdr->mIsAutoArray = false; return std::exchange(mHdr, autoHdr); } const auto length = Length(); if (!length) { return EmptyHdr(); } size_type size = sizeof(Header) + length * aElemSize; Header* header = static_cast(Alloc::Malloc(size)); if (!header) { return nullptr; } RelocationStrategy::RelocateNonOverlappingRegionWithHeader(header, mHdr, length, aElemSize); header->mCapacity = length; // This will be set by our caller if needed. header->mIsAutoArray = false; mHdr->mLength = 0; MOZ_ASSERT(UsesAutoArrayBuffer()); MOZ_ASSERT(IsEmpty()); return header; } namespace mozilla { template class nsTArrayBackInserter { ArrayT* mArray; class Proxy { ArrayT& mArray; public: explicit Proxy(ArrayT& aArray) : mArray{aArray} {} template void operator=(E2&& aValue) { mArray.AppendElement(std::forward(aValue)); } }; public: using iterator_category = std::output_iterator_tag; using value_type = void; using difference_type = void; using pointer = void; using reference = void; explicit nsTArrayBackInserter(ArrayT& aArray) : mArray{&aArray} {} // Return a proxy so that nsTArrayBackInserter has the default special member // functions, and the operator= template is defined in Proxy rather than this // class (which otherwise breaks with recent MS STL versions). // See also Bug 1331137, comment 11. Proxy operator*() { return Proxy(*mArray); } nsTArrayBackInserter& operator++() { return *this; } nsTArrayBackInserter& operator++(int) { return *this; } }; } // namespace mozilla template auto MakeBackInserter(nsTArray& aArray) { return mozilla::nsTArrayBackInserter>{aArray}; } // Span integration namespace mozilla { template Span(nsTArray_Impl&) -> Span; template Span(const nsTArray_Impl&) -> Span; // Provides a view on a nsTArray through which the existing array elements can // be accessed in a non-const way, but the array itself cannot be modified, so // that references to elements are guaranteed to be stable. template class nsTArrayView { public: using element_type = E; using pointer = element_type*; using reference = element_type&; using index_type = typename Span::index_type; using size_type = typename Span::index_type; explicit nsTArrayView(nsTArray aArray) : mArray(std::move(aArray)), mSpan(mArray) {} element_type& operator[](index_type aIndex) { return mSpan[aIndex]; } const element_type& operator[](index_type aIndex) const { return mSpan[aIndex]; } size_type Length() const { return mSpan.Length(); } auto begin() { return mSpan.begin(); } auto end() { return mSpan.end(); } auto begin() const { return mSpan.begin(); } auto end() const { return mSpan.end(); } auto cbegin() const { return mSpan.cbegin(); } auto cend() const { return mSpan.cend(); } Span AsSpan() { return mSpan; } Span AsSpan() const { return mSpan; } private: nsTArray mArray; const Span mSpan; }; // NOTE(emilio): If changing the name of this or so, make sure to change // specializations too. template ::iterator>::iterator_category, std::random_access_iterator_tag>>> size_t RangeSizeEstimate(const Range& aRange) { // See https://en.cppreference.com/w/cpp/iterator/begin, section 'User-defined // overloads'. using std::begin; using std::end; return std::distance(begin(aRange), end(aRange)); } /** * Materialize a range as a nsTArray (or a compatible variant, like AutoTArray) * of an explicitly specified type. The array value type must be implicitly * convertible from the range's value type. */ template auto ToTArray(Range&& aRange) { using std::begin; using std::end; Array res; if (auto estimate = RangeSizeEstimate(aRange)) { res.SetCapacity(estimate); } std::copy(begin(aRange), end(aRange), MakeBackInserter(res)); return res; } /** * Materialize a range as a nsTArray of its (decayed) value type. */ template auto ToArray(Range&& aRange) { return ToTArray::iterator>::value_type>>>( std::forward(aRange)); } /** * Appends all elements from a range to an array. */ template void AppendToArray(Array& aArray, Range&& aRange) { using std::begin; using std::end; if (auto estimate = RangeSizeEstimate(aRange)) { aArray.SetCapacity(aArray.Length() + estimate); } std::copy(begin(aRange), end(aRange), MakeBackInserter(aArray)); } } // namespace mozilla // MOZ_DBG support template std::ostream& operator<<(std::ostream& aOut, const nsTArray_Impl& aTArray) { return aOut << mozilla::Span(aTArray); } #endif // nsTArray_h__