[libc-commits] [libc] a5e67fb - [libc] Implement vasprintf and asprintf (#98824)
via libc-commits
libc-commits at lists.llvm.org
Thu Aug 1 09:52:44 PDT 2024
Author: Tsz Chan
Date: 2024-08-01T11:52:36-05:00
New Revision: a5e67fba8abb35211aebb945d9d07ac988b80cf5
URL: https://github.com/llvm/llvm-project/commit/a5e67fba8abb35211aebb945d9d07ac988b80cf5
DIFF: https://github.com/llvm/llvm-project/commit/a5e67fba8abb35211aebb945d9d07ac988b80cf5.diff
LOG: [libc] Implement vasprintf and asprintf (#98824)
[libc] Implement vasprintf and asprintf
---------
Co-authored-by: Izaak Schroeder <izaak.schroeder at gmail.com>
Added:
libc/src/stdio/asprintf.cpp
libc/src/stdio/asprintf.h
libc/src/stdio/printf_core/vasprintf_internal.h
libc/src/stdio/vasprintf.cpp
libc/src/stdio/vasprintf.h
libc/test/src/stdio/asprintf_test.cpp
libc/test/src/stdio/vasprintf_test.cpp
Modified:
libc/config/baremetal/arm/entrypoints.txt
libc/config/baremetal/riscv/entrypoints.txt
libc/config/darwin/arm/entrypoints.txt
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/riscv/entrypoints.txt
libc/config/linux/x86_64/entrypoints.txt
libc/newhdrgen/yaml/stdio.yaml
libc/spec/stdc.td
libc/src/stdio/CMakeLists.txt
libc/src/stdio/printf_core/CMakeLists.txt
libc/src/stdio/printf_core/core_structs.h
libc/src/stdio/printf_core/writer.h
libc/test/src/stdio/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index 8025ac09b9f82..ef145e994a3d1 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 fb0308c953746..be41f9a13aac2 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 13280d2dd56d4..d09b4e34b951c 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 1cb357fa5ea59..ff0bf0ea345d3 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 60b5654c597a6..8443ef417b479 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -216,12 +216,14 @@ 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.vsscanf
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/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index a577bfa635b9c..f737cca7f15b6 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -216,12 +216,14 @@ 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.vsscanf
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/newhdrgen/yaml/stdio.yaml b/libc/newhdrgen/yaml/stdio.yaml
index 660087e20b0cc..f0acf5338b1d6 100644
--- a/libc/newhdrgen/yaml/stdio.yaml
+++ b/libc/newhdrgen/yaml/stdio.yaml
@@ -97,6 +97,22 @@ functions:
arguments:
- type: const char *__restrict
- type: va_list
+ - name: asprintf
+ standards:
+ - GNUExtensions
+ return_type: int
+ arguments:
+ - type: char **__restrict
+ - type: const char *__restrict
+ - type: ...
+ - name: vasprintf
+ standards:
+ - GNUExtensions
+ return_type: int
+ arguments:
+ - type: char **__restrict
+ - type: const char *__restrict
+ - type: va_list
- name: sscanf
standards:
- stdc
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index fa536b220ed43..559726ff35499 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -971,6 +971,13 @@ def StdC : StandardSpec<"stdc"> {
ArgSpec<ConstCharRestrictedPtr>,
ArgSpec<VarArgType>]
>,
+ FunctionSpec<
+ "asprintf",
+ RetValSpec<IntType>,
+ [ArgSpec<CharRestrictedPtrPtr>,
+ ArgSpec<ConstCharRestrictedPtr>,
+ ArgSpec<VarArgType>]
+ >,
FunctionSpec<
"vsprintf",
RetValSpec<IntType>,
@@ -1004,6 +1011,13 @@ def StdC : StandardSpec<"stdc"> {
RetValSpec<IntType>,
[ArgSpec<IntType>, ArgSpec<FILEPtr>]
>,
+ FunctionSpec<
+ "vasprintf",
+ RetValSpec<IntType>,
+ [ArgSpec<CharRestrictedPtrPtr>,
+ ArgSpec<ConstCharRestrictedPtr>,
+ ArgSpec<VaListType>]
+ >,
],
[
ObjectSpec<
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 94f92351e92fa..bc5ef5fe0e9b4 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -175,6 +175,16 @@ add_entrypoint_object(
libc.src.stdio.printf_core.writer
)
+add_entrypoint_object(
+ asprintf
+ SRCS
+ asprintf.cpp
+ HDRS
+ asprintf.h
+ DEPENDS
+ libc.src.stdio.printf_core.vasprintf_internal
+)
+
add_entrypoint_object(
vsprintf
SRCS
@@ -197,6 +207,16 @@ add_entrypoint_object(
libc.src.stdio.printf_core.writer
)
+add_entrypoint_object(
+ vasprintf
+ SRCS
+ vasprintf.cpp
+ HDRS
+ vasprintf.h
+ DEPENDS
+ libc.src.stdio.printf_core.vasprintf_internal
+)
+
add_subdirectory(printf_core)
add_subdirectory(scanf_core)
diff --git a/libc/src/stdio/asprintf.cpp b/libc/src/stdio/asprintf.cpp
new file mode 100644
index 0000000000000..88b458a9e103b
--- /dev/null
+++ b/libc/src/stdio/asprintf.cpp
@@ -0,0 +1,28 @@
+//===-- 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/arg_list.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);
+ internal::ArgList args(vlist); // This holder class allows for easier copying
+ // and pointer semantics, as well as handling
+ // destruction automatically.
+ va_end(vlist);
+ int ret = printf_core::vasprintf_internal(buffer, format, args);
+ 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/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index 21ff0d43ab728..bbd693bc8a2ab 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -132,3 +132,15 @@ add_header_library(
libc.src.stdio.printf_core.writer
${use_system_file}
)
+
+add_header_library(
+ vasprintf_internal
+ HDRS
+ vasprintf_internal.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
+)
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..24ebc02a0b33f
--- /dev/null
+++ b/libc/src/stdio/printf_core/vasprintf_internal.h
@@ -0,0 +1,67 @@
+//===-- Internal 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/__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 <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;
+}
+
+constexpr size_t DEFAULT_BUFFER_SIZE = 200;
+
+LIBC_INLINE int vasprintf_internal(char **ret, const char *format,
+ internal::ArgList args) {
+ 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 printf_core::ALLOCATION_ERROR;
+ 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..7dfdea2c5bc40 100644
--- a/libc/src/stdio/printf_core/writer.h
+++ b/libc/src/stdio/printf_core/writer.h
@@ -22,60 +22,83 @@ 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 *);
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
new file mode 100644
index 0000000000000..7fa4cc6f127dd
--- /dev/null
+++ b/libc/src/stdio/vasprintf.cpp
@@ -0,0 +1,23 @@
+//===-- 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 "src/stdio/printf_core/vasprintf_internal.h"
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, vasprintf,
+ (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 printf_core::vasprintf_internal(ret, format, args);
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdio/vasprintf.h b/libc/src/stdio/vasprintf.h
new file mode 100644
index 0000000000000..792e948cf1850
--- /dev/null
+++ b/libc/src/stdio/vasprintf.h
@@ -0,0 +1,20 @@
+//===-- 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>
+
+namespace LIBC_NAMESPACE {
+
+int vasprintf(char **__restrict s, const char *format, va_list vlist);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDIO_VASPRINTF_H
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 4ac83ec2dd600..8b05b928a0269 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..9292cebb80e24
--- /dev/null
+++ b/libc/test/src/stdio/asprintf_test.cpp
@@ -0,0 +1,87 @@
+//===-- 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"
+
+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, "%");
+ free(buff);
+
+ written = LIBC_NAMESPACE::asprintf(&buff, "abc %% def");
+ EXPECT_EQ(written, 9);
+ ASSERT_STREQ(buff, "abc % def");
+ free(buff);
+
+ 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");
+ free(buff);
+
+ written = LIBC_NAMESPACE::asprintf(&buff, "%3c %-3c", '1', '2');
+ EXPECT_EQ(written, 7);
+ ASSERT_STREQ(buff, " 1 2 ");
+ free(buff);
+
+ 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..2eb1be3d7a9bf
--- /dev/null
+++ b/libc/test/src/stdio/vasprintf_test.cpp
@@ -0,0 +1,99 @@
+//===-- 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"
+
+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, "%");
+ free(buff);
+
+ written = call_vasprintf(&buff, "abc %% def");
+ EXPECT_EQ(written, 9);
+ ASSERT_STREQ(buff, "abc % def");
+ free(buff);
+
+ 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");
+ free(buff);
+
+ written = call_vasprintf(&buff, "%3c %-3c", '1', '2');
+ EXPECT_EQ(written, 7);
+ ASSERT_STREQ(buff, " 1 2 ");
+ free(buff);
+
+ 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