EDK: Add mojo::util::StringPrintf() (etc.) and convert away from base's.

This is loosely based on Chromium's base::StringPrintf() (but
simplified/improved). Note that the names of "append f", etc. are also
made more consistent (ditto for "v printf", etc.).

R=vardhan@google.com

Review URL: https://codereview.chromium.org/1486923002 .
diff --git a/mojo/edk/system/channel.cc b/mojo/edk/system/channel.cc
index c26a374..69227ab 100644
--- a/mojo/edk/system/channel.cc
+++ b/mojo/edk/system/channel.cc
@@ -9,14 +9,15 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
-#include "base/strings/stringprintf.h"
 #include "mojo/edk/system/endpoint_relayer.h"
 #include "mojo/edk/system/transport_data.h"
+#include "mojo/edk/util/string_printf.h"
 
 using mojo::platform::ScopedPlatformHandle;
 using mojo::util::MakeRefCounted;
 using mojo::util::MutexLocker;
 using mojo::util::RefPtr;
+using mojo::util::StringPrintf;
 
 namespace mojo {
 namespace system {
@@ -162,10 +163,10 @@
   if (!SendControlMessage(MessageInTransit::Subtype::CHANNEL_REMOVE_ENDPOINT,
                           local_id, remote_id, 0, nullptr)) {
     HandleLocalError(
-        base::StringPrintf("Failed to send message to remove remote endpoint "
-                           "(local ID %u, remote ID %u)",
-                           static_cast<unsigned>(local_id.value()),
-                           static_cast<unsigned>(remote_id.value()))
+        StringPrintf("Failed to send message to remove remote endpoint (local "
+                     "ID %u, remote ID %u)",
+                     static_cast<unsigned>(local_id.value()),
+                     static_cast<unsigned>(remote_id.value()))
             .c_str());
   }
 }
@@ -309,10 +310,9 @@
       OnReadMessageForChannel(message_view, std::move(platform_handles));
       break;
     default:
-      HandleRemoteError(
-          base::StringPrintf("Received message of invalid type %u",
-                             static_cast<unsigned>(message_view.type()))
-              .c_str());
+      HandleRemoteError(StringPrintf("Received message of invalid type %u",
+                                     static_cast<unsigned>(message_view.type()))
+                            .c_str());
       break;
   }
 }
@@ -390,9 +390,10 @@
   }
   if (!endpoint) {
     HandleRemoteError(
-        base::StringPrintf(
+        StringPrintf(
             "Received a message for nonexistent local destination ID %u",
-            static_cast<unsigned>(local_id.value())).c_str());
+            static_cast<unsigned>(local_id.value()))
+            .c_str());
     // This is strongly indicative of some problem. However, it's not a fatal
     // error, since it may indicate a buggy (or hostile) remote process. Don't
     // die even for Debug builds, since handling this properly needs to be
@@ -544,11 +545,12 @@
   if (!SendControlMessage(
           MessageInTransit::Subtype::CHANNEL_REMOVE_ENDPOINT_ACK, local_id,
           remote_id, 0, nullptr)) {
-    HandleLocalError(base::StringPrintf(
-                         "Failed to send message to ack remove remote endpoint "
-                         "(local ID %u, remote ID %u)",
-                         static_cast<unsigned>(local_id.value()),
-                         static_cast<unsigned>(remote_id.value())).c_str());
+    HandleLocalError(
+        StringPrintf("Failed to send message to ack remove remote endpoint "
+                     "(local ID %u, remote ID %u)",
+                     static_cast<unsigned>(local_id.value()),
+                     static_cast<unsigned>(remote_id.value()))
+            .c_str());
   }
 
   return true;
