diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -35,6 +35,7 @@ libc.src.string.strcpy libc.src.string.strcspn libc.src.string.strlen + libc.src.string.strncat libc.src.string.strncmp libc.src.string.strncpy libc.src.string.strnlen diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt --- a/libc/src/string/CMakeLists.txt +++ b/libc/src/string/CMakeLists.txt @@ -95,6 +95,17 @@ libc.include.string ) +add_entrypoint_object( + strncat + SRCS + strncat.cpp + HDRS + strncat.h + DEPENDS + .strncpy + .string_utils +) + add_entrypoint_object( strncmp SRCS diff --git a/libc/src/string/strcat.cpp b/libc/src/string/strcat.cpp --- a/libc/src/string/strcat.cpp +++ b/libc/src/string/strcat.cpp @@ -16,7 +16,10 @@ LLVM_LIBC_FUNCTION(char *, strcat, (char *__restrict dest, const char *__restrict src)) { - __llvm_libc::strcpy(dest + internal::string_length(dest), src); + size_t destLength = internal::string_length(dest); + size_t srcLength = internal::string_length(src); + __llvm_libc::strcpy(dest + destLength, src); + dest[destLength + srcLength] = '\0'; return dest; } diff --git a/libc/src/string/strncat.h b/libc/src/string/strncat.h new file mode 100644 --- /dev/null +++ b/libc/src/string/strncat.h @@ -0,0 +1,20 @@ +//===-- Implementation header for strncat -----------------------*- 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_STRING_STRNCAT_H +#define LLVM_LIBC_SRC_STRING_STRNCAT_H + +#include + +namespace __llvm_libc { + +char *strncat(char *__restrict dest, const char *__restrict src, size_t count); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STRING_STRNCAT_H diff --git a/libc/src/string/strcat.cpp b/libc/src/string/strncat.cpp copy from libc/src/string/strcat.cpp copy to libc/src/string/strncat.cpp --- a/libc/src/string/strcat.cpp +++ b/libc/src/string/strncat.cpp @@ -1,4 +1,4 @@ -//===-- Implementation of strcat ------------------------------------------===// +//===-- Implementation of strncat -----------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,17 +6,22 @@ // //===----------------------------------------------------------------------===// -#include "src/string/strcat.h" -#include "src/string/strcpy.h" +#include "src/string/strncat.h" #include "src/string/string_utils.h" +#include "src/string/strncpy.h" #include "src/__support/common.h" namespace __llvm_libc { -LLVM_LIBC_FUNCTION(char *, strcat, - (char *__restrict dest, const char *__restrict src)) { - __llvm_libc::strcpy(dest + internal::string_length(dest), src); +LLVM_LIBC_FUNCTION(char *, strncat, + (char *__restrict dest, const char *__restrict src, + size_t count)) { + size_t srcLength = internal::string_length(src); + size_t copyAmount = srcLength > count ? count : srcLength; + size_t destLength = internal::string_length(dest); + __llvm_libc::strncpy(dest + destLength, src, copyAmount); + dest[destLength + copyAmount] = '\0'; return dest; } diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt --- a/libc/test/src/string/CMakeLists.txt +++ b/libc/test/src/string/CMakeLists.txt @@ -82,6 +82,16 @@ libc.src.string.strlen ) +add_libc_unittest( + strncat_test + SUITE + libc_string_unittests + SRCS + strncat_test.cpp + DEPENDS + libc.src.string.strncat +) + add_libc_unittest( strncmp_test SUITE diff --git a/libc/test/src/string/strncat_test.cpp b/libc/test/src/string/strncat_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/string/strncat_test.cpp @@ -0,0 +1,76 @@ +//===-- Unittests for strncat ---------------------------------------------===// +// +// 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/string/strncat.h" +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcStrNCatTest, EmptyDest) { + const char *abc = "abc"; + char dest[4]; + + dest[0] = '\0'; + + // Start by copying nothing + char *result = __llvm_libc::strncat(dest, abc, 0); + ASSERT_EQ(dest, result); + ASSERT_EQ(dest[0], '\0'); + + // Then copy part of it. + result = __llvm_libc::strncat(dest, abc, 1); + ASSERT_EQ(dest, result); + ASSERT_STREQ(dest, "a"); + + // Reset for the last test. + dest[0] = '\0'; + + // Then copy all of it. + result = __llvm_libc::strncat(dest, abc, 3); + ASSERT_EQ(dest, result); + ASSERT_STREQ(dest, result); + ASSERT_STREQ(dest, abc); +} + +TEST(LlvmLibcStrNCatTest, NonEmptyDest) { + const char *abc = "abc"; + char dest[7]; + + dest[0] = 'x'; + dest[1] = 'y'; + dest[2] = 'z'; + dest[3] = '\0'; + + // Copy only part of the string onto the end + char *result = __llvm_libc::strncat(dest, abc, 1); + ASSERT_EQ(dest, result); + ASSERT_STREQ(dest, "xyza"); + + // Copy a bit more, but without resetting. + result = __llvm_libc::strncat(dest, abc, 2); + ASSERT_EQ(dest, result); + ASSERT_STREQ(dest, "xyzaab"); + + // Set just the end marker, to make sure it overwrites properly. + dest[3] = '\0'; + + result = __llvm_libc::strncat(dest, abc, 3); + ASSERT_EQ(dest, result); + ASSERT_STREQ(dest, "xyzabc"); + + // Check that copying still works when count > src length + dest[0] = '\0'; + // And that it doesn't write beyond what is necessary. + dest[4] = 'Z'; + result = __llvm_libc::strncat(dest, abc, 4); + ASSERT_EQ(dest, result); + ASSERT_STREQ(dest, "abc"); + ASSERT_EQ(dest[4], 'Z'); + + result = __llvm_libc::strncat(dest, abc, 5); + ASSERT_EQ(dest, result); + ASSERT_STREQ(dest, "abcabc"); +}