Bug 687722 - Make swapping two nsAutoTArrays preserve their auto-ness when possible. r=roc
authorJustin Lebar <justin.lebar@gmail.com>
Thu, 22 Sep 2011 11:22:20 -0400
changeset 78641 51ca12bc0c44f9d1bdf1af3c70bdfafa18c057c2
parent 78640 519f498256da9d7af22d3dfd82c8e2f3db4d0aeb
child 78642 4e0c380bb90d87da81a4e83f534e492bb87875c2
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs687722
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 687722 - Make swapping two nsAutoTArrays preserve their auto-ness when possible. r=roc
xpcom/glue/nsTArray-inl.h
xpcom/glue/nsTArray.h
xpcom/tests/Makefile.in
xpcom/tests/TestTArray.cpp
--- a/xpcom/glue/nsTArray-inl.h
+++ b/xpcom/glue/nsTArray-inl.h
@@ -219,88 +219,150 @@ nsTArray_base<Alloc>::InsertSlotsAt(inde
 
   // Move the existing elements as needed.  Note that this will
   // change our mLength, so no need to call IncrementLength.
   ShiftData(index, 0, count, elementSize);
       
   return PR_TRUE;
 }
 
+// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes
+// |nsTArray_base &array| in its constructor.  When it's destructed, it ensures
+// that
+//
+//   * array.mIsAutoArray has the same value as it did when we started, and
+//   * if array has an auto buffer and mHdr would otherwise point to sEmptyHdr,
+//     array.mHdr points to array's auto buffer.
+
+template<class Alloc>
+nsTArray_base<Alloc>::IsAutoArrayRestorer::IsAutoArrayRestorer(
+  nsTArray_base<Alloc> &array) 
+  : mArray(array),
+    mIsAuto(array.IsAutoArray())
+{
+}
+
+template<class Alloc>
+nsTArray_base<Alloc>::IsAutoArrayRestorer::~IsAutoArrayRestorer() {
+  // Careful: We don't want to set mIsAutoArray = 1 on sEmptyHdr.
+  if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) {
+    // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts
+    // that mHdr->mIsAutoArray is true, which surely isn't the case here.
+    mArray.mHdr = mArray.GetAutoArrayBufferUnsafe();
+    mArray.mHdr->mLength = 0;
+  }
+  else {
+    mArray.mHdr->mIsAutoArray = mIsAuto;
+  }
+}
+
 template<class Alloc>
 template<class Allocator>
 PRBool
 nsTArray_base<Alloc>::SwapArrayElements(nsTArray_base<Allocator>& other,
                                         size_type elemSize) {
-#ifdef DEBUG
-  PRBool isAuto = IsAutoArray();
-  PRBool otherIsAuto = other.IsAutoArray();
-#endif
+
+  // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyHdr even if we have an
+  // auto buffer.  We need to point mHdr back to our auto buffer before we
+  // return, otherwise we'll forget that we have an auto buffer at all!
+  // IsAutoArrayRestorer takes care of this for us.
+
+  IsAutoArrayRestorer ourAutoRestorer(*this);
+  typename nsTArray_base<Allocator>::IsAutoArrayRestorer otherAutoRestorer(other);
+
+  // 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() < other.Length()) &&
+      (!other.UsesAutoArrayBuffer() || other.Capacity() < Length())) {
+
+    if (!EnsureNotUsingAutoArrayBuffer(elemSize) ||
+        !other.EnsureNotUsingAutoArrayBuffer(elemSize)) {
+      return PR_FALSE;
+    }
 
-  if (!EnsureNotUsingAutoArrayBuffer(elemSize) ||
-      !other.EnsureNotUsingAutoArrayBuffer(elemSize)) {
+    Header *temp = mHdr;
+    mHdr = other.mHdr;
+    other.mHdr = temp;
+
+    return PR_TRUE;
+  }
+
+  // Swap the two arrays using memcpy, since at least one is using an auto
+  // buffer which is large enough to hold all of the other'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 (!EnsureCapacity(other.Length(), elemSize) ||
+      !other.EnsureCapacity(Length(), elemSize)) {
     return PR_FALSE;
   }
 