@@ -619,11 +621,12 @@
   if (!SendControlMessage(
           MessageInTransit::Subtype::CHANNEL_ATTACH_AND_RUN_ENDPOINT, local_id,
           remote_id, 0, nullptr)) {
-    HandleLocalError(base::StringPrintf(
-                         "Failed to send message to run remote endpoint (local "
-                         "ID %u, remote ID %u)",
-                         static_cast<unsigned>(local_id.value()),
-                         static_cast<unsigned>(remote_id.value())).c_str());
+    HandleLocalError(
+        StringPrintf("Failed to send message to run remote endpoint (local "
+                     "ID %u, remote ID %u)",
+                     static_cast<unsigned>(local_id.value()),
+                     static_cast<unsigned>(remote_id.value()))
+            .c_str());
     // TODO(vtl): Should we continue on to |AttachAndRun()|?
   }
 
diff --git a/mojo/edk/system/message_pipe_perftest.cc b/mojo/edk/system/message_pipe_perftest.cc
index 27394c6..e3d77ef 100644
--- a/mojo/edk/system/message_pipe_perftest.cc
+++ b/mojo/edk/system/message_pipe_perftest.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
-#include "base/strings/stringprintf.h"
 #include "mojo/edk/platform/scoped_platform_handle.h"
 #include "mojo/edk/system/local_message_pipe_endpoint.h"
 #include "mojo/edk/system/message_pipe.h"
@@ -19,11 +18,13 @@
 #include "mojo/edk/system/test/stopwatch.h"
 #include "mojo/edk/test/test_utils.h"
 #include "mojo/edk/util/ref_ptr.h"
+#include "mojo/edk/util/string_printf.h"
 #include "mojo/public/cpp/system/macros.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using mojo::platform::ScopedPlatformHandle;
 using mojo::util::RefPtr;
