Bug 1309552 - Specify buffer size when freeing data in AllocPolicy, r=waldo.
authorBrian Hackett <bhackett1024@gmail.com>
Fri, 20 Jul 2018 23:58:34 +0000
changeset 485383 5c8c2d8a6003d2729497a2cf93c9afc89d2bf4f4
parent 485382 ff3c10d0cc05d15ec0e3e2d7c4d95c6dd860f0d1
child 485384 35160c4985ffecac51ed46bc3faad49937a7673d
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswaldo
bugs1309552
milestone63.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 1309552 - Specify buffer size when freeing data in AllocPolicy, r=waldo.
js/public/AllocPolicy.h
js/public/HashTable.h
js/src/ds/LifoAlloc.h
js/src/ds/OrderedHashTable.h
js/src/jit/JitAllocPolicy.h
memory/mozalloc/mozalloc.h
memory/replace/dmd/DMD.cpp
mfbt/AllocPolicy.h
mfbt/BufferList.h
mfbt/SegmentedVector.h
mfbt/Vector.h
mfbt/tests/TestBufferList.cpp
mfbt/tests/TestSegmentedVector.cpp
mozglue/misc/Printf.h
--- a/js/public/AllocPolicy.h
+++ b/js/public/AllocPolicy.h
@@ -35,17 +35,17 @@ class SystemAllocPolicy
     template <typename T> T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
         return js_pod_realloc<T>(p, oldSize, newSize);
     }
     template <typename T> T* pod_malloc(size_t numElems) { return maybe_pod_malloc<T>(numElems); }
     template <typename T> T* pod_calloc(size_t numElems) { return maybe_pod_calloc<T>(numElems); }
     template <typename T> T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
         return maybe_pod_realloc<T>(p, oldSize, newSize);
     }
-    void free_(void* p) { js_free(p); }
+    template <typename T> void free_(T* p, size_t numElems = 0) { js_free(p); }
     void reportAllocOverflow() const {}
     bool checkSimulatedOOM() const {
         return !js::oom::ShouldFailWithOOM();
     }
 };
 
 MOZ_COLD JS_FRIEND_API(void) ReportOutOfMemory(JSContext* cx);
 
@@ -114,17 +114,18 @@ class TempAllocPolicy
     template <typename T>
     T* pod_realloc(T* prior, size_t oldSize, size_t newSize) {
         T* p2 = maybe_pod_realloc<T>(prior, oldSize, newSize);
         if (MOZ_UNLIKELY(!p2))
             p2 = onOutOfMemoryTyped<T>(AllocFunction::Realloc, newSize, prior);
         return p2;
     }
 
-    void free_(void* p) {
+    template <typename T>
+    void free_(T* p, size_t numElems = 0) {
         js_free(p);
     }
 
     JS_FRIEND_API(void) reportAllocOverflow() const;
 
     bool checkSimulatedOOM() const {
         if (js::oom::ShouldFailWithOOM()) {
             ReportOutOfMemory(cx_);
@@ -156,17 +157,17 @@ class ZoneAllocPolicy
     // These methods are defined in gc/Zone.h.
     template <typename T> inline T* maybe_pod_malloc(size_t numElems);
     template <typename T> inline T* maybe_pod_calloc(size_t numElems);
     template <typename T> inline T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize);
     template <typename T> inline T* pod_malloc(size_t numElems);
     template <typename T> inline T* pod_calloc(size_t numElems);
     template <typename T> inline T* pod_realloc(T* p, size_t oldSize, size_t newSize);
 
-    void free_(void* p) { js_free(p); }
+    template <typename T> void free_(T* p, size_t numElems = 0) { js_free(p); }
     void reportAllocOverflow() const {}
 
     MOZ_MUST_USE bool checkSimulatedOOM() const {
         return !js::oom::ShouldFailWithOOM();
     }
 };
 
 } /* namespace js */
