diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt index 8025ac09b9f821..ef145e994a3d1e 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 fb0308c9537460..be41f9a13aac21 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 13280d2dd56d4c..d09b4e34b951cd 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 1cb357fa5ea59f..ff0bf0ea345d36 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 60b5654c597a60..8443ef417b4790 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 a577bfa635b9cd..f737cca7f15b6f 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 660087e20b0ccf..f0acf5338b1d63 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 fa536b220ed43a..559726ff354999 100644 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -971,6 +971,13 @@ def StdC : StandardSpec<"stdc"> { ArgSpec, ArgSpec] >, + FunctionSpec< + "asprintf", + RetValSpec, + [ArgSpec, + ArgSpec, + ArgSpec] + >, FunctionSpec< "vsprintf", RetValSpec, @@ -1004,6 +1011,13 @@ def StdC : StandardSpec<"stdc"> { RetValSpec, [ArgSpec, ArgSpec] >, + FunctionSpec< + "vasprintf", + RetValSpec, + [ArgSpec, + ArgSpec, + ArgSpec] + >, ], [ ObjectSpec< diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt index 94f92351e92fa2..bc5ef5fe0e9b48 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 00000000000000..88b458a9e103bf --- /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 00000000000000..fd2b908db171df --- /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 +#include + +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 21ff0d43ab7287..bbd693bc8a2abd 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 76d006b813dd1e..4c3b81ff018ab9 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 00000000000000..24ebc02a0b33f2 --- /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 // 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(target); + size_t new_size = new_str.size() + wb->buff_cur; + const bool isBuffOnStack = (wb->buff == wb->init_buff); + char *new_buff = static_cast( + 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(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 89421561f3b2ed..7dfdea2c5bc400 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 00000000000000..7fa4cc6f127dda --- /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 00000000000000..792e948cf1850c --- /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 + +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 4ac83ec2dd600f..8b05b928a02695 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 00000000000000..9292cebb80e245 --- /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 00000000000000..2eb1be3d7a9bfd --- /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); +}