[libc] Add Annex K strnlen_s function (#186112)
This patch adds the `strnlen_s` function from Annex K. In order to reduce duplication between `strnlen` and `strnlen_s`, the common logic has been extracted to a new internal function which both now call. In addition to the function definition, the patch adds a unit test and a fuzzing test.
This commit is contained in:
@@ -87,6 +87,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -87,6 +87,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -87,6 +87,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -46,6 +46,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncmp
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strspn
|
||||
|
||||
@@ -45,6 +45,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncmp
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strspn
|
||||
|
||||
@@ -65,6 +65,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -65,6 +65,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -62,6 +62,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -79,6 +79,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -49,6 +49,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncmp
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -79,6 +79,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -81,6 +81,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strndup
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strsep
|
||||
|
||||
@@ -43,6 +43,7 @@ set(TARGET_LIBC_ENTRYPOINTS
|
||||
libc.src.string.strncmp
|
||||
libc.src.string.strncpy
|
||||
libc.src.string.strnlen
|
||||
libc.src.string.strnlen_s
|
||||
libc.src.string.strpbrk
|
||||
libc.src.string.strrchr
|
||||
libc.src.string.strspn
|
||||
|
||||
@@ -48,3 +48,11 @@ add_libc_fuzzer(
|
||||
DEPENDS
|
||||
libc.src.string.strlen
|
||||
)
|
||||
|
||||
add_libc_fuzzer(
|
||||
strnlen_s_differential_fuzz
|
||||
SRCS
|
||||
strnlen_s_differential_fuzz.cpp
|
||||
DEPENDS
|
||||
libc.src.string.strnlen_s
|
||||
)
|
||||
|
||||
59
libc/fuzzing/string/strnlen_s_differential_fuzz.cpp
Normal file
59
libc/fuzzing/string/strnlen_s_differential_fuzz.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
//===-- strnlen_s_differential_fuzz.cpp -----------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
///
|
||||
/// Differential fuzz test for llvm-libc strnlen_s implementation.
|
||||
///
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#define __STDC_WANT_LIB_EXT1__ 1
|
||||
|
||||
#include "src/string/strnlen_s.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
extern "C" size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size);
|
||||
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size,
|
||||
size_t max_size,
|
||||
unsigned int /*seed*/) {
|
||||
// The buffer is constructed as follows:
|
||||
// data = max_len (size_t) + null-terminated string
|
||||
if (max_size < sizeof(size_t) + 1)
|
||||
return size;
|
||||
|
||||
do {
|
||||
size = LLVMFuzzerMutate(data, size, max_size);
|
||||
} while (size < sizeof(size_t) + 1);
|
||||
|
||||
data[size - 1] = '\0';
|
||||
return size;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
if (size < sizeof(size_t) + 1)
|
||||
return 0;
|
||||
|
||||
size_t max_len;
|
||||
::memcpy(&max_len, data, sizeof(size_t));
|
||||
data += sizeof(size_t);
|
||||
|
||||
// If Annex K is not available in the system's C library, we compare against
|
||||
// strnlen instead. We can assume this is valid because in the case where the
|
||||
// input string is not null, the two functions must have identical semantics.
|
||||
#ifdef __STDC_LIB_EXT1__
|
||||
size_t ref = ::strnlen_s(reinterpret_cast<const char *>(data), max_len);
|
||||
#else
|
||||
size_t ref = ::strnlen(reinterpret_cast<const char *>(data), max_len);
|
||||
#endif
|
||||
size_t impl =
|
||||
LIBC_NAMESPACE::strnlen_s(reinterpret_cast<const char *>(data), max_len);
|
||||
|
||||
if (ref != impl)
|
||||
__builtin_trap();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -265,6 +265,7 @@ add_header_macro(
|
||||
string.h
|
||||
DEPENDS
|
||||
.llvm_libc_common_h
|
||||
.llvm-libc-macros.annex_k_macros
|
||||
.llvm-libc-macros.null_macro
|
||||
.llvm-libc-types.errno_t
|
||||
.llvm-libc-types.rsize_t
|
||||
|
||||
@@ -4,6 +4,8 @@ standards:
|
||||
macros:
|
||||
- macro_name: "NULL"
|
||||
macro_header: null-macro.h
|
||||
- macro_name: LIBC_HAS_ANNEX_K
|
||||
macro_header: annex-k-macros.h
|
||||
types:
|
||||
- type_name: errno_t
|
||||
- type_name: locale_t
|
||||
@@ -251,6 +253,14 @@ functions:
|
||||
arguments:
|
||||
- type: const char *
|
||||
- type: size_t
|
||||
- name: strnlen_s
|
||||
standards:
|
||||
- stdc
|
||||
return_type: size_t
|
||||
arguments:
|
||||
- type: const char *
|
||||
- type: size_t
|
||||
guard: LIBC_HAS_ANNEX_K
|
||||
- name: strpbrk
|
||||
standards:
|
||||
- stdc
|
||||
|
||||
@@ -302,6 +302,19 @@ add_entrypoint_object(
|
||||
.string_utils
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
strnlen_s
|
||||
SRCS
|
||||
strnlen_s.cpp
|
||||
HDRS
|
||||
strnlen_s.h
|
||||
DEPENDS
|
||||
libc.hdr.types.size_t
|
||||
.string_utils
|
||||
libc.src.__support.common
|
||||
libc.src.__support.macros.config
|
||||
)
|
||||
|
||||
add_entrypoint_object(
|
||||
strpbrk
|
||||
SRCS
|
||||
|
||||
@@ -124,6 +124,12 @@ LIBC_INLINE void *find_first_character(const unsigned char *src,
|
||||
return find_first_character_impl(src, ch, max_strlen);
|
||||
}
|
||||
|
||||
LIBC_INLINE size_t strnlen(const char *s, size_t max_len) {
|
||||
const void *temp = internal::find_first_character(
|
||||
reinterpret_cast<const unsigned char *>(s), '\0', max_len);
|
||||
return temp ? reinterpret_cast<const char *>(temp) - s : max_len;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
LLVM_LIBC_FUNCTION(size_t, strnlen, (const char *src, size_t n)) {
|
||||
const void *temp = internal::find_first_character(
|
||||
reinterpret_cast<const unsigned char *>(src), '\0', n);
|
||||
return temp ? reinterpret_cast<const char *>(temp) - src : n;
|
||||
return internal::strnlen(src, n);
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
|
||||
21
libc/src/string/strnlen_s.cpp
Normal file
21
libc/src/string/strnlen_s.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
//===-- Implementation of strnlen_s ------------------------------*- 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/string/strnlen_s.h"
|
||||
#include "hdr/types/size_t.h"
|
||||
#include "src/__support/common.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
#include "src/string/string_utils.h"
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
LLVM_LIBC_FUNCTION(size_t, strnlen_s, (const char *s, size_t n)) {
|
||||
return (s != 0) ? internal::strnlen(s, n) : 0;
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
21
libc/src/string/strnlen_s.h
Normal file
21
libc/src/string/strnlen_s.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//===-- Implementation header for strnlen_s ----------------------*- 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_STRNLEN_S_H
|
||||
#define LLVM_LIBC_SRC_STRING_STRNLEN_S_H
|
||||
|
||||
#include "hdr/types/size_t.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
size_t strnlen_s(const char *src, size_t n);
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
|
||||
#endif // LLVM_LIBC_SRC_STRING_STRNLEN_S_H
|
||||
@@ -274,6 +274,16 @@ add_libc_test(
|
||||
libc.src.string.strnlen
|
||||
)
|
||||
|
||||
add_libc_test(
|
||||
strnlen_s_test
|
||||
SUITE
|
||||
libc-string-tests
|
||||
SRCS
|
||||
strnlen_s_test.cpp
|
||||
DEPENDS
|
||||
libc.src.string.strnlen_s
|
||||
)
|
||||
|
||||
add_libc_test(
|
||||
strpbrk_test
|
||||
SUITE
|
||||
|
||||
57
libc/test/src/string/strnlen_s_test.cpp
Normal file
57
libc/test/src/string/strnlen_s_test.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
//===-- Unittests for strnlen_s -------------------------------------------===//
|
||||
//
|
||||
// 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/strnlen_s.h"
|
||||
#include "test/UnitTest/Test.h"
|
||||
#include <stddef.h>
|
||||
|
||||
TEST(LlvmLibcStrNLenSTest, NullPointerInput) {
|
||||
const char *str = nullptr;
|
||||
// If the string input is a null pointer, it should return 0 regardless of
|
||||
// the max len arg value.
|
||||
ASSERT_EQ(LIBC_NAMESPACE::strnlen_s(str, 0), size_t(0));
|
||||
ASSERT_EQ(LIBC_NAMESPACE::strnlen_s(str, 1), size_t(0));
|
||||
}
|
||||
|
||||
// The semantics when the string input is not null are the same as strnlen. The
|
||||
// following tests are copied from the latter's tests.
|
||||
|
||||
TEST(LlvmLibcStrNLenSTest, EmptyString) {
|
||||
const char *empty = "";
|
||||
ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(empty, 0));
|
||||
// If N is greater than string length, this should still return 0.
|
||||
ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(empty, 1));
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStrNLenSTest, OneCharacterString) {
|
||||
const char *single = "X";
|
||||
ASSERT_EQ(static_cast<size_t>(1), LIBC_NAMESPACE::strnlen_s(single, 1));
|
||||
// If N is zero, this should return 0.
|
||||
ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(single, 0));
|
||||
// If N is greater than string length, this should still return 1.
|
||||
ASSERT_EQ(static_cast<size_t>(1), LIBC_NAMESPACE::strnlen_s(single, 2));
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStrNLenSTest, ManyCharacterString) {
|
||||
const char *many = "123456789";
|
||||
ASSERT_EQ(static_cast<size_t>(9), LIBC_NAMESPACE::strnlen_s(many, 9));
|
||||
// If N is smaller than the string length, it should return N.
|
||||
ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(many, 3));
|
||||
// If N is zero, this should return 0.
|
||||
ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(many, 0));
|
||||
// If N is greater than the string length, this should still return 9.
|
||||
ASSERT_EQ(static_cast<size_t>(9), LIBC_NAMESPACE::strnlen_s(many, 42));
|
||||
}
|
||||
|
||||
TEST(LlvmLibcStrNLenSTest, CharactersAfterNullTerminatorShouldNotBeIncluded) {
|
||||
const char str[5] = {'a', 'b', 'c', '\0', 'd'};
|
||||
ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(str, 3));
|
||||
// This should only read up to the null terminator.
|
||||
ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(str, 4));
|
||||
ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(str, 5));
|
||||
}
|
||||
@@ -66,6 +66,9 @@ functions:
|
||||
strndup:
|
||||
c-definition: 7.26.2.7
|
||||
in-latest-posix: ''
|
||||
strnlen_s:
|
||||
c-definition: K.3.7.4.4
|
||||
in-latest-posix: ''
|
||||
strpbrk:
|
||||
c-definition: 7.26.5.5
|
||||
in-latest-posix: ''
|
||||
|
||||
Reference in New Issue
Block a user