Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libc/fuzzing/__support/freelist_heap_fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ asm(R"(
_end:
.fill 1024
__llvm_libc_heap_limit:
)";
)");

using LIBC_NAMESPACE::FreeListHeap;
using LIBC_NAMESPACE::inline_memset;
Expand Down
8 changes: 8 additions & 0 deletions libc/fuzzing/string/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ add_libc_fuzzer(
DEPENDS
libc.src.strings.bcmp
)

add_libc_fuzzer(
strlen_fuzz
SRCS
strlen_fuzz.cpp
DEPENDS
libc.src.string.strlen
)
32 changes: 32 additions & 0 deletions libc/fuzzing/string/strlen_fuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//===-- strlen_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
//
//===----------------------------------------------------------------------===//
///
/// Fuzzing test for llvm-libc strlen implementation.
///
//===----------------------------------------------------------------------===//

#include "src/string/strlen.h"
#include <cstdint>
#include <cstring>

// always null terminate the data
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) {
size = LLVMFuzzerMutate(data, size, max_size);
data[size - 1] = '\0';
return size;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
size_t ref = ::strlen(reinterpret_cast<const char *>(data));
size_t impl = LIBC_NAMESPACE::strlen(reinterpret_cast<const char *>(data));
if (ref != impl)
__builtin_trap();
return 0;
}
63 changes: 58 additions & 5 deletions libc/src/string/memory_utils/aarch64/inline_strlen.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
#ifndef LLVM_LIBC_SRC_STRING_MEMORY_UTILS_AARCH64_INLINE_STRLEN_H
#define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_AARCH64_INLINE_STRLEN_H

#include "src/__support/macros/properties/cpu_features.h"

#if defined(__ARM_NEON)
#include "src/__support/CPP/bit.h" // countr_zero

#include <arm_neon.h>
#include <stddef.h> // size_t

namespace LIBC_NAMESPACE_DECL {

namespace neon {
[[maybe_unused]] LIBC_NO_SANITIZE_OOB_ACCESS LIBC_INLINE static size_t
string_length(const char *src) {
Expand Down Expand Up @@ -45,9 +44,63 @@ string_length(const char *src) {
}
}
} // namespace neon
} // namespace LIBC_NAMESPACE_DECL
#endif // __ARM_NEON

namespace string_length_impl = neon;
#ifdef LIBC_TARGET_CPU_HAS_SVE
#include "src/__support/macros/optimization.h"
#include <arm_sve.h>
namespace LIBC_NAMESPACE_DECL {
namespace sve {
[[maybe_unused]] LIBC_INLINE static size_t string_length(const char *src) {
const uint8_t *ptr = reinterpret_cast<const uint8_t *>(src);
// Initialize the first-fault register to all true
svsetffr();
const svbool_t all_true = svptrue_b8(); // all true predicate
svbool_t cmp_zero;
size_t len = 0;

for (;;) {
// Read a vector's worth of bytes, stopping on first fault.
svuint8_t data = svldff1_u8(all_true, &ptr[len]);
svbool_t fault_mask = svrdffr_z(all_true);
bool has_no_fault = svptest_last(all_true, fault_mask);
if (LIBC_LIKELY(has_no_fault)) {
// First fault did not fail: the whole vector is valid.
// Avoid depending on the contents of FFR beyond the branch.
len += svcntb(); // speculative increment
cmp_zero = svcmpeq_n_u8(all_true, data, 0);
bool has_no_zero = !svptest_any(all_true, cmp_zero);
if (LIBC_LIKELY(has_no_zero))
continue;
len -= svcntb(); // undo speculative increment
break;
} else {
// First fault failed: only some of the vector is valid.
// Perform the comparison only on the valid bytes.
cmp_zero = svcmpeq_n_u8(fault_mask, data, 0);
bool has_zero = svptest_any(fault_mask, cmp_zero);
if (LIBC_LIKELY(has_zero))
break;
svsetffr();
len += svcntp_b8(all_true, fault_mask);
continue;
}
}
// Select the bytes before the first and count them.
svbool_t before_zero = svbrkb_z(all_true, cmp_zero);
len += svcntp_b8(all_true, before_zero);
return len;
}
} // namespace sve
} // namespace LIBC_NAMESPACE_DECL
#endif // LIBC_TARGET_CPU_HAS_SVE

namespace LIBC_NAMESPACE_DECL {
#ifdef LIBC_TARGET_CPU_HAS_SVE
namespace string_length_impl = sve;
#elif defined(__ARM_NEON)
namespace string_length_impl = neon;
#endif
} // namespace LIBC_NAMESPACE_DECL
#endif // __ARM_NEON
#endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_AARCH64_INLINE_STRLEN_H
12 changes: 12 additions & 0 deletions libc/test/src/string/strlen_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ TEST(LlvmLibcStrLenTest, AnyString) {
size_t result = LIBC_NAMESPACE::strlen(any);
ASSERT_EQ((size_t)12, result);
}

TEST(LlvmLibcStrLenTest, DataAfterNulString) {
constexpr char A[10] = {'a', 'b', 'c', 'd', 'e', 'f', 0, 'h', 'i', 'j'};
size_t result = LIBC_NAMESPACE::strlen(A);
ASSERT_EQ((size_t)6, result);
}

TEST(LlvmLibcStrLenTest, MultipleNulsInOneWord) {
constexpr char A[10] = {'a', 'b', 0, 'd', 'e', 'f', 0, 'h', 'i', 'j'};
size_t result = LIBC_NAMESPACE::strlen(A);
ASSERT_EQ((size_t)2, result);
}
Loading