Fix off-by-one in llvm::Format::print.
authorDaniel Dunbar <daniel@zuster.org>
Sun, 23 Aug 2009 20:31:39 +0000 (20:31 +0000)
committerDaniel Dunbar <daniel@zuster.org>
Sun, 23 Aug 2009 20:31:39 +0000 (20:31 +0000)
 - This also shortens the Format.h implementation, and uses the print buffer
   fully (it was wasting a character).

 - This manifested as llvm-test failures, because one side effect was that
   raw_ostream would write garbage '\x00' values into the output stream if it
   happened that the string was at the end of the buffer. This meant that grep
   would report 'Binary file matches', which meant the silly pattern matching
   llvm-test eventually does would fail. Cute. :)

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@79862 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/Support/Format.h
lib/Support/raw_ostream.cpp
unittests/Support/raw_ostream_test.cpp

index 2ab097faf56d97b8624707acc517fb0f9556d972..df03f66ddc7d1ea09ec7290b77510d12e228cf4e 100644 (file)
@@ -36,6 +36,10 @@ class format_object_base {
 protected:
   const char *Fmt;
   virtual void home(); // Out of line virtual method.
+
+  /// snprint - Call snprintf() for this object, on the given buffer and size.
+  virtual int snprint(char *Buffer, unsigned BufferSize) const = 0;
+
 public:
   format_object_base(const char *fmt) : Fmt(fmt) {}
   virtual ~format_object_base() {}
@@ -43,7 +47,23 @@ public:
   /// print - Format the object into the specified buffer.  On success, this
   /// returns the length of the formatted string.  If the buffer is too small,
   /// this returns a length to retry with, which will be larger than BufferSize.
-  virtual unsigned print(char *Buffer, unsigned BufferSize) const = 0;
+  unsigned print(char *Buffer, unsigned BufferSize) const {
+    assert(BufferSize && "Invalid buffer size!");
+
+    // Print the string, leaving room for the terminating null.
+    int N = snprint(Buffer, BufferSize);
+
+    // VC++ and old GlibC return negative on overflow, just double the size.
+    if (N < 0)
+      return BufferSize*2;
+
+    // Other impls yield number of bytes needed, not including the final '\0'.
+    if (unsigned(N) >= BufferSize)
+      return N+1;
+
+    // Otherwise N is the length of output (not including the final '\0').
+    return N;
+  }
 };
 
 /// format_object1 - This is a templated helper class used by the format
@@ -58,17 +78,8 @@ public:
     : format_object_base(fmt), Val(val) {
   }
 
-  /// print - Format the object into the specified buffer.  On success, this
-  /// returns the length of the formatted string.  If the buffer is too small,
-  /// this returns a length to retry with, which will be larger than BufferSize.
-  virtual unsigned print(char *Buffer, unsigned BufferSize) const {
-    int N = snprintf(Buffer, BufferSize-1, Fmt, Val);
-    if (N < 0)             // VC++ and old GlibC return negative on overflow.
-      return BufferSize*2;
-    if (unsigned(N) >= BufferSize-1)// Other impls yield number of bytes needed.
-      return N+1;
-    // If N is positive and <= BufferSize-1, then the string fit, yay.
-    return N;
+  virtual int snprint(char *Buffer, unsigned BufferSize) const {
+    return snprintf(Buffer, BufferSize, Fmt, Val);
   }
 };
 
@@ -85,17 +96,8 @@ public:
   : format_object_base(fmt), Val1(val1), Val2(val2) {
   }
 
-  /// print - Format the object into the specified buffer.  On success, this
-  /// returns the length of the formatted string.  If the buffer is too small,
-  /// this returns a length to retry with, which will be larger than BufferSize.
-  virtual unsigned print(char *Buffer, unsigned BufferSize) const {
-    int N = snprintf(Buffer, BufferSize-1, Fmt, Val1, Val2);
-    if (N < 0)             // VC++ and old GlibC return negative on overflow.
-      return BufferSize*2;
-    if (unsigned(N) >= BufferSize-1)// Other impls yield number of bytes needed.
-      return N+1;
-    // If N is positive and <= BufferSize-1, then the string fit, yay.
-    return N;
+  virtual int snprint(char *Buffer, unsigned BufferSize) const {
+    return snprintf(Buffer, BufferSize, Fmt, Val1, Val2);
   }
 };
 
@@ -113,17 +115,8 @@ public:
     : format_object_base(fmt), Val1(val1), Val2(val2), Val3(val3) {
   }
 