+using mojo::util::StringPrintf;
 
 namespace mojo {
 namespace system {
@@ -68,9 +69,8 @@
     // Have one ping-pong to ensure channel being established.
     WriteWaitThenRead(mp);
 
-    std::string test_name =
-        base::StringPrintf("IPC_Perf_%dx_%u", message_count_,
-                           static_cast<unsigned>(message_size_));
+    std::string test_name = StringPrintf("IPC_Perf_%dx_%u", message_count_,
+                                         static_cast<unsigned>(message_size_));
     test::Stopwatch stopwatch;
 
     stopwatch.Start();
diff --git a/mojo/edk/util/BUILD.gn b/mojo/edk/util/BUILD.gn
index 6a08566c..502164b 100644
--- a/mojo/edk/util/BUILD.gn
+++ b/mojo/edk/util/BUILD.gn
@@ -20,6 +20,8 @@
     "ref_ptr.h",
     "ref_ptr_internal.h",
     "scoped_file.h",
+    "string_printf.cc",
+    "string_printf.h",
     "thread_annotations.h",
     "thread_checker.h",
     "waitable_event.cc",
@@ -35,6 +37,7 @@
     "cond_var_unittest.cc",
     "mutex_unittest.cc",
     "ref_counted_unittest.cc",
+    "string_printf_unittest.cc",
     "thread_annotations_unittest.cc",
     "thread_checker_unittest.cc",
     "waitable_event_unittest.cc",
diff --git a/mojo/edk/util/string_printf.cc b/mojo/edk/util/string_printf.cc
new file mode 100644
index 0000000..ba762ee
--- /dev/null
+++ b/mojo/edk/util/string_printf.cc
@@ -0,0 +1,92 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/util/string_printf.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include <memory>
+
+namespace mojo {
+namespace util {
+namespace {
+
+void StringVAppendfHelper(std::string* dest, const char* format, va_list ap) {
+  // Size of the small stack buffer to use first. This should be kept in sync
+  // with the numbers in StringPrintfTest.StringPrintf_Boundary.
+  constexpr size_t kStackBufferSize = 1024u;
+
+  // First, try with a small buffer on the stack.
+  char stack_buf[kStackBufferSize];
+  // Copy |ap| (which can only be used once), in case we need to retry.
+  va_list ap_copy;
+  va_copy(ap_copy, ap);
+  int result = vsnprintf(stack_buf, kStackBufferSize, format, ap_copy);
+  va_end(ap_copy);
+  if (result < 0) {
+    // As far as I can tell, we'd only get |EOVERFLOW| if the result is so large
+    // that it can't be represented by an |int| (in which case retrying would be
+    // futile), so Chromium's implementation is wrong.
+    return;
+  }
+  // |result| should be the number of characters we need, not including the
+  // terminating null. However, |vsnprintf()| always null-terminates!
+  size_t output_size = static_cast<size_t>(result);
+  // Check if the output fit into our stack buffer. This is "<" not "<=", since
+  // |vsnprintf()| will null-terminate.
+  if (output_size < kStackBufferSize) {
+    // It fit.
+    dest->append(stack_buf, static_cast<size_t>(result));
+    return;
+  }
+
+  // Since we have the required output size, we can just heap allocate that.
+  // (Add 1 because |vsnprintf()| will always null-terminate.)
+  size_t heap_buf_size = output_size + 1u;
+  std::unique_ptr<char[]> heap_buf(new char[heap_buf_size]);
+  result = vsnprintf(heap_buf.get(), heap_buf_size, format, ap);
+  if (result < 0 || static_cast<size_t>(result) > output_size) {
+    assert(false);
+    return;
+  }
+  assert(static_cast<size_t>(result) == output_size);
+  dest->append(heap_buf.get(), static_cast<size_t>(result));
+}
+
+}  // namespace
+
+std::string StringPrintf(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  std::string rv;
+  StringVAppendf(&rv, format, ap);
+  va_end(ap);
+  return rv;
+}
+
+std::string StringVPrintf(const char* format, va_list ap) {
+  std::string rv;
+  StringVAppendf(&rv, format, ap);
+  return rv;
+}
+
+void StringAppendf(std::string* dest, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  StringVAppendf(dest, format, ap);
+  va_end(ap);
+}
+
+void StringVAppendf(std::string* dest, const char* format, va_list ap) {
+  int old_errno = errno;
+  StringVAppendfHelper(dest, format, ap);
+  errno = old_errno;
+}
+
+}  // namespace util
+}  // namespace mojo
diff --git a/mojo/edk/util/string_printf.h b/mojo/edk/util/string_printf.h
new file mode 100644
index 0000000..584b5b9
--- /dev/null
+++ b/mojo/edk/util/string_printf.h
@@ -0,0 +1,38 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// |printf()|-like formatting functions that output/append to C++ strings.
+//
+// TODO(vtl): We don't have the |PRINTF_FORMAT()| macros/warnings like
+// Chromium's version -- should we? (I've rarely seen them being useful.)
+
+#ifndef MOJO_EDK_UTIL_STRING_PRINTF_H_
+#define MOJO_EDK_UTIL_STRING_PRINTF_H_
+
+#include <stdarg.h>
+
+#include <string>
+
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace util {
+
+// Formats |printf()|-like input and returns it as an |std::string|.
+std::string StringPrintf(const char* format, ...) MOJO_WARN_UNUSED_RESULT;
+
+// Formats |vprintf()|-like input and returns it as an |std::string|.
+std::string StringVPrintf(const char* format,
+                          va_list ap) MOJO_WARN_UNUSED_RESULT;
+
+// Formats |printf()|-like input and appends it to |*dest|.
+void StringAppendf(std::string* dest, const char* format, ...);
+
+// Formats |vprintf()|-like input and appends it to |*dest|.
+void StringVAppendf(std::string* dest, const char* format, va_list ap);
+
+}  // namespace util
+}  // namespace mojo
+
+#endif  // MOJO_EDK_UTIL_STRING_PRINTF_H_
diff --git a/mojo/edk/util/string_printf_unittest.cc b/mojo/edk/util/string_printf_unittest.cc
new file mode 100644
index 0000000..f9faea0
--- /dev/null
+++ b/mojo/edk/util/string_printf_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/edk/util/string_printf.h"
+
+#include <errno.h>
+#include <stdarg.h>
+
+#include <string>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace util {
+namespace {
+
+// Note: |runnable| can't be a reference since that'd make the behavior of
+// |va_start()| undefined.
+template <typename Runnable>
+std::string VAListHelper(Runnable runnable, ...) {
+  va_list ap;
+  va_start(ap, runnable);
+  std::string rv = runnable(ap);
+  va_end(ap);
+  return rv;
+}
+
+TEST(StringPrintfTest, StringPrintf_Basic) {
+  EXPECT_EQ("", StringPrintf(""));
+  EXPECT_EQ("hello", StringPrintf("hello"));
+  EXPECT_EQ("hello-123", StringPrintf("hello%d", -123));
+  EXPECT_EQ("hello0123FACE", StringPrintf("%s%04d%X", "hello", 123, 0xfaceU));
+}
+
+TEST(StringPrintfTest, StringVPrintf_Basic) {
+  EXPECT_EQ("", VAListHelper([](va_list ap) -> std::string {
+              return StringVPrintf("", ap);
+            }));
+  EXPECT_EQ("hello", VAListHelper([](va_list ap) -> std::string {
+              return StringVPrintf("hello", ap);
+            }));
+  EXPECT_EQ("hello-123", VAListHelper([](va_list ap) -> std::string {
+              return StringVPrintf("hello%d", ap);
+            }, -123));
+  EXPECT_EQ("hello0123FACE", VAListHelper([](va_list ap) -> std::string {
+              return StringVPrintf("%s%04d%X", ap);
+            }, "hello", 123, 0xfaceU));
+}
+
+TEST(StringPrintfTest, StringAppendf_Basic) {
+  {
+    std::string s = "existing";
+    StringAppendf(&s, "");
+    EXPECT_EQ("existing", s);
+  }
+  {
+    std::string s = "existing";
+    StringAppendf(&s, "hello");
+    EXPECT_EQ("existinghello", s);
+  }
+  {
+    std::string s = "existing";
+    StringAppendf(&s, "hello%d", -123);
+    EXPECT_EQ("existinghello-123", s);
+  }
+  {
+    std::string s = "existing";
+    StringAppendf(&s, "%s%04d%X", "hello", 123, 0xfaceU);
+    EXPECT_EQ("existinghello0123FACE", s);
+  }
+}
+
+TEST(StringPrintfTest, StringVAppendf_Basic) {
+  EXPECT_EQ("existing", VAListHelper([](va_list ap) -> std::string {
+              std::string s = "existing";
+              StringVAppendf(&s, "", ap);
+              return s;
+            }));
+  EXPECT_EQ("existinghello", VAListHelper([](va_list ap) -> std::string {
+              std::string s = "existing";
+              StringVAppendf(&s, "hello", ap);
+              return s;
+            }));
+  EXPECT_EQ("existinghello-123", VAListHelper([](va_list ap) -> std::string {
+              std::string s = "existing";
+              StringVAppendf(&s, "hello%d", ap);
+              return s;
+            }, -123));
+  EXPECT_EQ("existinghello0123FACE",
+            VAListHelper([](va_list ap) -> std::string {
+              std::string s = "existing";
+              StringVAppendf(&s, "%s%04d%X", ap);
+              return s;
+            }, "hello", 123, 0xfaceU));
+}
+
+// Generally, we assume that everything forwards to |StringVAppendf()|, so
+// testing |StringPrintf()| more carefully suffices.
+
+TEST(StringPrintfTest, StringPrintf_Boundary) {
+  // Note: The size of strings generated should cover the boundary cases in the
+  // constant |kStackBufferSize| in |StringVAppendf()|.
+  for (size_t i = 800; i < 1200; i++) {
+    std::string stuff(i, 'x');
+    std::string format = stuff + "%d" + "%s" + " world";
+    EXPECT_EQ(stuff + "123" + "hello world",
+              StringPrintf(format.c_str(), 123, "hello"))
+        << i;
+  }
+}
+
+TEST(StringPrintfTest, StringPrintf_VeryBig) {
+  // 4 megabytes of exes (we'll generate 5 times this).
+  std::string stuff(4u << 20u, 'x');
+  std::string format = "%s" + stuff + "%s" + stuff + "%s";
+  EXPECT_EQ(stuff + stuff + stuff + stuff + stuff,
+            StringPrintf(format.c_str(), stuff.c_str(), stuff.c_str(),
+                         stuff.c_str()));
+}
+
+}  // namespace
+}  // namespace util
+}  // namespace mojo