make io::Cursor::push() safe to call with an empty ByteRange
[folly.git] / folly / io / Cursor.h
index ce73705125957ec1ddb4d33a502b98d337326f72..23186764da40f631ae59b7f60b5bb9ca1a14cfd7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#ifndef FOLLY_CURSOR_H
-#define FOLLY_CURSOR_H
+#pragma once
 
 #include <assert.h>
+#include <cstdarg>
 #include <stdexcept>
 #include <string.h>
 #include <type_traits>
 #include <memory>
 
-#include "folly/Bits.h"
-#include "folly/io/IOBuf.h"
-#include "folly/io/IOBufQueue.h"
-#include "folly/Likely.h"
+#include <folly/Bits.h>
+#include <folly/Likely.h>
+#include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
+#include <folly/io/IOBuf.h>
+#include <folly/io/IOBufQueue.h>
+#include <folly/portability/BitsFunctexcept.h>
 
 /**
  * Cursor class for fast iteration over IOBuf chains.
  * access to the buffer (you need to call unshare() yourself if necessary).
  **/
 namespace folly { namespace io {
+
 namespace detail {
 
-template <class Derived, typename BufType>
+template <class Derived, class BufType>
 class CursorBase {
+  // Make all the templated classes friends for copy constructor.
+  template <class D, typename B> friend class CursorBase;
  public:
+  explicit CursorBase(BufType* buf) : crtBuf_(buf), buffer_(buf) { }
+
+  /**
+   * Copy constructor.
+   *
+   * This also allows constructing a CursorBase from other derived types.
+   * For instance, this allows constructing a Cursor from an RWPrivateCursor.
+   */
+  template <class OtherDerived, class OtherBuf>
+  explicit CursorBase(const CursorBase<OtherDerived, OtherBuf>& cursor)
+    : crtBuf_(cursor.crtBuf_),
+      offset_(cursor.offset_),
+      buffer_(cursor.buffer_) { }
+
+  /**
+   * Reset cursor to point to a new buffer.
+   */
+  void reset(BufType* buf) {
+    crtBuf_ = buf;
+    buffer_ = buf;
+    offset_ = 0;
+  }
+
   const uint8_t* data() const {
     return crtBuf_->data() + offset_;
   }
 
-  // Space available in the current IOBuf.  May be 0; use peek() instead which
-  // will always point to a non-empty chunk of data or at the end of the
-  // chain.
+  /**
+   * Return the remaining space available in the current IOBuf.
+   *
+   * May return 0 if the cursor is at the end of an IOBuf.  Use peekBytes()
+   * instead if you want to avoid this.  peekBytes() will advance to the next
+   * non-empty IOBuf (up to the end of the chain) if the cursor is currently
+   * pointing at the end of a buffer.
+   */
   size_t length() const {
     return crtBuf_->length() - offset_;
   }
 
+  /**
+   * Return the space available until the end of the entire IOBuf chain.
+   */
+  size_t totalLength() const {
+    if (crtBuf_ == buffer_) {
+      return crtBuf_->computeChainDataLength() - offset_;
+    }
+    CursorBase end(buffer_->prev());
+    end.offset_ = end.buffer_->length();
+    return end - *this;
+  }
+
+  /**
+   * Return true if the cursor could advance the specified number of bytes
+   * from its current position.
+   * This is useful for applications that want to do checked reads instead of
+   * catching exceptions and is more efficient than using totalLength as it
+   * walks the minimal set of buffers in the chain to determine the result.
+   */
+  bool canAdvance(size_t amount) const {
+    const IOBuf* nextBuf = crtBuf_;
+    size_t available = length();
+    do {
+      if (available >= amount) {
+        return true;
+      }
+      amount -= available;
+      nextBuf = nextBuf->next();
+      available = nextBuf->length();
+    } while (nextBuf != buffer_);
+    return false;
+  }
+
+  /*
+   * Return true if the cursor is at the end of the entire IOBuf chain.
+   */
+  bool isAtEnd() const {
+    // Check for the simple cases first.
+    if (offset_ != crtBuf_->length()) {
+      return false;
+    }
+    if (crtBuf_ == buffer_->prev()) {
+      return true;
+    }
+    // We are at the end of a buffer, but it isn't the last buffer.
+    // We might still be at the end if the remaining buffers in the chain are
+    // empty.
+    const IOBuf* buf = crtBuf_->next();;
+    while (buf != buffer_) {
+      if (buf->length() > 0) {
+        return false;
+      }
+      buf = buf->next();
+    }
+    return true;
+  }
+
+  /**
+   * Advances the cursor to the end of the entire IOBuf chain.
+   */
+  void advanceToEnd() {
+    offset_ = buffer_->prev()->length();
+    if (crtBuf_ != buffer_->prev()) {
+      crtBuf_ = buffer_->prev();
+      static_cast<Derived*>(this)->advanceDone();
+    }
+  }
+
   Derived& operator+=(size_t offset) {
     Derived* p = static_cast<Derived*>(this);
     p->skip(offset);
     return *p;
   }
+  Derived operator+(size_t offset) const {
+    Derived other(*this);
+    other.skip(offset);
+    return other;
+  }
+
+  Derived& operator-=(size_t offset) {
+    Derived* p = static_cast<Derived*>(this);
+    p->retreat(offset);
+    return *p;
+  }
+  Derived operator-(size_t offset) const {
+    Derived other(*this);
+    other.retreat(offset);
+    return other;
+  }
+
+  /**
+   * Compare cursors for equality/inequality.
+   *
+   * Two cursors are equal if they are pointing to the same location in the
+   * same IOBuf chain.
+   */
+  bool operator==(const Derived& other) const {
+    return (offset_ == other.offset_) && (crtBuf_ == other.crtBuf_);
+  }
+  bool operator!=(const Derived& other) const {
+    return !operator==(other);
+  }
+
+  template <class T>
+  typename std::enable_if<std::is_arithmetic<T>::value, bool>::type tryRead(
+      T& val) {
+    if (LIKELY(length() >= sizeof(T))) {
+      val = loadUnaligned<T>(data());
+      offset_ += sizeof(T);
+      advanceBufferIfEmpty();
+      return true;
+    }
+    return pullAtMostSlow(&val, sizeof(T)) == sizeof(T);
+  }
+
+  template <class T>
+  bool tryReadBE(T& val) {
+    const bool result = tryRead(val);
+    val = Endian::big(val);
+    return result;
+  }
+
+  template <class T>
+  bool tryReadLE(T& val) {
+    const bool result = tryRead(val);
+    val = Endian::little(val);
+    return result;
+  }
 
   template <class T>
-  typename std::enable_if<std::is_integral<T>::value, T>::type
-  read() {
-    T val;
-    pull(&val, sizeof(T));
+  T read() {
+    T val{};
+    if (!tryRead(val)) {
+      std::__throw_out_of_range("underflow");
+    }
     return val;
   }
 
@@ -94,23 +253,15 @@ class CursorBase {
    */
   std::string readFixedString(size_t len) {
     std::string str;
-
     str.reserve(len);
-    for (;;) {
-      // Fast path: it all fits in one buffer.
-      size_t available = length();
-      if (LIKELY(available >= len)) {
-        str.append(reinterpret_cast<const char*>(data()), len);
-        offset_ += len;
-        return str;
-      }
-
-      str.append(reinterpret_cast<const char*>(data()), available);
-      if (UNLIKELY(!tryAdvanceBuffer())) {
-        throw std::out_of_range("string underflow");
-      }
-      len -= available;
+    if (LIKELY(length() >= len)) {
+      str.append(reinterpret_cast<const char*>(data()), len);
+      offset_ += len;
+      advanceBufferIfEmpty();
+    } else {
+      readFixedStringSlow(&str, len);
     }
+    return str;
   }
 
   /**
@@ -122,143 +273,160 @@ class CursorBase {
    * vs. using pull().
    */
   std::string readTerminatedString(
-    char termChar = '\0',
-    size_t maxLength = std::numeric_limits<size_t>::max()) {
-    std::string str;
+      char termChar = '\0',
+      size_t maxLength = std::numeric_limits<size_t>::max());
 
-    for (;;) {
-      const uint8_t* buf = data();
-      size_t buflen = length();
-
-      size_t i = 0;
-      while (i < buflen && buf[i] != termChar) {
-        ++i;
+  /*
+   * Read all bytes until the specified predicate returns true.
+   *
+   * The predicate will be called on each byte in turn, until it returns false
+   * or until the end of the IOBuf chain is reached.
+   *
+   * Returns the result as a string.
+   */
+  template <typename Predicate>
+  std::string readWhile(const Predicate& predicate);
 
-        // Do this check after incrementing 'i', as even though we start at the
-        // 0 byte, it still represents a single character
-        if (str.length() + i >= maxLength) {
-          throw std::length_error("string overflow");
-        }
-      }
+  /*
+   * Read all bytes until the specified predicate returns true.
+   *
+   * This is a more generic version of readWhile() takes an arbitrary Output
+   * object, and calls Output::append() with each chunk of matching data.
+   */
+  template <typename Predicate, typename Output>
+  void readWhile(const Predicate& predicate, Output& out);
 
-      str.append(reinterpret_cast<const char*>(buf), i);
-      if (i < buflen) {
-        skip(i + 1);
-        return str;
-      }
+  /*
+   * Skip all bytes until the specified predicate returns true.
+   *
+   * The predicate will be called on each byte in turn, until it returns false
+   * or until the end of the IOBuf chain is reached.
+   */
+  template <typename Predicate>
+  void skipWhile(const Predicate& predicate);
 
-      skip(i);
+  size_t skipAtMost(size_t len) {
+    if (LIKELY(length() >= len)) {
+      offset_ += len;
+      advanceBufferIfEmpty();
+      return len;
+    }
+    return skipAtMostSlow(len);
+  }
 
-      if (UNLIKELY(!tryAdvanceBuffer())) {
-        throw std::out_of_range("string underflow");
-      }
+  void skip(size_t len) {
+    if (LIKELY(length() >= len)) {
+      offset_ += len;
+      advanceBufferIfEmpty();
+    } else {
+      skipSlow(len);
     }
   }
 
-  explicit CursorBase(BufType* buf)
-    : crtBuf_(buf)
-    , offset_(0)
-    , buffer_(buf) {}
+  size_t retreatAtMost(size_t len) {
+    if (len <= offset_) {
+      offset_ -= len;
+      return len;
+    }
+    return retreatAtMostSlow(len);
+  }
 
-  // Make all the templated classes friends for copy constructor.
-  template <class D, typename B> friend class CursorBase;
+  void retreat(size_t len) {
+    if (len <= offset_) {
+      offset_ -= len;
+    } else {
+      retreatSlow(len);
+    }
+  }
 
-  template <class T>
-  explicit CursorBase(const T& cursor) {
-    crtBuf_ = cursor.crtBuf_;
-    offset_ = cursor.offset_;
-    buffer_ = cursor.buffer_;
+  size_t pullAtMost(void* buf, size_t len) {
+    // Fast path: it all fits in one buffer.
+    if (LIKELY(length() >= len)) {
+      memcpy(buf, data(), len);
+      offset_ += len;
+      advanceBufferIfEmpty();
+      return len;
+    }
+    return pullAtMostSlow(buf, len);
   }
 
-  // reset cursor to point to a new buffer.
-  void reset(BufType* buf) {
-    crtBuf_ = buf;
-    buffer_ = buf;
-    offset_ = 0;
+  void pull(void* buf, size_t len) {
+    if (LIKELY(length() >= len)) {
+      memcpy(buf, data(), len);
+      offset_ += len;
+      advanceBufferIfEmpty();
+    } else {
+      pullSlow(buf, len);
+    }
   }
 
   /**
    * Return the available data in the current buffer.
    * If you want to gather more data from the chain into a contiguous region
-   * (for hopefully zero-copy access), use gather() before peek().
+   * (for hopefully zero-copy access), use gather() before peekBytes().
    */
-  std::pair<const uint8_t*, size_t> peek() {
+  ByteRange peekBytes() {
     // Ensure that we're pointing to valid data
     size_t available = length();
     while (UNLIKELY(available == 0 && tryAdvanceBuffer())) {
       available = length();
     }
-
-    return std::make_pair(data(), available);
-  }
-
-  void pull(void* buf, size_t length) {
-    if (UNLIKELY(pullAtMost(buf, length) != length)) {
-      throw std::out_of_range("underflow");
-    }
+    return ByteRange{data(), available};
   }
 
-  void clone(std::unique_ptr<folly::IOBuf>& buf, size_t length) {
-    if (UNLIKELY(cloneAtMost(buf, length) != length)) {
-      throw std::out_of_range("underflow");
-    }
+  /**
+   * Alternate version of peekBytes() that returns a std::pair
+   * instead of a ByteRange.  (This method pre-dates ByteRange.)
+   *
+   * This function will eventually be deprecated.
+   */
+  std::pair<const uint8_t*, size_t> peek() {
+    auto bytes = peekBytes();
+    return std::make_pair(bytes.data(), bytes.size());
   }
 
-  void skip(size_t length) {
-    if (UNLIKELY(skipAtMost(length) != length)) {
-      throw std::out_of_range("underflow");
+  void clone(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
+    if (UNLIKELY(cloneAtMost(buf, len) != len)) {
+      std::__throw_out_of_range("underflow");
     }
   }
 
-  size_t pullAtMost(void* buf, size_t len) {
-    uint8_t* p = reinterpret_cast<uint8_t*>(buf);
-    size_t copied = 0;
-    for (;;) {
-      // Fast path: it all fits in one buffer.
-      size_t available = length();
-      if (LIKELY(available >= len)) {
-        memcpy(p, data(), len);
-        offset_ += len;
-        return copied + len;
-      }
-
-      memcpy(p, data(), available);
-      copied += available;
-      if (UNLIKELY(!tryAdvanceBuffer())) {
-        return copied;
-      }
-      p += available;
-      len -= available;
+  void clone(folly::IOBuf& buf, size_t len) {
+    if (UNLIKELY(cloneAtMost(buf, len) != len)) {
+      std::__throw_out_of_range("underflow");
     }
   }
 
-  size_t cloneAtMost(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
-    buf.reset(nullptr);
-
+  size_t cloneAtMost(folly::IOBuf& buf, size_t len) {
     std::unique_ptr<folly::IOBuf> tmp;
     size_t copied = 0;
-    for (;;) {
+    for (int loopCount = 0; true; ++loopCount) {
       // Fast path: it all fits in one buffer.
       size_t available = length();
       if (LIKELY(available >= len)) {
-        tmp = crtBuf_->cloneOne();
-        tmp->trimStart(offset_);
-        tmp->trimEnd(tmp->length() - len);
-        offset_ += len;
-        if (!buf) {
-          buf = std::move(tmp);
+        if (loopCount == 0) {
+          crtBuf_->cloneOneInto(buf);
+          buf.trimStart(offset_);
+          buf.trimEnd(buf.length() - len);
         } else {
-          buf->prependChain(std::move(tmp));
+          tmp = crtBuf_->cloneOne();
+          tmp->trimStart(offset_);
+          tmp->trimEnd(tmp->length() - len);
+          buf.prependChain(std::move(tmp));
         }
+
+        offset_ += len;
+        advanceBufferIfEmpty();
         return copied + len;
       }
 
-      tmp = crtBuf_->cloneOne();
-      tmp->trimStart(offset_);
-      if (!buf) {
-        buf = std::move(tmp);
+      if (loopCount == 0) {
+        crtBuf_->cloneOneInto(buf);
+        buf.trimStart(offset_);
       } else {
-        buf->prependChain(std::move(tmp));
+        tmp = crtBuf_->cloneOne();
+        tmp->trimStart(offset_);
+        buf.prependChain(std::move(tmp));
       }
 
       copied += available;
@@ -269,22 +437,11 @@ class CursorBase {
     }
   }
 
-  size_t skipAtMost(size_t len) {
-    size_t skipped = 0;
-    for (;;) {
-      // Fast path: it all fits in one buffer.
-      size_t available = length();
-      if (LIKELY(available >= len)) {
-        offset_ += len;
-        return skipped + len;
-      }
-
-      skipped += available;
-      if (UNLIKELY(!tryAdvanceBuffer())) {
-        return skipped;
-      }
-      len -= available;
+  size_t cloneAtMost(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
+    if (!buf) {
+      buf = std::make_unique<folly::IOBuf>();
     }
+    return cloneAtMost(*buf, len);
   }
 
   /**
@@ -304,13 +461,13 @@ class CursorBase {
       }
 
       if (otherBuf == other.buffer_) {
-        throw std::out_of_range("wrap-around");
+        std::__throw_out_of_range("wrap-around");
       }
 
       len += offset_;
     } else {
       if (offset_ < other.offset_) {
-        throw std::out_of_range("underflow");
+        std::__throw_out_of_range("underflow");
       }
 
       len += offset_ - other.offset_;
@@ -325,12 +482,12 @@ class CursorBase {
   size_t operator-(const BufType* buf) const {
     size_t len = 0;
 
-    BufType *curBuf = buf;
+    const BufType* curBuf = buf;
     while (curBuf != crtBuf_) {
       len += curBuf->length();
       curBuf = curBuf->next();
       if (curBuf == buf || curBuf == buffer_) {
-        throw std::out_of_range("wrap-around");
+        std::__throw_out_of_range("wrap-around");
       }
     }
 
@@ -339,10 +496,11 @@ class CursorBase {
   }
 
  protected:
-  BufType* crtBuf_;
-  size_t offset_;
+  ~CursorBase() { }
 
-  ~CursorBase(){}
+  BufType* head() {
+    return buffer_;
+  }
 
   bool tryAdvanceBuffer() {
     BufType* nextBuf = crtBuf_->next();
@@ -357,53 +515,203 @@ class CursorBase {
     return true;
   }
 
+  bool tryRetreatBuffer() {
+    if (UNLIKELY(crtBuf_ == buffer_)) {
+      offset_ = 0;
+      return false;
+    }
+    crtBuf_ = crtBuf_->prev();
+    offset_ = crtBuf_->length();
+    static_cast<Derived*>(this)->advanceDone();
+    return true;
+  }
+
+  void advanceBufferIfEmpty() {
+    if (length() == 0) {
+      tryAdvanceBuffer();
+    }
+  }
+
+  BufType* crtBuf_;
+  size_t offset_ = 0;
+
  private:
+  void readFixedStringSlow(std::string* str, size_t len) {
+    for (size_t available; (available = length()) < len; ) {
+      str->append(reinterpret_cast<const char*>(data()), available);
+      if (UNLIKELY(!tryAdvanceBuffer())) {
+        std::__throw_out_of_range("string underflow");
+      }
+      len -= available;
+    }
+    str->append(reinterpret_cast<const char*>(data()), len);
+    offset_ += len;
+    advanceBufferIfEmpty();
+  }
+
+  size_t pullAtMostSlow(void* buf, size_t len) {
+    uint8_t* p = reinterpret_cast<uint8_t*>(buf);
+    size_t copied = 0;
+    for (size_t available; (available = length()) < len; ) {
+      memcpy(p, data(), available);
+      copied += available;
+      if (UNLIKELY(!tryAdvanceBuffer())) {
+        return copied;
+      }
+      p += available;
+      len -= available;
+    }
+    memcpy(p, data(), len);
+    offset_ += len;
+    advanceBufferIfEmpty();
+    return copied + len;
+  }
+
+  void pullSlow(void* buf, size_t len) {
+    if (UNLIKELY(pullAtMostSlow(buf, len) != len)) {
+      std::__throw_out_of_range("underflow");
+    }
+  }
+
+  size_t skipAtMostSlow(size_t len) {
+    size_t skipped = 0;
+    for (size_t available; (available = length()) < len; ) {
+      skipped += available;
+      if (UNLIKELY(!tryAdvanceBuffer())) {
+        return skipped;
+      }
+      len -= available;
+    }
+    offset_ += len;
+    advanceBufferIfEmpty();
+    return skipped + len;
+  }
+
+  void skipSlow(size_t len) {
+    if (UNLIKELY(skipAtMostSlow(len) != len)) {
+      std::__throw_out_of_range("underflow");
+    }
+  }
+
+  size_t retreatAtMostSlow(size_t len) {
+    size_t retreated = 0;
+    for (size_t available; (available = offset_) < len;) {
+      retreated += available;
+      if (UNLIKELY(!tryRetreatBuffer())) {
+        return retreated;
+      }
+      len -= available;
+    }
+    offset_ -= len;
+    return retreated + len;
+  }
+
+  void retreatSlow(size_t len) {
+    if (UNLIKELY(retreatAtMostSlow(len) != len)) {
+      std::__throw_out_of_range("underflow");
+    }
+  }
+
   void advanceDone() {
   }
 
   BufType* buffer_;
 };
 
+}  // namespace detail
+
+class Cursor : public detail::CursorBase<Cursor, const IOBuf> {
+ public:
+  explicit Cursor(const IOBuf* buf)
+    : detail::CursorBase<Cursor, const IOBuf>(buf) {}
+
+  template <class OtherDerived, class OtherBuf>
+  explicit Cursor(const detail::CursorBase<OtherDerived, OtherBuf>& cursor)
+    : detail::CursorBase<Cursor, const IOBuf>(cursor) {}
+};
+
+namespace detail {
+
 template <class Derived>
 class Writable {
  public:
   template <class T>
-  typename std::enable_if<std::is_integral<T>::value>::type
+  typename std::enable_if<std::is_arithmetic<T>::value>::type
   write(T value) {
     const uint8_t* u8 = reinterpret_cast<const uint8_t*>(&value);
-    push(u8, sizeof(T));
+    Derived* d = static_cast<Derived*>(this);
+    d->push(u8, sizeof(T));
   }
 
   template <class T>
   void writeBE(T value) {
-    write(Endian::big(value));
+    Derived* d = static_cast<Derived*>(this);
+    d->write(Endian::big(value));
   }
 
   template <class T>
   void writeLE(T value) {
-    write(Endian::little(value));
+    Derived* d = static_cast<Derived*>(this);
+    d->write(Endian::little(value));
   }
 
   void push(const uint8_t* buf, size_t len) {
     Derived* d = static_cast<Derived*>(this);
     if (d->pushAtMost(buf, len) != len) {
-      throw std::out_of_range("overflow");
+      std::__throw_out_of_range("overflow");
     }
   }
-};
 
-} // namespace detail
+  void push(ByteRange buf) {
+    if (this->pushAtMost(buf) != buf.size()) {
+      std::__throw_out_of_range("overflow");
+    }
+  }
 
-class Cursor : public detail::CursorBase<Cursor, const IOBuf> {
- public:
-  explicit Cursor(const IOBuf* buf)
-    : detail::CursorBase<Cursor, const IOBuf>(buf) {}
+  size_t pushAtMost(ByteRange buf) {
+    Derived* d = static_cast<Derived*>(this);
+    return d->pushAtMost(buf.data(), buf.size());
+  }
 
-  template <class CursorType>
-  explicit Cursor(CursorType& cursor)
-    : detail::CursorBase<Cursor, const IOBuf>(cursor) {}
+  /**
+   * push len bytes of data from input cursor, data could be in an IOBuf chain.
+   * If input cursor contains less than len bytes, or this cursor has less than
+   * len bytes writable space, an out_of_range exception will be thrown.
+   */
+  void push(Cursor cursor, size_t len) {
+    if (this->pushAtMost(cursor, len) != len) {
+      std::__throw_out_of_range("overflow");
+    }
+  }
+
+  size_t pushAtMost(Cursor cursor, size_t len) {
+    size_t written = 0;
+    for(;;) {
+      auto currentBuffer = cursor.peekBytes();
+      const uint8_t* crtData = currentBuffer.data();
+      size_t available = currentBuffer.size();
+      if (available == 0) {
+        // end of buffer chain
+        return written;
+      }
+      // all data is in current buffer
+      if (available >= len) {
+        this->push(crtData, len);
+        cursor.skip(len);
+        return written + len;
+      }
+
+      // write the whole current IOBuf
+      this->push(crtData, available);
+      cursor.skip(available);
+      written += available;
+      len -= available;
+    }
+  }
 };
 
+} // namespace detail
+
 enum class CursorAccess {
   PRIVATE,
   UNSHARE
@@ -419,8 +727,8 @@ class RWCursor
     : detail::CursorBase<RWCursor<access>, IOBuf>(buf),
       maybeShared_(true) {}
 
-  template <class CursorType>
-  explicit RWCursor(CursorType& cursor)
+  template <class OtherDerived, class OtherBuf>
+  explicit RWCursor(const detail::CursorBase<OtherDerived, OtherBuf>& cursor)
     : detail::CursorBase<RWCursor<access>, IOBuf>(cursor),
       maybeShared_(true) {}
   /**
@@ -428,10 +736,34 @@ class RWCursor
    * by coalescing subsequent buffers from the chain as necessary.
    */
   void gather(size_t n) {
+    // Forbid attempts to gather beyond the end of this IOBuf chain.
+    // Otherwise we could try to coalesce the head of the chain and end up
+    // accidentally freeing it, invalidating the pointer owned by external
+    // code.
+    //
+    // If crtBuf_ == head() then IOBuf::gather() will perform all necessary
+    // checking.  We only have to perform an explicit check here when calling
+    // gather() on a non-head element.
+    if (this->crtBuf_ != this->head() && this->totalLength() < n) {
+      throw std::overflow_error("cannot gather() past the end of the chain");
+    }
     this->crtBuf_->gather(this->offset_ + n);
   }
+  void gatherAtMost(size_t n) {
+    size_t size = std::min(n, this->totalLength());
+    return this->crtBuf_->gather(this->offset_ + size);
+  }
 
+  using detail::Writable<RWCursor<access>>::pushAtMost;
   size_t pushAtMost(const uint8_t* buf, size_t len) {
+    // We have to explicitly check for an input length of 0.
+    // We support buf being nullptr in this case, but we need to avoid calling
+    // memcpy() with a null source pointer, since that is undefined behavior
+    // even if the length is 0.
+    if (len == 0) {
+      return 0;
+    }
+
     size_t copied = 0;
     for (;;) {
       // Fast path: the current buffer is big enough.
@@ -462,7 +794,7 @@ class RWCursor
     folly::IOBuf* nextBuf;
     if (this->offset_ == 0) {
       // Can just prepend
-      nextBuf = buf.get();
+      nextBuf = this->crtBuf_;
       this->crtBuf_->prependChain(std::move(buf));
     } else {
       std::unique_ptr<folly::IOBuf> remaining;
@@ -516,7 +848,7 @@ typedef RWCursor<CursorAccess::UNSHARE> RWUnshareCursor;
  */
 class Appender : public detail::Writable<Appender> {
  public:
-  Appender(IOBuf* buf, uint32_t growth)
+  Appender(IOBuf* buf, uint64_t growth)
     : buffer_(buf),
       crtBuf_(buf->prev()),
       growth_(growth) {
@@ -542,7 +874,7 @@ class Appender : public detail::Writable<Appender> {
    * Ensure at least n contiguous bytes available to write.
    * Postcondition: length() >= n.
    */
-  void ensure(uint32_t n) {
+  void ensure(uint64_t n) {
     if (LIKELY(length() >= n)) {
       return;
     }
@@ -550,7 +882,7 @@ class Appender : public detail::Writable<Appender> {
     // Waste the rest of the current buffer and allocate a new one.
     // Don't make it too small, either.
     if (growth_ == 0) {
-      throw std::out_of_range("can't grow buffer chain");
+      std::__throw_out_of_range("can't grow buffer chain");
     }
 
     n = std::max(n, growth_);
@@ -558,7 +890,16 @@ class Appender : public detail::Writable<Appender> {
     crtBuf_ = buffer_->prev();
   }
 
+  using detail::Writable<Appender>::pushAtMost;
   size_t pushAtMost(const uint8_t* buf, size_t len) {
+    // We have to explicitly check for an input length of 0.
+    // We support buf being nullptr in this case, but we need to avoid calling
+    // memcpy() with a null source pointer, since that is undefined behavior
+    // even if the length is 0.
+    if (len == 0) {
+      return 0;
+    }
+
     size_t copied = 0;
     for (;;) {
       // Fast path: it all fits in one buffer.
@@ -580,6 +921,42 @@ class Appender : public detail::Writable<Appender> {
     }
   }
 
+  /*
+   * Append to the end of this buffer, using a printf() style
+   * format specifier.
+   *
+   * Note that folly/Format.h provides nicer and more type-safe mechanisms
+   * for formatting strings, which should generally be preferred over
+   * printf-style formatting.  Appender objects can be used directly as an
+   * output argument for Formatter objects.  For example:
+   *
+   *   Appender app(&iobuf);
+   *   format("{} {}", "hello", "world")(app);
+   *
+   * However, printf-style strings are still needed when dealing with existing
+   * third-party code in some cases.
+   *
+   * This will always add a nul-terminating character after the end
+   * of the output.  However, the buffer data length will only be updated to
+   * include the data itself.  The nul terminator will be the first byte in the
+   * buffer tailroom.
+   *
+   * This method may throw exceptions on error.
+   */
+  void printf(FOLLY_PRINTF_FORMAT const char* fmt, ...)
+    FOLLY_PRINTF_FORMAT_ATTR(2, 3);
+
+  void vprintf(const char* fmt, va_list ap);
+
+  /*
+   * Calling an Appender object with a StringPiece will append the string
+   * piece.  This allows Appender objects to be used directly with
+   * Formatter.
+   */
+  void operator()(StringPiece sp) {
+    push(ByteRange(sp));
+  }
+
  private:
   bool tryGrowChain() {
     assert(crtBuf_->next() == buffer_);
@@ -594,7 +971,7 @@ class Appender : public detail::Writable<Appender> {
 
   IOBuf* buffer_;
   IOBuf* crtBuf_;
-  uint32_t growth_;
+  uint64_t growth_;
 };
 
 class QueueAppender : public detail::Writable<QueueAppender> {
@@ -603,92 +980,76 @@ class QueueAppender : public detail::Writable<QueueAppender> {
    * Create an Appender that writes to a IOBufQueue.  When we allocate
    * space in the queue, we grow no more than growth bytes at once
    * (unless you call ensure() with a bigger value yourself).
-   * Throw if we ever need to allocate more than maxTotalGrowth.
    */
-  QueueAppender(IOBufQueue* queue,
-                uint32_t growth,
-                size_t maxTotalGrowth = std::numeric_limits<size_t>::max()) {
-    reset(queue, growth, maxTotalGrowth);
+  QueueAppender(IOBufQueue* queue, uint64_t growth) {
+    reset(queue, growth);
   }
 
-  void reset(IOBufQueue* queue,
-             uint32_t growth,
-             size_t maxTotalGrowth = std::numeric_limits<size_t>::max()) {
+  void reset(IOBufQueue* queue, uint64_t growth) {
     queue_ = queue;
     growth_ = growth;
-    remainingGrowth_ = maxTotalGrowth;
-    next_ = nullptr;
-    available_ = 0;
   }
 
-  uint8_t* writableData() { return next_; }
+  uint8_t* writableData() {
+    return static_cast<uint8_t*>(queue_->writableTail());
+  }
 
-  size_t length() const { return available_; }
+  size_t length() const { return queue_->tailroom(); }
 
-  void append(size_t n) {
-    assert(n <= available_);
-    assert(n <= remainingGrowth_);
-    queue_->postallocate(n);
-    next_ += n;
-    available_ -= n;
-    remainingGrowth_ -= n;
-  }
+  void append(size_t n) { queue_->postallocate(n); }
 
   // Ensure at least n contiguous; can go above growth_, throws if
   // not enough room.
-  void ensure(uint32_t n) {
-    if (UNLIKELY(n > remainingGrowth_)) {
-      throw std::out_of_range("overflow");
-    }
-
-    if (LIKELY(length() >= n)) {
-      return;
-    }
-
-    size_t desired = std::min(growth_, remainingGrowth_ - n);
+  void ensure(uint64_t n) { queue_->preallocate(n, growth_); }
 
-    // Grab some more.
-    auto p = queue_->preallocate(n, desired);
-
-    next_ = static_cast<uint8_t*>(p.first);
-    available_ = p.second;
+  template <class T>
+  typename std::enable_if<std::is_arithmetic<T>::value>::type
+  write(T value) {
+    // We can't fail.
+    auto p = queue_->preallocate(sizeof(T), growth_);
+    storeUnaligned(p.first, value);
+    queue_->postallocate(sizeof(T));
   }
 
+  using detail::Writable<QueueAppender>::pushAtMost;
   size_t pushAtMost(const uint8_t* buf, size_t len) {
-    if (UNLIKELY(len > remainingGrowth_)) {
-      len = remainingGrowth_;
+    // Fill the current buffer
+    const size_t copyLength = std::min(len, length());
+    if (copyLength != 0) {
+      memcpy(writableData(), buf, copyLength);
+      append(copyLength);
+      buf += copyLength;
     }
-
-    size_t remaining = len;
+    // Allocate more buffers as necessary
+    size_t remaining = len - copyLength;
     while (remaining != 0) {
-      ensure(std::min(remaining, growth_));
-      size_t n = std::min(remaining, available_);
-      memcpy(next_, buf, n);
-      buf += n;
-      remaining -= n;
-      append(n);
+      auto p = queue_->preallocate(std::min(remaining, growth_),
+                                   growth_,
+                                   remaining);
+      memcpy(p.first, buf, p.second);
+      queue_->postallocate(p.second);
+      buf += p.second;
+      remaining -= p.second;
     }
 
     return len;
   }
 
-  // insert doesn't count towards maxTotalGrowth
   void insert(std::unique_ptr<folly::IOBuf> buf) {
     if (buf) {
       queue_->append(std::move(buf), true);
-      next_ = nullptr;
-      available_ = 0;
     }
   }
 
+  void insert(const folly::IOBuf& buf) {
+    insert(buf.clone());
+  }
+
  private:
   folly::IOBufQueue* queue_;
   size_t growth_;
-  size_t remainingGrowth_;
-  uint8_t* next_;
-  size_t available_;
 };
 
 }}  // folly::io
 
-#endif // FOLLY_CURSOR_H
+#include <folly/io/Cursor-inl.h>