--- a/js/public/HashTable.h
+++ b/js/public/HashTable.h
@@ -1314,17 +1314,17 @@ class HashTable : private AllocPolicy
         return table;
     }
 
     static void destroyTable(AllocPolicy& alloc, Entry* oldTable, uint32_t capacity)
     {
         Entry* end = oldTable + capacity;
         for (Entry* e = oldTable; e < end; ++e)
             e->~Entry();
-        alloc.free_(oldTable);
+        alloc.free_(oldTable, capacity);
     }
 
   public:
     explicit HashTable(AllocPolicy ap)
       : AllocPolicy(ap)
       , gen(0)
       , hashShift(sHashBits)
       , table(nullptr)
@@ -1582,17 +1582,17 @@ class HashTable : private AllocPolicy
                 findFreeEntry(hn).setLive(
                     hn, std::move(const_cast<typename Entry::NonConstT&>(src->get())));
             }
 
             src->~Entry();
         }
 
         // All entries have been destroyed, no need to destroyTable.
-        this->free_(oldTable);
+        this->free_(oldTable, oldCap);
         return Rehashed;
     }
 
     bool shouldCompressTable()
     {
         // Compress if a quarter or more of all entries are removed.
         return removedCount >= (capacity() >> 2);
     }
--- a/js/src/ds/LifoAlloc.h
+++ b/js/src/ds/LifoAlloc.h
@@ -1028,17 +1028,18 @@ class LifoAllocPolicy
     template <typename T>
     T* pod_calloc(size_t numElems) {
         return maybe_pod_calloc<T>(numElems);
     }
     template <typename T>
     T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
         return maybe_pod_realloc<T>(p, oldSize, newSize);
     }
-    void free_(void* p) {
+    template <typename T>
+    void free_(T* p, size_t numElems) {
     }
     void reportAllocOverflow() const {
     }
     MOZ_MUST_USE bool checkSimulatedOOM() const {
         return fb == Infallible || !js::oom::ShouldFailWithOOM();
     }
 };
 
--- a/js/src/ds/OrderedHashTable.h
+++ b/js/src/ds/OrderedHashTable.h
@@ -119,17 +119,17 @@ class OrderedHashTable
         if (!tableAlloc)
             return false;
         for (uint32_t i = 0; i < buckets; i++)
             tableAlloc[i] = nullptr;
 
         uint32_t capacity = uint32_t(buckets * fillFactor());
         Data* dataAlloc = alloc.template pod_malloc<Data>(capacity);
         if (!dataAlloc) {
-            alloc.free_(tableAlloc);
+            alloc.free_(tableAlloc, buckets);
             return false;
         }
 
         // clear() requires that members are assigned only after all allocation
         // has succeeded, and that this->ranges is left untouched.
         hashTable = tableAlloc;
         data = dataAlloc;
         dataLength = 0;