-  /// print - Format the object into the specified buffer.  On success, this
-  /// returns the length of the formatted string.  If the buffer is too small,
-  /// this returns a length to retry with, which will be larger than BufferSize.
-  virtual unsigned print(char *Buffer, unsigned BufferSize) const {
-    int N = snprintf(Buffer, BufferSize-1, Fmt, Val1, Val2, Val3);
-    if (N < 0)             // VC++ and old GlibC return negative on overflow.
-      return BufferSize*2;
-    if (unsigned(N) >= BufferSize-1)// Other impls yield number of bytes needed.
-      return N+1;
-    // If N is positive and <= BufferSize-1, then the string fit, yay.
-    return N;
+  virtual int snprint(char *Buffer, unsigned BufferSize) const {
+    return snprintf(Buffer, BufferSize, Fmt, Val1, Val2, Val3);
   }
 };
 
index 2181ccec0b1a5fba9a43ae5c7d2f1b75490f1828..221859d5337999380c46e502259f6949f0a35642 100644 (file)
@@ -253,12 +253,12 @@ raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
   // If we have more than a few bytes left in our output buffer, try
   // formatting directly onto its end.
   size_t NextBufferSize = 127;
-  if (OutBufEnd-OutBufCur > 3) {
-    size_t BufferBytesLeft = OutBufEnd-OutBufCur;
+  size_t BufferBytesLeft = OutBufEnd - OutBufCur;
+  if (BufferBytesLeft > 3) {
     size_t BytesUsed = Fmt.print(OutBufCur, BufferBytesLeft);
     
     // Common case is that we have plenty of space.
-    if (BytesUsed < BufferBytesLeft) {
+    if (BytesUsed <= BufferBytesLeft) {
       OutBufCur += BytesUsed;
       return *this;
     }
@@ -277,11 +277,11 @@ raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
     V.resize(NextBufferSize);
     
     // Try formatting into the SmallVector.
-    size_t BytesUsed = Fmt.print(&V[0], NextBufferSize);
+    size_t BytesUsed = Fmt.print(V.data(), NextBufferSize);
     
     // If BytesUsed fit into the vector, we win.
     if (BytesUsed <= NextBufferSize)
-      return write(&V[0], BytesUsed);
+      return write(V.data(), BytesUsed);
     
     // Otherwise, try again with a new size.
     assert(BytesUsed > NextBufferSize && "Didn't grow buffer!?");
index 3e22a0569b21199a9e16787230f36b233f926b79..52639ba7a2f9faa303e559239c260a53874ea9ca 100644 (file)
@@ -8,6 +8,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "gtest/gtest.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Format.h"
 #include "llvm/Support/raw_ostream.h"
 
 using namespace llvm;
@@ -20,6 +22,23 @@ template<typename T> std::string printToString(const T &Value) {
   return res;    
 }
 
+/// printToString - Print the given value to a stream which only has \arg
+/// BytesLeftInBuffer bytes left in the buffer. This is useful for testing edge
+/// cases in the buffer handling logic.
+template<typename T> std::string printToString(const T &Value,
+                                               unsigned BytesLeftInBuffer) {
+  // FIXME: This is relying on internal knowledge of how raw_ostream works to
+  // get the buffer position right.
+  SmallString<256> SVec;
+  assert(BytesLeftInBuffer < 256 && "Invalid buffer count!");
+  llvm::raw_svector_ostream OS(SVec);
+  unsigned StartIndex = 256 - BytesLeftInBuffer;
+  for (unsigned i = 0; i != StartIndex; ++i)
+    OS << '?';
+  OS << Value;
+  return OS.str().substr(StartIndex);
+}
+
 template<typename T> std::string printToStringUnbuffered(const T &Value) {
   std::string res;
   llvm::raw_string_ostream OS(res);
@@ -90,4 +109,12 @@ TEST(raw_ostreamTest, Types_Unbuffered) {
   EXPECT_EQ("-9223372036854775808", printToStringUnbuffered(INT64_MIN));
 }
 
+TEST(raw_ostreamTest, BufferEdge) {  
+  EXPECT_EQ("1.20", printToString(format("%.2f", 1.2), 1));
+  EXPECT_EQ("1.20", printToString(format("%.2f", 1.2), 2));
+  EXPECT_EQ("1.20", printToString(format("%.2f", 1.2), 3));
+  EXPECT_EQ("1.20", printToString(format("%.2f", 1.2), 4));
+  EXPECT_EQ("1.20", printToString(format("%.2f", 1.2), 10));
+}
+
 }