[libc-commits] [libc] [libc] Implement vasprintf and asprintf (PR #98824)

Tsz Chan via libc-commits libc-commits at lists.llvm.org
Sun Jul 28 04:14:58 PDT 2024


https://github.com/tszhin-swe updated https://github.com/llvm/llvm-project/pull/98824

>From 98059bab8557e839c6e5564ebb3d02e0ece87365 Mon Sep 17 00:00:00 2001
From: Izaak Schroeder <izaak.schroeder at gmail.com>
Date: Tue, 2 Jul 2024 23:53:14 -0700
Subject: [PATCH 1/2] [libc] Add `vasprintf` function stubs

---
 libc/spec/stdc.td             |  7 +++++++
 libc/src/stdio/CMakeLists.txt | 31 +++++++++++++++++++++++++++++++
 libc/src/stdio/vasprintf.cpp  | 26 ++++++++++++++++++++++++++
 libc/src/stdio/vasprintf.h    | 21 +++++++++++++++++++++
 4 files changed, 85 insertions(+)
 create mode 100644 libc/src/stdio/vasprintf.cpp
 create mode 100644 libc/src/stdio/vasprintf.h

diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index aa56152aee141..e5e0900cce8c6 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -967,6 +967,13 @@ def StdC : StandardSpec<"stdc"> {
               RetValSpec<IntType>,
               [ArgSpec<IntType>, ArgSpec<FILEPtr>]
           >,
+          FunctionSpec<
+              "vasprintf",
+              RetValSpec<IntType>,
+              [ArgSpec<CharRestrictedPtrPtr>,
+               ArgSpec<ConstCharPtr>,
+               ArgSpec<VaListType>]
+          >,
       ],
       [
           ObjectSpec<
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 2d528a903cc2f..0582c96d320b3 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -185,6 +185,37 @@ add_entrypoint_object(
     libc.src.stdio.printf_core.writer
 )
 
+add_entrypoint_object(
+  vfprintf
+  SRCS
+    vfprintf.cpp
+  HDRS
+    vfprintf.h
+  DEPENDS
+    libc.src.__support.arg_list
+    libc.src.stdio.printf_core.vfprintf_internal
+)
+
+add_entrypoint_object(
+  vasprintf
+  SRCS
+    vasprintf.cpp
+  HDRS
+    vasprintf.h
+  DEPENDS
+    libc.src.__support.arg_list
+)
+
+add_stdio_entrypoint_object(
+  fileno
+  SRCS
+    fileno.cpp
+  HDRS
+    fileno.h
+  DEPENDS
+    libc.src.stdio.fileno
+)
+
 add_subdirectory(printf_core)
 add_subdirectory(scanf_core)
 
diff --git a/libc/src/stdio/vasprintf.cpp b/libc/src/stdio/vasprintf.cpp
new file mode 100644
index 0000000000000..712a3f72ab62d
--- /dev/null
+++ b/libc/src/stdio/vasprintf.cpp
@@ -0,0 +1,26 @@
+//===-- Implementation of vasprintf -----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/vasprintf.h"
+
+#include "src/__support/arg_list.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, vasprintf,
+                   (char **__restrict, const char *, va_list vlist)) {
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  return -1;
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdio/vasprintf.h b/libc/src/stdio/vasprintf.h
new file mode 100644
index 0000000000000..c11675461e3fb
--- /dev/null
+++ b/libc/src/stdio/vasprintf.h
@@ -0,0 +1,21 @@
+//===-- Implementation header of vasprintf ----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_VASPRINTF_H
+#define LLVM_LIBC_SRC_STDIO_VASPRINTF_H
+
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace LIBC_NAMESPACE {
+
+int vasprintf(char **__restrict s, const char *format, va_list vlist);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDIO_VASPRINTF_H

>From b32476427d07526e21c3207a18a1fbc9a7e90b36 Mon Sep 17 00:00:00 2001
From: Tsz Chan <keithcth2001 at gmail.com>
Date: Mon, 22 Jul 2024 00:25:49 +0800
Subject: [PATCH 2/2] [libc] Implement vasprint and asprintf

---
 libc/config/baremetal/arm/entrypoints.txt     |   2 +
 libc/config/baremetal/riscv/entrypoints.txt   |   2 +
 libc/config/darwin/arm/entrypoints.txt        |  10 ++
 libc/config/linux/aarch64/entrypoints.txt     |   2 +
 libc/config/linux/riscv/entrypoints.txt       |   2 +
 libc/config/linux/x86_64/entrypoints.txt      |   2 +
 libc/spec/stdc.td                             |   9 +-
 libc/src/stdio/CMakeLists.txt                 |  39 ++++---
 libc/src/stdio/asprintf.cpp                   |  25 +++++
 libc/src/stdio/asprintf.h                     |  22 ++++
 libc/src/stdio/printf_core/core_structs.h     |   2 +-
 .../stdio/printf_core/vasprintf_internal.h    |  75 +++++++++++++
 libc/src/stdio/printf_core/writer.h           | 100 +++++++++++-------
 libc/src/stdio/vasprintf.cpp                  |  10 +-
 libc/test/src/stdio/CMakeLists.txt            |  25 +++++
 libc/test/src/stdio/asprintf_test.cpp         |  84 +++++++++++++++
 libc/test/src/stdio/vasprintf_test.cpp        |  96 +++++++++++++++++
 17 files changed, 440 insertions(+), 67 deletions(-)
 create mode 100644 libc/src/stdio/asprintf.cpp
 create mode 100644 libc/src/stdio/asprintf.h
 create mode 100644 libc/src/stdio/printf_core/vasprintf_internal.h
 create mode 100644 libc/test/src/stdio/asprintf_test.cpp
 create mode 100644 libc/test/src/stdio/vasprintf_test.cpp

diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index 4f2462abc7452..773ee53f21a93 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -90,9 +90,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.remove
     libc.src.stdio.snprintf
     libc.src.stdio.sprintf
+    libc.src.stdio.asprintf
     libc.src.stdio.vprintf
     libc.src.stdio.vsnprintf
     libc.src.stdio.vsprintf
+    libc.src.stdio.vasprintf
 
     # stdbit.h entrypoints
     libc.src.stdbit.stdc_bit_ceil_uc
diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt
index c2ab9d7f19921..0469953f3e916 100644
--- a/libc/config/baremetal/riscv/entrypoints.txt
+++ b/libc/config/baremetal/riscv/entrypoints.txt
@@ -86,9 +86,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.remove
     libc.src.stdio.snprintf
     libc.src.stdio.sprintf
+    libc.src.stdio.asprintf
     libc.src.stdio.vprintf
     libc.src.stdio.vsnprintf
     libc.src.stdio.vsprintf
+    libc.src.stdio.vasprintf
 
     # stdbit.h entrypoints
     libc.src.stdbit.stdc_bit_ceil_uc
diff --git a/libc/config/darwin/arm/entrypoints.txt b/libc/config/darwin/arm/entrypoints.txt
index 383118dc781e5..3eb16816bf414 100644
--- a/libc/config/darwin/arm/entrypoints.txt
+++ b/libc/config/darwin/arm/entrypoints.txt
@@ -94,6 +94,16 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdlib.calloc
     libc.src.stdlib.realloc
     libc.src.stdlib.free
+
+    # stdio.h external entrypoints
+    libc.src.stdio.snprintf
+    libc.src.stdio.sprintf
+    libc.src.stdio.asprintf
+    libc.src.stdio.asprintf
+    libc.src.stdio.vprintf
+    libc.src.stdio.vsnprintf
+    libc.src.stdio.vsprintf
+    libc.src.stdio.vasprintf
 )
 
 set(TARGET_LIBM_ENTRYPOINTS
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index dee6ac673643e..2ee29b8fd1818 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -207,10 +207,12 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.rename
     libc.src.stdio.snprintf
     libc.src.stdio.sprintf
+    libc.src.stdio.asprintf
     #libc.src.stdio.scanf
     #libc.src.stdio.sscanf
     libc.src.stdio.vsnprintf
     libc.src.stdio.vsprintf
+    libc.src.stdio.vasprintf
 
     # sys/mman.h entrypoints
     libc.src.sys.mman.madvise
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 516a4b6ce3433..f7f4e73cbb429 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -205,11 +205,13 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.scanf
     libc.src.stdio.snprintf
     libc.src.stdio.sprintf
+    libc.src.stdio.asprintf
     libc.src.stdio.sscanf
     libc.src.stdio.vfprintf
     libc.src.stdio.vprintf
     libc.src.stdio.vsnprintf
     libc.src.stdio.vsprintf
+    libc.src.stdio.vasprintf
 
     # sys/mman.h entrypoints
     libc.src.sys.mman.madvise
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index b6c55e7aa3033..da97d2eea24a4 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -216,11 +216,13 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdio.scanf
     libc.src.stdio.snprintf
     libc.src.stdio.sprintf
+    libc.src.stdio.asprintf
     libc.src.stdio.sscanf
     libc.src.stdio.vfprintf
     libc.src.stdio.vprintf
     libc.src.stdio.vsnprintf
     libc.src.stdio.vsprintf
+    libc.src.stdio.vasprintf
 
     # sys/epoll.h entrypoints
     libc.src.sys.epoll.epoll_create
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index e5e0900cce8c6..df1686a0946c7 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -934,6 +934,13 @@ def StdC : StandardSpec<"stdc"> {
                ArgSpec<ConstCharRestrictedPtr>,
                ArgSpec<VarArgType>]
           >,
+          FunctionSpec<
+              "asprintf",
+              RetValSpec<IntType>,
+              [ArgSpec<CharRestrictedPtrPtr>,
+               ArgSpec<ConstCharRestrictedPtr>,
+               ArgSpec<VarArgType>]
+          >,
           FunctionSpec<
               "vsprintf",
               RetValSpec<IntType>,
@@ -971,7 +978,7 @@ def StdC : StandardSpec<"stdc"> {
               "vasprintf",
               RetValSpec<IntType>,
               [ArgSpec<CharRestrictedPtrPtr>,
-               ArgSpec<ConstCharPtr>,
+               ArgSpec<ConstCharRestrictedPtr>,
                ArgSpec<VaListType>]
           >,
       ],
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 0582c96d320b3..b5c8a8a31c6e0 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -164,36 +164,39 @@ add_entrypoint_object(
 )
 
 add_entrypoint_object(
-  vsprintf
+  asprintf
   SRCS
-    vsprintf.cpp
+    asprintf.cpp
   HDRS
-    vsprintf.h
+    asprintf.h
   DEPENDS
+    libc.src.__support.arg_list
     libc.src.stdio.printf_core.printf_main
     libc.src.stdio.printf_core.writer
+    libc.src.stdlib.malloc
+    libc.src.stdlib.realloc
 )
 
 add_entrypoint_object(
-  vsnprintf
+  vsprintf
   SRCS
-    vsnprintf.cpp
+    vsprintf.cpp
   HDRS
-    vsnprintf.h
+    vsprintf.h
   DEPENDS
     libc.src.stdio.printf_core.printf_main
     libc.src.stdio.printf_core.writer
 )
 
 add_entrypoint_object(
-  vfprintf
+  vsnprintf
   SRCS
-    vfprintf.cpp
+    vsnprintf.cpp
   HDRS
-    vfprintf.h
+    vsnprintf.h
   DEPENDS
-    libc.src.__support.arg_list
-    libc.src.stdio.printf_core.vfprintf_internal
+    libc.src.stdio.printf_core.printf_main
+    libc.src.stdio.printf_core.writer
 )
 
 add_entrypoint_object(
@@ -204,16 +207,10 @@ add_entrypoint_object(
     vasprintf.h
   DEPENDS
     libc.src.__support.arg_list
-)
-
-add_stdio_entrypoint_object(
-  fileno
-  SRCS
-    fileno.cpp
-  HDRS
-    fileno.h
-  DEPENDS
-    libc.src.stdio.fileno
+    libc.src.stdio.printf_core.printf_main
+    libc.src.stdio.printf_core.writer
+    libc.src.stdlib.malloc
+    libc.src.stdlib.realloc
 )
 
 add_subdirectory(printf_core)
diff --git a/libc/src/stdio/asprintf.cpp b/libc/src/stdio/asprintf.cpp
new file mode 100644
index 0000000000000..157be66f8eb09
--- /dev/null
+++ b/libc/src/stdio/asprintf.cpp
@@ -0,0 +1,25 @@
+//===-- Implementation of asprintf -----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/asprintf.h"
+#include "src/__support/OSUtil/io.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/vasprintf_internal.h"
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, asprintf,
+                   (char **__restrict buffer, const char *format, ...)) {
+  va_list vlist;
+  va_start(vlist, format);
+  int ret = printf_core::vasprintf_internal(buffer, format, vlist);
+  va_end(vlist);
+  return ret;
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdio/asprintf.h b/libc/src/stdio/asprintf.h
new file mode 100644
index 0000000000000..fd2b908db171d
--- /dev/null
+++ b/libc/src/stdio/asprintf.h
@@ -0,0 +1,22 @@
+//===-- Implementation header of asprintf ----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_ASPRINTF_H
+#define LLVM_LIBC_SRC_STDIO_ASPRINTF_H
+
+#include "src/__support/macros/config.h"
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace LIBC_NAMESPACE {
+
+int asprintf(char **__restrict s, const char *format, ...);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDIO_ASPRINTF_H
diff --git a/libc/src/stdio/printf_core/core_structs.h b/libc/src/stdio/printf_core/core_structs.h
index 76d006b813dd1..4c3b81ff018ab 100644
--- a/libc/src/stdio/printf_core/core_structs.h
+++ b/libc/src/stdio/printf_core/core_structs.h
@@ -134,7 +134,7 @@ constexpr int FILE_STATUS_ERROR = -2;
 constexpr int NULLPTR_WRITE_ERROR = -3;
 constexpr int INT_CONVERSION_ERROR = -4;
 constexpr int FIXED_POINT_CONVERSION_ERROR = -5;
-
+constexpr int ALLOCATION_ERROR = -6;
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/printf_core/vasprintf_internal.h b/libc/src/stdio/printf_core/vasprintf_internal.h
new file mode 100644
index 0000000000000..1298b4718df9a
--- /dev/null
+++ b/libc/src/stdio/printf_core/vasprintf_internal.h
@@ -0,0 +1,75 @@
+//===-- Internal Implementation of vasprintf -----------------------------*- C++
+//-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/OSUtil/io.h"
+#include "src/__support/arg_list.h"
+#include "src/stdio/printf.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/printf_main.h"
+#include "src/stdio/printf_core/writer.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h> // malloc, realloc, free
+
+namespace LIBC_NAMESPACE {
+namespace printf_core {
+
+LIBC_INLINE int resize_overflow_hook(cpp::string_view new_str, void *target) {
+  printf_core::WriteBuffer *wb =
+      reinterpret_cast<printf_core::WriteBuffer *>(target);
+  size_t new_size = new_str.size() + wb->buff_cur;
+  const bool isBuffOnStack = (wb->buff == wb->init_buff);
+  char *new_buff = static_cast<char *>(
+      isBuffOnStack ? malloc(new_size + 1)
+                    : realloc(wb->buff, new_size + 1)); // +1 for null
+  if (new_buff == nullptr) {
+    if (wb->buff != wb->init_buff) {
+      free(wb->buff);
+    }
+    return printf_core::ALLOCATION_ERROR;
+  }
+  if (isBuffOnStack)
+    inline_memcpy(new_buff, wb->buff, wb->buff_cur);
+  wb->buff = new_buff;
+  inline_memcpy(wb->buff + wb->buff_cur, new_str.data(), new_str.size());
+  wb->buff_cur = new_size;
+  wb->buff_len = new_size;
+  return printf_core::WRITE_OK;
+}
+
+inline constexpr size_t DEFAULT_BUFFER_SIZE = 200;
+
+LIBC_INLINE int vasprintf_internal(char **ret, const char *format,
+                                   va_list vlist) {
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  char init_buff_on_stack[DEFAULT_BUFFER_SIZE];
+  printf_core::WriteBuffer wb(init_buff_on_stack, DEFAULT_BUFFER_SIZE,
+                              resize_overflow_hook);
+  printf_core::Writer writer(&wb);
+
+  auto ret_val = printf_core::printf_main(&writer, format, args);
+  if (ret_val < 0) {
+    *ret = nullptr;
+    return -1;
+  }
+  if (wb.buff == init_buff_on_stack) {
+    *ret = static_cast<char *>(malloc(ret_val + 1));
+    if (ret == nullptr)
+      return -1;
+    inline_memcpy(*ret, wb.buff, ret_val);
+  } else {
+    *ret = wb.buff;
+  }
+  (*ret)[ret_val] = '\0';
+  return ret_val;
+}
+} // namespace printf_core
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdio/printf_core/writer.h b/libc/src/stdio/printf_core/writer.h
index 89421561f3b2e..b8fb564212f2e 100644
--- a/libc/src/stdio/printf_core/writer.h
+++ b/libc/src/stdio/printf_core/writer.h
@@ -22,60 +22,84 @@ namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
 struct WriteBuffer {
+  enum class WriteMode {
+    FILL_BUFF_AND_DROP_OVERFLOW,
+    FLUSH_TO_STREAM,
+    RESIZE_AND_FILL_BUFF,
+  };
   using StreamWriter = int (*)(cpp::string_view, void *);
+  using OverflowWriter = int (WriteBuffer::*)(cpp::string_view);
   char *buff;
-  const size_t buff_len;
+  const char *init_buff; // for checking when resize.
+  size_t buff_len;
   size_t buff_cur = 0;
 
   // The stream writer will be called when the buffer is full. It will be passed
   // string_views to write to the stream.
   StreamWriter stream_writer;
   void *output_target;
+  WriteMode write_mode;
 
   LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len, StreamWriter hook,
                           void *target)
-      : buff(Buff), buff_len(Buff_len), stream_writer(hook),
-        output_target(target) {}
+      : buff(Buff), init_buff(Buff), buff_len(Buff_len), stream_writer(hook),
+        output_target(target), write_mode(WriteMode::FLUSH_TO_STREAM) {}
 
   LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len)
-      : buff(Buff), buff_len(Buff_len), stream_writer(nullptr),
-        output_target(nullptr) {}
+      : buff(Buff), init_buff(Buff), buff_len(Buff_len), stream_writer(nullptr),
+        output_target(nullptr),
+        write_mode(WriteMode::FILL_BUFF_AND_DROP_OVERFLOW) {}
+
+  LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len, StreamWriter hook)
+      : buff(Buff), init_buff(Buff), buff_len(Buff_len), stream_writer(hook),
+        output_target(this), write_mode(WriteMode::RESIZE_AND_FILL_BUFF) {}
+
+  LIBC_INLINE int flush_to_stream(cpp::string_view new_str) {
+    if (buff_cur > 0) {
+      int retval = stream_writer({buff, buff_cur}, output_target);
+      if (retval < 0)
+        return retval;
+    }
+    if (new_str.size() > 0) {
+      int retval = stream_writer(new_str, output_target);
+      if (retval < 0)
+        return retval;
+    }
+    buff_cur = 0;
+    return WRITE_OK;
+  }
+
+  LIBC_INLINE int fill_remaining_to_buff(cpp::string_view new_str) {
+    if (buff_cur < buff_len) {
+      size_t bytes_to_write = buff_len - buff_cur;
+      if (bytes_to_write > new_str.size()) {
+        bytes_to_write = new_str.size();
+      }
+      inline_memcpy(buff + buff_cur, new_str.data(), bytes_to_write);
+      buff_cur += bytes_to_write;
+    }
+    return WRITE_OK;
+  }
+
+  LIBC_INLINE int resize_and_write(cpp::string_view new_str) {
+    return stream_writer(new_str, output_target);
+  }
 
   // The overflow_write method is intended to be called to write the contents of
-  // the buffer and new_str to the stream_writer if it exists, else it will
-  // write as much of new_str to the buffer as it can. The current position in
-  // the buffer will be reset iff stream_writer is called. Calling this with an
-  // empty string will flush the buffer if relevant.
+  // the buffer and new_str to the stream_writer if it exists. If a resizing
+  // hook is provided, it will resize the buffer and write the contents. If
+  // neither a stream_writer nor a resizing hook is provided, it will fill the
+  // remaining space in the buffer with new_str and drop the overflow. Calling
+  // this with an empty string will flush the buffer if relevant.
+
   LIBC_INLINE int overflow_write(cpp::string_view new_str) {
-    // If there is a stream_writer, write the contents of the buffer, then
-    // new_str, then clear the buffer.
-    if (stream_writer != nullptr) {
-      if (buff_cur > 0) {
-        int retval = stream_writer({buff, buff_cur}, output_target);
-        if (retval < 0) {
-          return retval;
-        }
-      }
-      if (new_str.size() > 0) {
-        int retval = stream_writer(new_str, output_target);
-        if (retval < 0) {
-          return retval;
-        }
-      }
-      buff_cur = 0;
-      return WRITE_OK;
-    } else {
-      // We can't flush to the stream, so fill the rest of the buffer, then drop
-      // the overflow.
-      if (buff_cur < buff_len) {
-        size_t bytes_to_write = buff_len - buff_cur;
-        if (bytes_to_write > new_str.size()) {
-          bytes_to_write = new_str.size();
-        }
-        inline_memcpy(buff + buff_cur, new_str.data(), bytes_to_write);
-        buff_cur += bytes_to_write;
-      }
-      return WRITE_OK;
+    switch (write_mode) {
+    case WriteMode::FILL_BUFF_AND_DROP_OVERFLOW:
+      return fill_remaining_to_buff(new_str);
+    case WriteMode::FLUSH_TO_STREAM:
+      return flush_to_stream(new_str);
+    case WriteMode::RESIZE_AND_FILL_BUFF:
+      return resize_and_write(new_str);
     }
   }
 };
diff --git a/libc/src/stdio/vasprintf.cpp b/libc/src/stdio/vasprintf.cpp
index 712a3f72ab62d..9a3fe8b1b061a 100644
--- a/libc/src/stdio/vasprintf.cpp
+++ b/libc/src/stdio/vasprintf.cpp
@@ -7,20 +7,18 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdio/vasprintf.h"
-
+#include "src/__support/OSUtil/io.h"
 #include "src/__support/arg_list.h"
-
-#include <stdarg.h>
-#include <stdio.h>
+#include "src/stdio/printf_core/vasprintf_internal.h"
 
 namespace LIBC_NAMESPACE {
 
 LLVM_LIBC_FUNCTION(int, vasprintf,
-                   (char **__restrict, const char *, va_list vlist)) {
+                   (char **__restrict ret, const char *format, va_list vlist)) {
   internal::ArgList args(vlist); // This holder class allows for easier copying
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
-  return -1;
+  return printf_core::vasprintf_internal(ret, format, vlist);
 }
 
 } // namespace LIBC_NAMESPACE
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 5eb8c9577893b..c062baee7f8c4 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -191,6 +191,19 @@ add_libc_test(
     libc.src.stdio.printf
 )
 
+add_libc_test(
+   asprintf_test
+   SUITE
+     libc_stdio_unittests
+   SRCS
+     asprintf_test.cpp
+   DEPENDS
+     libc.src.stdio.asprintf
+     libc.src.string.memset
+     libc.include.stdlib
+     libc.src.stdio.sprintf
+ )
+
 add_fp_unittest(
   vsprintf_test
   UNIT_TEST_ONLY
@@ -236,6 +249,18 @@ add_libc_test(
     libc.src.stdio.vprintf
 )
 
+add_libc_test(
+   vasprintf_test
+   SUITE
+     libc_stdio_unittests
+   SRCS
+     vasprintf_test.cpp
+   DEPENDS
+     libc.src.stdio.vasprintf
+     libc.src.string.memset
+     libc.include.stdlib
+     libc.src.stdio.sprintf
+ )
 
 if(LLVM_LIBC_FULL_BUILD)
   # In fullbuild mode, fscanf's tests use the internal FILE for other functions.
diff --git a/libc/test/src/stdio/asprintf_test.cpp b/libc/test/src/stdio/asprintf_test.cpp
new file mode 100644
index 0000000000000..c5a09dc7e0e27
--- /dev/null
+++ b/libc/test/src/stdio/asprintf_test.cpp
@@ -0,0 +1,84 @@
+//===-- Unittests for asprintf--------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/asprintf.h"
+#include "src/stdio/sprintf.h"
+#include "src/string/memset.h"
+#include "test/UnitTest/Test.h"
+#include <iostream>
+
+TEST(LlvmLibcASPrintfTest, SimpleNoConv) {
+  char *buff = nullptr;
+  int written;
+  written =
+      LIBC_NAMESPACE::asprintf(&buff, "A simple string with no conversions.");
+  EXPECT_EQ(written, 36);
+  ASSERT_STREQ(buff, "A simple string with no conversions.");
+  free(buff);
+}
+
+TEST(LlvmLibcASPrintfTest, PercentConv) {
+  char *buff = nullptr;
+  int written;
+
+  written = LIBC_NAMESPACE::asprintf(&buff, "%%");
+  EXPECT_EQ(written, 1);
+  ASSERT_STREQ(buff, "%");
+
+  written = LIBC_NAMESPACE::asprintf(&buff, "abc %% def");
+  EXPECT_EQ(written, 9);
+  ASSERT_STREQ(buff, "abc % def");
+
+  written = LIBC_NAMESPACE::asprintf(&buff, "%%%%%%");
+  EXPECT_EQ(written, 3);
+  ASSERT_STREQ(buff, "%%%");
+  free(buff);
+}
+
+TEST(LlvmLibcASPrintfTest, CharConv) {
+  char *buff = nullptr;
+  int written;
+
+  written = LIBC_NAMESPACE::asprintf(&buff, "%c", 'a');
+  EXPECT_EQ(written, 1);
+  ASSERT_STREQ(buff, "a");
+
+  written = LIBC_NAMESPACE::asprintf(&buff, "%3c %-3c", '1', '2');
+  EXPECT_EQ(written, 7);
+  ASSERT_STREQ(buff, "  1 2  ");
+
+  written = LIBC_NAMESPACE::asprintf(&buff, "%*c", 2, '3');
+  EXPECT_EQ(written, 2);
+  ASSERT_STREQ(buff, " 3");
+  free(buff);
+}
+
+TEST(LlvmLibcASPrintfTest, LargeStringNoConv) {
+  char *buff = nullptr;
+  char long_str[1001];
+  LIBC_NAMESPACE::memset(long_str, 'a', 1000);
+  long_str[1000] = '\0';
+  int written;
+  written = LIBC_NAMESPACE::asprintf(&buff, long_str);
+  EXPECT_EQ(written, 1000);
+  ASSERT_STREQ(buff, long_str);
+  free(buff);
+}
+
+TEST(LlvmLibcASPrintfTest, ManyReAlloc) {
+  char *buff = nullptr;
+  char long_str[1001];
+  auto expected_num_chars =
+      LIBC_NAMESPACE::sprintf(long_str, "%200s%200s%200s", "a", "b", "c");
+  long_str[expected_num_chars] = '\0';
+  int written;
+  written = LIBC_NAMESPACE::asprintf(&buff, long_str);
+  EXPECT_EQ(written, expected_num_chars);
+  ASSERT_STREQ(buff, long_str);
+  free(buff);
+}
diff --git a/libc/test/src/stdio/vasprintf_test.cpp b/libc/test/src/stdio/vasprintf_test.cpp
new file mode 100644
index 0000000000000..b9b41d32d3c96
--- /dev/null
+++ b/libc/test/src/stdio/vasprintf_test.cpp
@@ -0,0 +1,96 @@
+//===-- Unittests for vasprintf--------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/sprintf.h"
+#include "src/stdio/vasprintf.h"
+#include "src/string/memset.h"
+#include "test/UnitTest/Test.h"
+#include <iostream>
+
+int call_vasprintf(char **__restrict buffer, const char *__restrict format,
+                   ...) {
+  va_list vlist;
+  va_start(vlist, format);
+  int ret = LIBC_NAMESPACE::vasprintf(buffer, format, vlist);
+  va_end(vlist);
+  return ret;
+}
+
+TEST(LlvmLibcVASPrintfTest, SimpleNoConv) {
+  char *buff = nullptr;
+  int written;
+  written = call_vasprintf(&buff, "A simple string with no conversions.");
+  EXPECT_EQ(written, 36);
+  ASSERT_STREQ(buff, "A simple string with no conversions.");
+  free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, PercentConv) {
+  char *buff = nullptr;
+  int written;
+
+  written = call_vasprintf(&buff, "%%");
+  EXPECT_EQ(written, 1);
+  ASSERT_STREQ(buff, "%");
+
+  written = call_vasprintf(&buff, "abc %% def");
+  EXPECT_EQ(written, 9);
+  ASSERT_STREQ(buff, "abc % def");
+
+  written = call_vasprintf(&buff, "%%%%%%");
+  EXPECT_EQ(written, 3);
+  ASSERT_STREQ(buff, "%%%");
+  free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, CharConv) {
+  char *buff = nullptr;
+  int written;
+
+  written = call_vasprintf(&buff, "%c", 'a');
+  EXPECT_EQ(written, 1);
+  ASSERT_STREQ(buff, "a");
+
+  written = call_vasprintf(&buff, "%3c %-3c", '1', '2');
+  EXPECT_EQ(written, 7);
+  ASSERT_STREQ(buff, "  1 2  ");
+
+  written = call_vasprintf(&buff, "%*c", 2, '3');
+  EXPECT_EQ(written, 2);
+  ASSERT_STREQ(buff, " 3");
+  free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, LargeStringNoConv) {
+  char *buff = nullptr;
+  char long_str[1001];
+  LIBC_NAMESPACE::memset(long_str, 'a', 1000);
+  long_str[1000] = '\0';
+  int written;
+  written = call_vasprintf(&buff, long_str);
+  EXPECT_EQ(written, 1000);
+  ASSERT_STREQ(buff, long_str);
+  free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, ManyReAlloc) {
+  char *buff = nullptr;
+  const int expected_num_chars = 600;
+  int written = call_vasprintf(&buff, "%200s%200s%200s", "", "", "");
+  EXPECT_EQ(written, expected_num_chars);
+
+  bool isPadding = true;
+  for (int i = 0; i < expected_num_chars; i++) {
+    if (buff[i] != ' ') {
+      isPadding = false;
+      break;
+    }
+  }
+  EXPECT_TRUE(isPadding);
+  free(buff);
+}



More information about the libc-commits mailing list