@@ -137,18 +137,18 @@ class OrderedHashTable
         liveCount = 0;
         hashShift = HashNumberSizeBits - initialBucketsLog2();
         MOZ_ASSERT(hashBuckets() == buckets);
         return true;
     }
 
     ~OrderedHashTable() {
         forEachRange<Range::onTableDestroyed>();
-        alloc.free_(hashTable);
-        freeData(data, dataLength);
+        alloc.free_(hashTable, hashBuckets());
+        freeData(data, dataLength, dataCapacity);
     }
 
     /* Return the number of elements in the table. */
     uint32_t count() const { return liveCount; }
 
     /* True if any element matches l. */
     bool has(const Lookup& l) const {
         return lookup(l) != nullptr;
@@ -243,27 +243,29 @@ class OrderedHashTable
      * The effect on live Ranges is the same as removing all entries; in
      * particular, those Ranges are still live and will see any entries added
      * after a successful clear().
      */
     MOZ_MUST_USE bool clear() {
         if (dataLength != 0) {
             Data** oldHashTable = hashTable;
             Data* oldData = data;
+            uint32_t oldHashBuckets = hashBuckets();
             uint32_t oldDataLength = dataLength;
+            uint32_t oldDataCapacity = dataCapacity;
 
             hashTable = nullptr;
             if (!init()) {
                 // init() only mutates members on success; see comment above.
                 hashTable = oldHashTable;
                 return false;
             }
 
-            alloc.free_(oldHashTable);
-            freeData(oldData, oldDataLength);
+            alloc.free_(oldHashTable, oldHashBuckets);
+            freeData(oldData, oldDataLength, oldDataCapacity);
             forEachRange<&Range::onClear>();
         }
 
         MOZ_ASSERT(hashTable);
         MOZ_ASSERT(data);
         MOZ_ASSERT(dataLength == 0);
         MOZ_ASSERT(liveCount == 0);
         return true;
@@ -625,19 +627,19 @@ class OrderedHashTable
         return 1 << (HashNumberSizeBits - hashShift);
     }
 
     static void destroyData(Data* data, uint32_t length) {
         for (Data* p = data + length; p != data; )
             (--p)->~Data();
     }
 
-    void freeData(Data* data, uint32_t length) {
+    void freeData(Data* data, uint32_t length, uint32_t capacity) {
         destroyData(data, length);
-        alloc.free_(data);
+        alloc.free_(data, capacity);
     }
 
     Data* lookup(const Lookup& l, HashNumber h) {
         for (Data* e = hashTable[h >> hashShift]; e; e = e->chain) {
             if (Ops::match(Ops::getKey(e->element), l))
                 return e;
         }
         return nullptr;
@@ -699,34 +701,34 @@ class OrderedHashTable
         if (!newHashTable)
             return false;
         for (uint32_t i = 0; i < newHashBuckets; i++)
             newHashTable[i] = nullptr;
 
         uint32_t newCapacity = uint32_t(newHashBuckets * fillFactor());
         Data* newData = alloc.template pod_malloc<Data>(newCapacity);
         if (!newData) {
-            alloc.free_(newHashTable);
+            alloc.free_(newHashTable, newHashBuckets);
             return false;
         }
 
         Data* wp = newData;
         Data* end = data + dataLength;
         for (Data* p = data; p != end; p++) {
             if (!Ops::isEmpty(Ops::getKey(p->element))) {
                 HashNumber h = prepareHash(Ops::getKey(p->element)) >> newHashShift;
                 new (wp) Data(std::move(p->element), newHashTable[h]);
                 newHashTable[h] = wp;
                 wp++;
             }
         }
         MOZ_ASSERT(wp == newData + liveCount);
 
-        alloc.free_(hashTable);
-        freeData(data, dataLength);
+        alloc.free_(hashTable, hashBuckets());
+        freeData(data, dataLength, dataCapacity);
 
         hashTable = newHashTable;
         data = newData;
         dataLength = liveCount;
         dataCapacity = newCapacity;
         hashShift = newHashShift;
         MOZ_ASSERT(hashBuckets() == newHashBuckets);
 
--- a/js/src/jit/JitAllocPolicy.h
+++ b/js/src/jit/JitAllocPolicy.h
@@ -119,17 +119,18 @@ class JitAllocPolicy
     template <typename T>
     T* pod_calloc(size_t numElems) {
         return maybe_pod_calloc<T>(numElems);
     }
     template <typename T>
     T* pod_realloc(T* ptr, size_t oldSize, size_t newSize) {
         return maybe_pod_realloc<T>(ptr, oldSize, newSize);
     }
-    void free_(void* p) {
+    template <typename T>
+    void free_(T* p, size_t numElems = 0) {
     }
     void reportAllocOverflow() const {
     }
     MOZ_MUST_USE bool checkSimulatedOOM() const {
         return !js::oom::ShouldFailWithOOM();
     }
 };
 
--- a/memory/mozalloc/mozalloc.h
+++ b/memory/mozalloc/mozalloc.h
@@ -239,17 +239,18 @@ public:
     T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
     {
         if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
             reportAllocOverflow();
         }
         return static_cast<T*>(moz_xrealloc(aPtr, aNewSize * sizeof(T)));
     }
 
-    void free_(void* aPtr)
+    template <typename T>
+    void free_(T* aPtr, size_t aNumElems = 0)
     {
         free_impl(aPtr);
     }
 
     void reportAllocOverflow() const
     {
         mozalloc_abort("alloc overflow");
     }
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -181,17 +181,18 @@ public:
 
   static void* memalign_(size_t aAlignment, size_t aSize)
   {
     void* p = gMallocTable.memalign(aAlignment, aSize);
     ExitOnFailure(p);
     return p;
   }
 
-  static void free_(void* aPtr) { gMallocTable.free(aPtr); }
+  template <typename T>
+  static void free_(T* aPtr, size_t aSize = 0) { gMallocTable.free(aPtr); }
 
   static char* strdup_(const char* aStr)
   {
     char* s = (char*) InfallibleAllocPolicy::malloc_(strlen(aStr) + 1);
     strcpy(s, aStr);
     return s;
   }
 
--- a/mfbt/AllocPolicy.h
+++ b/mfbt/AllocPolicy.h
@@ -37,17 +37,21 @@ namespace mozilla {
  *      size is passed in, in addition to the new allocation size requested.
  *  - template <typename T> T* pod_malloc(size_t)
  *      Responsible for OOM reporting when null is returned.
  *  - template <typename T> T* pod_calloc(size_t)
  *      Responsible for OOM reporting when null is returned.
  *  - template <typename T> T* pod_realloc(T*, size_t, size_t)
  *      Responsible for OOM reporting when null is returned.  The old allocation
  *      size is passed in, in addition to the new allocation size requested.
- *  - void free_(void*)
+ *  - template <typename T> void free_(T*, size_t)
+ *      The capacity passed in must match the old allocation size.
+ *  - template <typename T> void free_(T*)
+ *      Frees a buffer without knowing its allocated size. This might not be
+ *      implemented by allocation policies that need the allocation size.
  *  - void reportAllocOverflow() const
  *      Called on allocation overflow (that is, an allocation implicitly tried
  *      to allocate more than the available memory space -- think allocating an
  *      array of large-size objects, where N * size overflows) before null is
  *      returned.
  *  - bool checkSimulatedOOM() const
  *      Some clients generally allocate memory yet in some circumstances won't
  *      need to do so. For example, appending to a vector with a small amount of
@@ -109,17 +113,18 @@ public:
   }
 
   template <typename T>
   T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
   {
     return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
   }
 
-  void free_(void* aPtr)
+  template <typename T>
+  void free_(T* aPtr, size_t aNumElems = 0)
   {
     free(aPtr);
   }
 
   void reportAllocOverflow() const
   {
   }
 
@@ -170,17 +175,18 @@ public:
   }
 
   template <typename T>
   T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
   {
     MOZ_CRASH("NeverAllocPolicy::pod_realloc");
   }
 
-  void free_(void* aPtr)
+  template <typename T>
+  void free_(T* aPtr, size_t aNumElems = 0)
   {
     MOZ_CRASH("NeverAllocPolicy::free_");
   }
 
   void reportAllocOverflow() const
   {
   }
 
--- a/mfbt/BufferList.h
+++ b/mfbt/BufferList.h
@@ -146,17 +146,17 @@ class BufferList : private AllocPolicy
     }
     return size;
   }
 
   void Clear()
   {
     if (mOwning) {
       for (Segment& segment : mSegments) {
-        this->free_(segment.mData);
+        this->free_(segment.mData, segment.mCapacity);
       }
     }
     mSegments.clear();
 
     mSize = 0;
   }
 
   // Iterates over bytes in the segments. You can advance it by as many bytes as
