From 6f3e6b12344bbad755368ab9abce3d39f7a48eb1 Mon Sep 17 00:00:00 2001 From: Kan-Ru Chen Date: Thu, 21 Jul 2016 17:04:12 +0800 Subject: [PATCH] Bug 1264642 - Part 2. Add BufferList::Extract and Pickle::ExtractBuffers. r=billm These methods allow us to move some buffers out of a pickle with minimum copying. It's useful when the IPC deserialized type uses BufferList to store data and we want to take the buffers from IPC directly. Borrowing is not suitable to use for IPC to hand out data because we often want to store the data somewhere for processing after IPC has released the underlying buffers. MozReview-Commit-ID: F1K2ZMkACqq --- ipc/chromium/src/base/pickle.cc | 15 +++++++ ipc/chromium/src/base/pickle.h | 1 + mfbt/BufferList.h | 79 +++++++++++++++++++++++++++++++++ mfbt/tests/TestBufferList.cpp | 22 +++++++++ 4 files changed, 117 insertions(+) diff --git a/ipc/chromium/src/base/pickle.cc b/ipc/chromium/src/base/pickle.cc index b70dc1f364b6..be248fa2f277 100644 --- a/ipc/chromium/src/base/pickle.cc +++ b/ipc/chromium/src/base/pickle.cc @@ -421,6 +421,21 @@ bool Pickle::FlattenBytes(PickleIterator* iter, const char** data, uint32_t leng return iter->iter_.AdvanceAcrossSegments(buffers_, AlignInt(length) - length); } +bool Pickle::ExtractBuffers(PickleIterator* iter, size_t length, BufferList* buffers) const +{ + if (AlignInt(length) < length) { + return false; + } + + bool success; + *buffers = const_cast(&buffers_)->Extract(iter->iter_, length, &success); + if (!success) { + return false; + } + + return iter->iter_.AdvanceAcrossSegments(buffers_, AlignInt(length) - length); +} + bool Pickle::ReadBytesInto(PickleIterator* iter, void* data, uint32_t length) const { if (AlignInt(length) < length) { return false; diff --git a/ipc/chromium/src/base/pickle.h b/ipc/chromium/src/base/pickle.h index dde3c9497516..a7c631886731 100644 --- a/ipc/chromium/src/base/pickle.h +++ b/ipc/chromium/src/base/pickle.h @@ -113,6 +113,7 @@ class Pickle { MOZ_MUST_USE bool ReadBytesInto(PickleIterator* iter, void* data, uint32_t length) const; MOZ_MUST_USE bool FlattenBytes(PickleIterator* iter, const char** data, uint32_t length, uint32_t alignment = sizeof(memberAlignmentType)); + MOZ_MUST_USE bool ExtractBuffers(PickleIterator* iter, size_t length, BufferList* buffers) const; // Safer version of ReadInt() checks for the result not being negative. // Use it for reading the object sizes. diff --git a/mfbt/BufferList.h b/mfbt/BufferList.h index 355941f2df69..927c3a00b9d7 100644 --- a/mfbt/BufferList.h +++ b/mfbt/BufferList.h @@ -10,6 +10,7 @@ #include #include "mozilla/AllocPolicy.h" #include "mozilla/Move.h" +#include "mozilla/ScopeExit.h" #include "mozilla/Types.h" #include "mozilla/TypeTraits.h" #include "mozilla/Vector.h" @@ -257,6 +258,16 @@ class BufferList : private AllocPolicy BufferList Borrow(IterImpl& aIter, size_t aSize, bool* aSuccess, BorrowingAllocPolicy aAP = BorrowingAllocPolicy()); + // Return a new BufferList that adopts the byte range starting at Iter so that + // range [aIter, aIter + aSize) is transplanted to the returned BufferList. + // Contents of the buffer before aIter + aSize is left undefined. + // Extract can fail, in which case *aSuccess will be false upon return. The + // moved buffers are erased from the original BufferList. In case of extract + // fails, the original BufferList is intact. All other iterators except aIter + // are invalidated. + // This method requires aIter and aSize to be 8-byte aligned. + BufferList Extract(IterImpl& aIter, size_t aSize, bool* aSuccess); + private: explicit BufferList(AllocPolicy aAP) : AllocPolicy(aAP), @@ -430,6 +441,74 @@ BufferList::Borrow(IterImpl& aIter, size_t aSize, bool* aSuccess, return result; } +template +BufferList +BufferList::Extract(IterImpl& aIter, size_t aSize, bool* aSuccess) +{ + MOZ_RELEASE_ASSERT(aSize); + MOZ_RELEASE_ASSERT(mOwning); + MOZ_ASSERT(aSize % kSegmentAlignment == 0); + MOZ_ASSERT(intptr_t(aIter.mData) % kSegmentAlignment == 0); + + IterImpl iter = aIter; + size_t size = aSize; + size_t toCopy = std::min(size, aIter.RemainingInSegment()); + MOZ_ASSERT(toCopy % kSegmentAlignment == 0); + + BufferList result(0, toCopy, mStandardCapacity); + BufferList error(0, 0, mStandardCapacity); + + // Copy the head + if (!result.WriteBytes(aIter.mData, toCopy)) { + *aSuccess = false; + return error; + } + iter.Advance(*this, toCopy); + size -= toCopy; + + // Move segments to result + auto resultGuard = MakeScopeExit([&] { + *aSuccess = false; + result.mSegments.erase(result.mSegments.begin()+1, result.mSegments.end()); + }); + + size_t movedSize = 0; + uintptr_t toRemoveStart = iter.mSegment; + uintptr_t toRemoveEnd = iter.mSegment; + while (!iter.Done() && + !iter.HasRoomFor(size)) { + if (!result.mSegments.append(Segment(mSegments[iter.mSegment].mData, + mSegments[iter.mSegment].mSize, + mSegments[iter.mSegment].mCapacity))) { + return error; + } + movedSize += iter.RemainingInSegment(); + size -= iter.RemainingInSegment(); + toRemoveEnd++; + iter.Advance(*this, iter.RemainingInSegment()); + } + + if (size) { + if (!iter.HasRoomFor(size) || + !result.WriteBytes(iter.Data(), size)) { + return error; + } + iter.Advance(*this, size); + } + + mSegments.erase(mSegments.begin() + toRemoveStart, mSegments.begin() + toRemoveEnd); + mSize -= movedSize; + aIter.mSegment = iter.mSegment - (toRemoveEnd - toRemoveStart); + aIter.mData = iter.mData; + aIter.mDataEnd = iter.mDataEnd; + MOZ_ASSERT(aIter.mDataEnd == mSegments[aIter.mSegment].End()); + result.mSize = aSize; + + resultGuard.release(); + *aSuccess = true; + return result; +} + } // namespace mozilla #endif /* mozilla_BufferList_h */ diff --git a/mfbt/tests/TestBufferList.cpp b/mfbt/tests/TestBufferList.cpp index e736ee13befd..b143e919cb31 100644 --- a/mfbt/tests/TestBufferList.cpp +++ b/mfbt/tests/TestBufferList.cpp @@ -239,5 +239,27 @@ int main(void) MOZ_RELEASE_ASSERT(iter2.AdvanceAcrossSegments(bl2, kBorrowSize - 5)); MOZ_RELEASE_ASSERT(iter1.Data() == iter2.Data()); + // Extracting. + + const size_t kExtractStart = 8; + const size_t kExtractSize = 24; + const size_t kExtractOverSize = 1000; + + iter = bl.Iter(); + iter.Advance(bl, kExtractStart); + bl2 = bl.Extract(iter, kExtractSize, &success); + MOZ_RELEASE_ASSERT(success); + MOZ_RELEASE_ASSERT(bl2.Size() == kExtractSize); + + BufferList bl3 = bl.Extract(iter, kExtractOverSize, &success); + MOZ_RELEASE_ASSERT(!success); + + MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl, kSmallWrite * 3 - kExtractSize - kExtractStart)); + MOZ_RELEASE_ASSERT(iter.Done()); + + iter = bl2.Iter(); + MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl2, kExtractSize)); + MOZ_RELEASE_ASSERT(iter.Done()); + return 0; }