-  NS_ASSERTION(isAuto == IsAutoArray(), "lost auto info");
-  NS_ASSERTION(otherIsAuto == other.IsAutoArray(), "lost auto info");
-  NS_ASSERTION(!UsesAutoArrayBuffer() && !other.UsesAutoArrayBuffer(),
-               "both should be using an alloced buffer now");
-
-  // If the two arrays have different mIsAutoArray values (i.e. one is
-  // an autoarray and one is not) then simply switching the buffers is
-  // going to make that bit wrong. We therefore adjust these
-  // mIsAutoArray bits before switching the buffers so that once the
-  // buffers are switched the mIsAutoArray bits are right again.
-  // However, we have to watch out so that we don't set the bit on
-  // sEmptyHeader. If an array (A) uses the empty header (and the
-  // other (B) therefore must be an nsAutoTArray) we make A point to
-  // the B's autobuffer so that when the buffers are switched B points
-  // to its own autobuffer.
+  // The EnsureCapacity calls above shouldn't have caused *both* arrays to
+  // switch from their auto buffers to malloc'ed space.
+  NS_ABORT_IF_FALSE(UsesAutoArrayBuffer() || other.UsesAutoArrayBuffer(),
+                    "One of the arrays should be using its auto buffer.");
 
-  // Adjust mIsAutoArray flags before swapping the buffers
-  if (IsAutoArray() && !other.IsAutoArray()) {
-    if (other.mHdr == EmptyHdr()) {
-      // Set other to use our built-in buffer so that we use it
-      // after the swap below.
-      other.mHdr = GetAutoArrayBuffer();
-      other.mHdr->mLength = 0;
-    }
-    else {
-      other.mHdr->mIsAutoArray = 1;
-    }
-    mHdr->mIsAutoArray = 0;
+  size_type smallerLength = NS_MIN(Length(), other.Length());
+  size_type largerLength = NS_MAX(Length(), other.Length());
+  void *smallerElements, *largerElements;
+  if (Length() <= other.Length()) {
+    smallerElements = Hdr() + 1;
+    largerElements = other.Hdr() + 1;
   }
-  else if (!IsAutoArray() && other.IsAutoArray()) {
-    if (mHdr == EmptyHdr()) {
-      // Set us to use other's built-in buffer so that other use it
-      // after the swap below.
-      mHdr = other.GetAutoArrayBuffer();
-      mHdr->mLength = 0;
-    }
-    else {
-      mHdr->mIsAutoArray = 1;
-    }
-    other.mHdr->mIsAutoArray = 0;
+  else {
+    smallerElements = other.Hdr() + 1;
+    largerElements = Hdr() + 1;
   }
 
-  // Swap the buffers
-  Header *h = other.mHdr;
-  other.mHdr = mHdr;
-  mHdr = h;
+  // Allocate temporary storage for the smaller of the two arrays.  We want to
+  // allocate this space on the stack, unless it's very 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.)
+  nsAutoTArray<PRUint8, 8192, Alloc> temp;
+  if (!temp.SetCapacity(smallerLength * elemSize)) {
+    return PR_FALSE;
+  }
 
-  NS_ASSERTION(isAuto == IsAutoArray(), "lost auto info");
-  NS_ASSERTION(otherIsAuto == other.IsAutoArray(), "lost auto info");
+  memcpy(temp.Elements(), smallerElements, smallerLength * elemSize);
+  memcpy(smallerElements, largerElements, largerLength * elemSize);
+  memcpy(largerElements, temp.Elements(), smallerLength * elemSize);
+
+  // Swap the arrays' lengths.
+  NS_ABORT_IF_FALSE((other.Length() == 0 || mHdr != EmptyHdr()) &&
+                    (Length() == 0 || other.mHdr != EmptyHdr()),
+                    "Don't set sEmptyHdr's length.");
+  size_type tempLength = Length();
+  mHdr->mLength = other.Length();
+  other.mHdr->mLength = tempLength;
 
   return PR_TRUE;
 }
 
 template<class Alloc>
 PRBool
 nsTArray_base<Alloc>::EnsureNotUsingAutoArrayBuffer(size_type elemSize) {
   if (UsesAutoArrayBuffer()) {
+
+    // If you call this on a 0-length array, we'll set that array's mHdr to
+    // sEmptyHdr, in flagrant violation of the nsAutoTArray invariants.  It's
+    // up to you to set it back!  (If you don't, the nsAutoTArray will forget
+    // that it has an auto buffer.)
+    if (Length() == 0) {
+      mHdr = EmptyHdr();
+      return PR_TRUE;
+    }
+
     size_type size = sizeof(Header) + Length() * elemSize;
 
     Header* header = static_cast<Header*>(Alloc::Malloc(size));
     if (!header)
       return PR_FALSE;
 
     memcpy(header, mHdr, size);
     header->mCapacity = Length();
--- a/xpcom/glue/nsTArray.h
+++ b/xpcom/glue/nsTArray.h
@@ -138,17 +138,16 @@ struct nsTArray_SafeElementAtHelper<E*, 
     return static_cast<Derived*> (this)->SafeElementAt(i, nsnull);
   }
 
   const elem_type SafeElementAt(index_type i) const {
     return static_cast<const Derived*> (this)->SafeElementAt(i, nsnull);
   }
 };
 
-
 //
 // 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 Alloc>
 class nsTArray_base
 {
@@ -226,22 +225,31 @@ protected:
   // @param index the place to insert the new elements. This must be no
   //              greater than the current length of the array.
   // @param count the number of slots to insert
   // @param elementSize the size of an array element.
   PRBool InsertSlotsAt(index_type index, size_type count,
                        size_type elementSize);
 
 protected:
-  // NOTE: This method isn't heavily optimized if either array is an
-  // nsAutoTArray.
   template<class Allocator>
   PRBool SwapArrayElements(nsTArray_base<Allocator>& other,
                            size_type elemSize);
 
+  // This is an RAII class used in SwapArrayElements.
+  class IsAutoArrayRestorer {
+    public:
+      IsAutoArrayRestorer(nsTArray_base<Alloc> &array);
+      ~IsAutoArrayRestorer();
+
+    private:
+      nsTArray_base<Alloc> &mArray;
+      PRBool mIsAuto;
+  };
+
   // Helper function for SwapArrayElements. Ensures that if the array
   // is an nsAutoTArray that it doesn't use the built-in buffer.
   PRBool EnsureNotUsingAutoArrayBuffer(size_type elemSize);
 
   // Returns true if this nsTArray is an nsAutoTArray with a built-in buffer.
   PRBool IsAutoArray() {
     return mHdr->mIsAutoArray;
   }
@@ -251,17 +259,22 @@ protected:
   struct AutoArray {
     Header *mHdr;
     PRUint64 aligned;
   };
 
   // Returns a Header for the built-in buffer of this nsAutoTArray.
   Header* GetAutoArrayBuffer() {
     NS_ASSERTION(IsAutoArray(), "Should be an auto array to call this");
+    return GetAutoArrayBufferUnsafe();
+  }
 
+  // Returns a Header for the built-in buffer of this nsAutoTArray, but doesn't
+  // assert that we are an nsAutoTArray.
+  Header* GetAutoArrayBufferUnsafe() {
     return reinterpret_cast<Header*>(&(reinterpret_cast<AutoArray*>(&mHdr))->aligned);
   }
 
   // Returns true if this is an nsAutoTArray and it currently uses the
   // built-in buffer to store its elements.
   PRBool UsesAutoArrayBuffer() {
     return mHdr->mIsAutoArray && mHdr == GetAutoArrayBuffer();
   }
@@ -932,18 +945,16 @@ public:
   // A variation on the RemoveElementSorted method defined above.
   template<class Item>
   PRBool RemoveElementSorted(const Item& item) {
     return RemoveElementSorted(item, nsDefaultComparator<elem_type, Item>());
   }
 
   // This method causes the elements contained in this array and the given
   // array to be swapped.
-  // NOTE: This method isn't heavily optimized if either array is an
-  // nsAutoTArray.
   template<class Allocator>
   PRBool SwapElements(nsTArray<E, Allocator>& other) {
     return this->SwapArrayElements(other, sizeof(elem_type));
   }
 
   //
   // Allocation
   //
--- a/xpcom/tests/Makefile.in
+++ b/xpcom/tests/Makefile.in
@@ -93,16 +93,17 @@ CPP_UNIT_TESTS = \
                  TestHashtables.cpp \
                  TestID.cpp \
                  TestObserverArray.cpp \
                  TestObserverService.cpp \
                  TestPipe.cpp \
                  TestRefPtr.cpp \
                  TestTextFormatter.cpp \
                  TestCheckedInt.cpp \
+                 TestTArray.cpp \
                  $(NULL)
 
 # XXX Make this tests work in libxul builds.
 #CPP_UNIT_TESTS += \
 #                  TestArray.cpp \
 #                  TestCRT.cpp \
 #                  TestDeque.cpp \
 #                  TestEncoding.cpp \
--- a/xpcom/tests/TestTArray.cpp
+++ b/xpcom/tests/TestTArray.cpp
@@ -36,17 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <stdlib.h>
 #include <stdio.h>
 #include "nsTArray.h"
 #include "nsMemory.h"
 #include "nsAutoPtr.h"
-#include "nsString.h"
+#include "nsStringAPI.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsXPCOM.h"
 #include "nsILocalFile.h"
 
 namespace TestTArray {
 
@@ -228,17 +228,17 @@ class Object {
     }
 
     PRBool operator==(const Object& other) const {
       return mStr == other.mStr && mNum == other.mNum;
     }
 
     PRBool operator<(const Object& other) const {
       // sort based on mStr only
-      return Compare(mStr, other.mStr) < 0;
+      return mStr.Compare(other.mStr) < 0;
     }
 
     const char *Str() const { return mStr.get(); }
     PRUint32 Num() const { return mNum; }
 
   private:
     nsCString mStr;
     PRUint32  mNum;
@@ -298,17 +298,19 @@ static PRBool operator==(const nsCString
   return a.Equals(b);
 }
 
 static PRBool test_string_array() {
   nsTArray<nsCString> strArray;
   const char kdata[] = "hello world";
   PRUint32 i;
   for (i = 0; i < NS_ARRAY_LENGTH(kdata); ++i) {
-    if (!strArray.AppendElement(nsCString(kdata[i])))
+    nsCString str;
+    str.Assign(kdata[i]);
+    if (!strArray.AppendElement(str))
       return PR_FALSE;
   }
   for (i = 0; i < NS_ARRAY_LENGTH(kdata); ++i) {
     if (strArray[i].CharAt(0) != kdata[i])
       return PR_FALSE;
   }
 
   const char kextra[] = "foo bar";
@@ -576,16 +578,296 @@ static PRBool test_heap() {
   for (index = 0; index < NS_ARRAY_LENGTH(data); index++)
     if (ary[index] != expected_data[index])
       return PR_FALSE;
   return PR_TRUE;
 }
 
 //----
 
+// An array |arr| is using its auto buffer if |&arr < arr.Elements()| and
+// |arr.Elements() - &arr| is small.
+
+#define IS_USING_AUTO(arr) \
+  ((uintptr_t) &(arr) < (uintptr_t) arr.Elements() && \
+   ((PRPtrdiff)arr.Elements() - (PRPtrdiff)&arr) <= 16)
+
+#define CHECK_IS_USING_AUTO(arr) \
+  do {                                                    \
+    if (!(IS_USING_AUTO(arr))) {                          \
+      printf("%s:%d CHECK_IS_USING_AUTO(%s) failed.\n",   \
+             __FILE__, __LINE__, #arr);                   \
+      return PR_FALSE;                                    \
+    }                                                     \
+  } while(0)
+
+#define CHECK_NOT_USING_AUTO(arr) \
+  do {                                                    \
+    if (IS_USING_AUTO(arr)) {                             \
+      printf("%s:%d CHECK_NOT_USING_AUTO(%s) failed.\n",  \
+             __FILE__, __LINE__, #arr);                   \
+      return PR_FALSE;                                    \
+    }                                                     \
+  } while(0)
+
+#define CHECK_USES_SHARED_EMPTY_HDR(arr) \
+  do {                                                    \
+    nsTArray<int> _empty;                                 \
+    if (_empty.Elements() != arr.Elements()) {            \
+      printf("%s:%d CHECK_USES_EMPTY_HDR(%s) failed.\n",  \
+             __FILE__, __LINE__, #arr);                   \
+      return PR_FALSE;                                    \
+    }                                                     \
+  } while(0)
+
+#define CHECK_EQ_INT(actual, expected) \
+  do {                                                                       \
+    if ((actual) != (expected)) {                                            \
+      printf("%s:%d CHECK_EQ_INT(%s=%u, %s=%u) failed.\n",                   \
+             __FILE__, __LINE__, #actual, (actual), #expected, (expected));  \
+      return PR_FALSE;                                                       \
+    }                                                                        \
+  } while(0)
+
+#define CHECK_ARRAY(arr, data) \
+  do {                                                          \
+    CHECK_EQ_INT((arr).Length(), NS_ARRAY_LENGTH(data));        \
+    for (PRUint32 _i = 0; _i < NS_ARRAY_LENGTH(data); _i++) {   \
+      CHECK_EQ_INT((arr)[_i], (data)[_i]);                      \
+    }                                                           \
+  } while(0)
+
+static PRBool test_swap() {
+  // Test nsTArray::SwapElements.  Unfortunately there are many cases.
+  int data1[] = {8, 6, 7, 5};
+  int data2[] = {3, 0, 9};
+
+  // Swap two auto arrays.
+  {
+    nsAutoTArray<int, 8> a;
+    nsAutoTArray<int, 6> b;
+
+    a.AppendElements(data1, NS_ARRAY_LENGTH(data1));
+    b.AppendElements(data2, NS_ARRAY_LENGTH(data2));
+    CHECK_IS_USING_AUTO(a);
+    CHECK_IS_USING_AUTO(b);
+
+    a.SwapElements(b);
+
+    CHECK_IS_USING_AUTO(a);
+    CHECK_IS_USING_AUTO(b);
+    CHECK_ARRAY(a, data2);
+    CHECK_ARRAY(b, data1);
+  }
+
+  // Swap two auto arrays -- one whose data lives on the heap, the other whose
+  // data lives on the stack -- which each fits into the other's auto storage.
+  {
+    nsAutoTArray<int, 3> a;
+    nsAutoTArray<int, 3> b;
+
+    a.AppendElements(data1, NS_ARRAY_LENGTH(data1));
+    a.RemoveElementAt(3);
+    b.AppendElements(data2, NS_ARRAY_LENGTH(data2));
+
+    // Here and elsewhere, we assert that if we start with an auto array
+    // capable of storing N elements, we store N+1 elements into the array, and
+    // then we remove one element, that array is still not using its auto
+    // buffer.
+    //
+    // This isn't at all required by the TArray API. It would be fine if, when
+    // we shrink back to N elements, the TArray frees its heap storage and goes
+    // back to using its stack storage.  But we assert here as a check that the
+    // test does what we expect.  If the TArray implementation changes, just
+    // change the failing assertions.
+    CHECK_NOT_USING_AUTO(a);
+
+    // This check had better not change, though.
+    CHECK_IS_USING_AUTO(b);
+
+    a.SwapElements(b);
+
+    CHECK_IS_USING_AUTO(b);
+    CHECK_ARRAY(a, data2);
+    int expectedB[] = {8, 6, 7};
+    CHECK_ARRAY(b, expectedB);
+  }
+
+  // Swap two auto arrays which are using heap storage such that one fits into
+  // the other's auto storage, but the other needs to stay on the heap.
+  {
+    nsAutoTArray<int, 3> a;
+    nsAutoTArray<int, 2> b;
+    a.AppendElements(data1, NS_ARRAY_LENGTH(data1));
+    a.RemoveElementAt(3);
+
+    b.AppendElements(data2, NS_ARRAY_LENGTH(data2));
+    b.RemoveElementAt(2);
+
+    CHECK_NOT_USING_AUTO(a);
+    CHECK_NOT_USING_AUTO(b);
+
+    a.SwapElements(b);
+
+    CHECK_NOT_USING_AUTO(b);
+
+    int expected1[] = {3, 0};
+    int expected2[] = {8, 6, 7};
+
+    CHECK_ARRAY(a, expected1);
+    CHECK_ARRAY(b, expected2);
+  }
+
+  // Swap two arrays, neither of which fits into the other's auto-storage.
+  {
+    nsAutoTArray<int, 1> a;
+    nsAutoTArray<int, 3> b;
+
+    a.AppendElements(data1, NS_ARRAY_LENGTH(data1));
+    b.AppendElements(data2, NS_ARRAY_LENGTH(data2));
+
+    a.SwapElements(b);
+
+    CHECK_ARRAY(a, data2);
+    CHECK_ARRAY(b, data1);
+  }
+
+  // Swap an empty nsTArray with a non-empty nsAutoTArray.
+  {
+    nsTArray<int> a;
+    nsAutoTArray<int, 3> b;
+
+    b.AppendElements(data2, NS_ARRAY_LENGTH(data2));
+    CHECK_IS_USING_AUTO(b);
+
+    a.SwapElements(b);
+
+    CHECK_ARRAY(a, data2);
+    CHECK_EQ_INT(b.Length(), 0);
+    CHECK_IS_USING_AUTO(b);
+  }
+
+  // Swap two big auto arrays.
+  {
+    const int size = 8192;
+    nsAutoTArray<int, size> a;
+    nsAutoTArray<int, size> b;
+
+    for (int i = 0; i < size; i++) {
+      a.AppendElement(i);
+      b.AppendElement(i + 1);
+    }
+
+    CHECK_IS_USING_AUTO(a);
+    CHECK_IS_USING_AUTO(b);
+
+    a.SwapElements(b);
+
+    CHECK_IS_USING_AUTO(a);
+    CHECK_IS_USING_AUTO(b);
+
+    CHECK_EQ_INT(a.Length(), size);
+    CHECK_EQ_INT(b.Length(), size);
+
+    for (int i = 0; i < size; i++) {
+      CHECK_EQ_INT(a[i], i + 1);
+      CHECK_EQ_INT(b[i], i);
+    }
+  }
+
+  // Swap two arrays and make sure that their capacities don't increase
+  // unnecessarily.
+  {
+    nsTArray<int> a;
+    nsTArray<int> b;
+    b.AppendElements(data2, NS_ARRAY_LENGTH(data2));
+
+    CHECK_EQ_INT(a.Capacity(), 0);
+    PRUint32 bCapacity = b.Capacity();
+
+    a.SwapElements(b);
+
+    // Make sure that we didn't increase the capacity of either array.
+    CHECK_ARRAY(a, data2);
+    CHECK_EQ_INT(b.Length(), 0);
+    CHECK_EQ_INT(b.Capacity(), 0);
+    CHECK_EQ_INT(a.Capacity(), bCapacity);
+  }
+
+  // Swap an auto array with a TArray, then clear the auto array and make sure
+  // it doesn't forget the fact that it has an auto buffer.
+  {
+    nsTArray<int> a;
+    nsAutoTArray<int, 3> b;
+
+    a.AppendElements(data1, NS_ARRAY_LENGTH(data1));
+
+    a.SwapElements(b);
+
+    CHECK_EQ_INT(a.Length(), 0);
+    CHECK_ARRAY(b, data1);
+
+    b.Clear();
+
+    CHECK_USES_SHARED_EMPTY_HDR(a);
+    CHECK_IS_USING_AUTO(b);
+  }
+
+  // Same thing as the previous test, but with more auto arrays.
+  {
+    nsAutoTArray<int, 16> a;
+    nsAutoTArray<int, 3> b;
+
+    a.AppendElements(data1, NS_ARRAY_LENGTH(data1));
+
+    a.SwapElements(b);
+
+    CHECK_EQ_INT(a.Length(), 0);
+    CHECK_ARRAY(b, data1);
+
+    b.Clear();
+
+    CHECK_IS_USING_AUTO(a);
+    CHECK_IS_USING_AUTO(b);
+  }
+
+  // Swap an empty nsTArray and an empty nsAutoTArray.
+  {
+    nsAutoTArray<int, 8> a;
+    nsTArray<int> b;
+
+    a.SwapElements(b);
+
+    CHECK_IS_USING_AUTO(a);
+    CHECK_NOT_USING_AUTO(b);
+    CHECK_EQ_INT(a.Length(), 0);
+    CHECK_EQ_INT(b.Length(), 0);
+  }
+
+  // Swap empty auto array with non-empty nsAutoTArray using malloc'ed storage.
+  // I promise, all these tests have a point.
+  {
+    nsAutoTArray<int, 2> a;
+    nsAutoTArray<int, 1> b;
+
+    a.AppendElements(data1, NS_ARRAY_LENGTH(data1));
+
+    a.SwapElements(b);
+
+    CHECK_IS_USING_AUTO(a);
+    CHECK_NOT_USING_AUTO(b);
+    CHECK_ARRAY(b, data1);
+    CHECK_EQ_INT(a.Length(), 0);
+  }
+
+  return PR_TRUE;
+}
+
+//----
+
 typedef PRBool (*TestFunc)();
 #define DECL_TEST(name) { #name, name }
 
 static const struct Test {
   const char* name;
   TestFunc    func;
 } tests[] = {
   DECL_TEST(test_int_array),
@@ -597,32 +879,37 @@ static const struct Test {
   DECL_TEST(test_comptr_array),
   DECL_TEST(test_refptr_array),
   DECL_TEST(test_ptrarray),
 #ifdef DEBUG
   DECL_TEST(test_autoarray),
 #endif
   DECL_TEST(test_indexof),
   DECL_TEST(test_heap),
+  DECL_TEST(test_swap),
   { nsnull, nsnull }
 };
 
 }
 
 using namespace TestTArray;
 
 int main(int argc, char **argv) {
   int count = 1;
   if (argc > 1)
     count = atoi(argv[1]);
 
   if (NS_FAILED(NS_InitXPCOM2(nsnull, nsnull, nsnull)))
     return -1;
 
+  bool success = true;
   while (count--) {
     for (const Test* t = tests; t->name != nsnull; ++t) {
-      printf("%25s : %s\n", t->name, t->func() ? "SUCCESS" : "FAILURE");
+      bool test_result = t->func();
+      printf("%25s : %s\n", t->name, test_result ? "SUCCESS" : "FAILURE");
+      if (!test_result)
+        success = false;
     }
   }
   
   NS_ShutdownXPCOM(nsnull);
-  return 0;
+  return success ? 0 : -1;
 }