@@ -362,17 +362,17 @@ class BufferList : private AllocPolicy
   // This takes ownership of the data
   void* WriteBytesZeroCopy(char *aData, size_t aSize, size_t aCapacity)
   {
     MOZ_ASSERT(aCapacity != 0);
     MOZ_ASSERT(aSize <= aCapacity);
     MOZ_ASSERT(mOwning);
 
     if (!mSegments.append(Segment(aData, aSize, aCapacity))) {
-      this->free_(aData);
+      this->free_(aData, aCapacity);
       return nullptr;
     }
     mSize += aSize;
     return aData;
   }
 
 private:
   explicit BufferList(AllocPolicy aAP)
@@ -389,17 +389,17 @@ private:
     MOZ_ASSERT(aCapacity != 0);
     MOZ_ASSERT(aSize <= aCapacity);
 
     char* data = this->template pod_malloc<char>(aCapacity);
     if (!data) {
       return nullptr;
     }
     if (!mSegments.append(Segment(data, aSize, aCapacity))) {
-      this->free_(data);
+      this->free_(data, aCapacity);
       return nullptr;
     }
     mSize += aSize;
     return data;
   }
 
   bool mOwning;
   Vector<Segment, 1, AllocPolicy> mSegments;
--- a/mfbt/SegmentedVector.h
+++ b/mfbt/SegmentedVector.h
@@ -187,17 +187,17 @@ public:
     MOZ_RELEASE_ASSERT(ok);
   }
 
   void Clear()
   {
     Segment* segment;
     while ((segment = mSegments.popFirst())) {
       segment->~Segment();
-      this->free_(segment);
+      this->free_(segment, 1);
     }
   }
 
   T& GetLast()
   {
     MOZ_ASSERT(!IsEmpty());
     Segment* last = mSegments.getLast();
     return (*last)[last->Length() - 1];
@@ -213,17 +213,17 @@ public:
   void PopLast()
   {
     MOZ_ASSERT(!IsEmpty());
     Segment* last = mSegments.getLast();
     last->PopLast();
     if (!last->Length()) {
       mSegments.popLast();
       last->~Segment();
-      this->free_(last);
+      this->free_(last, 1);
     }
   }
 
   // Equivalent to calling |PopLast| |aNumElements| times, but potentially
   // more efficient.
   void PopLastN(uint32_t aNumElements)
   {
     MOZ_ASSERT(aNumElements <= Length());
@@ -246,17 +246,17 @@ public:
       uint32_t segmentLen = last->Length();
       if (segmentLen > aNumElements) {
         break;
       }
 
       // Destroying the segment destroys all elements contained therein.
       mSegments.popLast();
       last->~Segment();
-      this->free_(last);
+      this->free_(last, 1);
 
       MOZ_ASSERT(aNumElements >= segmentLen);
       aNumElements -= segmentLen;
       if (aNumElements == 0) {
         return;
       }
     } while (true);
 
--- a/mfbt/Vector.h
+++ b/mfbt/Vector.h
@@ -139,17 +139,17 @@ struct VectorImpl
       return false;
     }
     T* dst = newbuf;
     T* src = aV.beginNoCheck();
     for (; src < aV.endNoCheck(); ++dst, ++src) {
       new_(dst, std::move(*src));
     }
     VectorImpl::destroy(aV.beginNoCheck(), aV.endNoCheck());
-    aV.free_(aV.mBegin);
+    aV.free_(aV.mBegin, aV.mTail.mCapacity);
     aV.mBegin = newbuf;
     /* aV.mLength is unchanged. */
     aV.mTail.mCapacity = aNewCap;
     return true;
   }
 };
 
 /*
@@ -239,17 +239,17 @@ struct VectorImpl<T, N, AP, true>
 
   static inline void
   podResizeToFit(Vector<T, N, AP>& aV)
   {
     if (aV.usingInlineStorage() || aV.mLength == aV.mTail.mCapacity) {
       return;
     }
     if (!aV.mLength) {
-      aV.free_(aV.mBegin);
+      aV.free_(aV.mBegin, aV.mTail.mCapacity);
       aV.mBegin = aV.inlineStorage();
       aV.mTail.mCapacity = aV.kInlineCapacity;
 #ifdef DEBUG
       aV.mTail.mReserved = 0;
 #endif
       return;
     }
     T* newbuf =
@@ -922,17 +922,17 @@ Vector<T, N, AP>::operator=(Vector&& aRh
 
 template<typename T, size_t N, class AP>
 MOZ_ALWAYS_INLINE
 Vector<T, N, AP>::~Vector()
 {
   MOZ_REENTRANCY_GUARD_ET_AL;
   Impl::destroy(beginNoCheck(), endNoCheck());
   if (!usingInlineStorage()) {
-    this->free_(beginNoCheck());
+    this->free_(beginNoCheck(), mTail.mCapacity);
   }
 }
 
 template<typename T, size_t N, class AP>
 MOZ_ALWAYS_INLINE void
 Vector<T, N, AP>::reverse() {
   MOZ_REENTRANCY_GUARD_ET_AL;
   T* elems = mBegin;
@@ -1233,17 +1233,17 @@ template<typename T, size_t N, class AP>
 inline void
 Vector<T, N, AP>::clearAndFree()
 {
   clear();
 
   if (usingInlineStorage()) {
     return;
   }
-  this->free_(beginNoCheck());
+  this->free_(beginNoCheck(), mTail.mCapacity);
   mBegin = inlineStorage();
   mTail.mCapacity = kInlineCapacity;
 #ifdef DEBUG
   mTail.mReserved = 0;
 #endif
 }
 
 template<typename T, size_t N, class AP>
@@ -1506,32 +1506,32 @@ template<typename T, size_t N, class AP>
 inline void
 Vector<T, N, AP>::replaceRawBuffer(T* aP, size_t aLength, size_t aCapacity)
 {
   MOZ_REENTRANCY_GUARD_ET_AL;
 
   /* Destroy what we have. */
   Impl::destroy(beginNoCheck(), endNoCheck());
   if (!usingInlineStorage()) {
-    this->free_(beginNoCheck());
+    this->free_(beginNoCheck(), mTail.mCapacity);
   }
 
   /* Take in the new buffer. */
   if (aCapacity <= kInlineCapacity) {
     /*
      * We convert to inline storage if possible, even though aP might
      * otherwise be acceptable.  Maybe this behaviour should be
      * specifiable with an argument to this function.
      */
     mBegin = inlineStorage();
     mLength = aLength;
     mTail.mCapacity = kInlineCapacity;
     Impl::moveConstruct(mBegin, aP, aP + aLength);
     Impl::destroy(aP, aP + aLength);
-    this->free_(aP);
+    this->free_(aP, aCapacity);
   } else {
     mBegin = aP;
     mLength = aLength;
     mTail.mCapacity = aCapacity;
   }
 #ifdef DEBUG
   mTail.mReserved = aCapacity;
 #endif
--- a/mfbt/tests/TestBufferList.cpp
+++ b/mfbt/tests/TestBufferList.cpp
@@ -21,17 +21,18 @@ public:
     }
     T* rv = static_cast<T*>(malloc(aNumElems * sizeof(T)));
     if (!rv) {
       MOZ_CRASH("TestBufferList.cpp: out of memory");
     }
     return rv;
   }
 
-  void free_(void* aPtr) { free(aPtr); }
+  template <typename T>
+  void free_(T* aPtr, size_t aNumElems = 0) { free(aPtr); }
 
   void reportAllocOverflow() const {}
 
   bool checkSimulatedOOM() const { return true; }
 };
 
 typedef mozilla::BufferList<InfallibleAllocPolicy> BufferList;
 
--- a/mfbt/tests/TestSegmentedVector.cpp
+++ b/mfbt/tests/TestSegmentedVector.cpp
@@ -26,17 +26,18 @@ public:
     }
     T* rv = static_cast<T*>(malloc(aNumElems * sizeof(T)));
     if (!rv) {
       MOZ_CRASH("TestSegmentedVector.cpp: out of memory");
     }
     return rv;
   }
 
-  void free_(void* aPtr) { free(aPtr); }
+  template <typename T>
+  void free_(T* aPtr, size_t aNumElems = 0) { free(aPtr); }
 };
 
 // We want to test Append(), which is fallible and marked with
 // MOZ_MUST_USE. But we're using an infallible alloc policy, and so
 // don't really need to check the result. Casting to |void| works with clang
 // but not GCC, so we instead use this dummy variable which works with both
 // compilers.
 static int gDummy;
--- a/mozglue/misc/Printf.h
+++ b/mozglue/misc/Printf.h
@@ -160,21 +160,23 @@ class MOZ_STACK_CLASS SprintfState final
         ptrdiff_t off;
         char* newbase;
         size_t newlen;
 
         off = mCur - mBase;
         if (off + len >= mMaxlen) {
             /* Grow the buffer */
             newlen = mMaxlen + ((len > 32) ? len : 32);
-            newbase = static_cast<char*>(this->maybe_pod_realloc(mBase, mMaxlen, newlen));
+            newbase = this->template maybe_pod_malloc<char>(newlen);
             if (!newbase) {
                 /* Ran out of memory */
                 return false;
             }
+            memcpy(newbase, mBase, mMaxlen);
+            this->free_(mBase);
             mBase = newbase;
             mMaxlen = newlen;
             mCur = mBase + off;
         }
 
         /* Copy data */
         memcpy(mCur, sp, len);
         mCur += len;