diff options
| author | Peter Collingbourne <peter@pcc.me.uk> | 2026-01-29 14:39:34 -0800 |
|---|---|---|
| committer | Peter Collingbourne <peter@pcc.me.uk> | 2026-01-29 14:39:34 -0800 |
| commit | 7b3f189a1369f9348c007730ddea953b1e68acb1 (patch) | |
| tree | 7db8969ee8a34a10b6c8ae033c939c9d653376f6 /libc/test | |
| parent | f3d6dae13ae710323a2ddbaf87af71b1abcbfada (diff) | |
| parent | 0893b70ecfc4f4aca0a20a078476d191edc1e623 (diff) | |
| download | llvm-users/pcc/spr/codegen-introduce-machinefunctiongetpreferredalignment.zip llvm-users/pcc/spr/codegen-introduce-machinefunctiongetpreferredalignment.tar.gz llvm-users/pcc/spr/codegen-introduce-machinefunctiongetpreferredalignment.tar.bz2 | |
Created using spr 1.3.6-beta.1
Diffstat (limited to 'libc/test')
| -rw-r--r-- | libc/test/shared/CMakeLists.txt | 35 | ||||
| -rw-r--r-- | libc/test/shared/shared_math_test.cpp | 44 | ||||
| -rw-r--r-- | libc/test/src/__support/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | libc/test/src/__support/weak_avl_test.cpp | 274 | ||||
| -rw-r--r-- | libc/test/src/stdio/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | libc/test/src/stdio/sprintf_test.cpp | 98 | ||||
| -rw-r--r-- | libc/test/src/strings/CMakeLists.txt | 15 | ||||
| -rw-r--r-- | libc/test/src/strings/wide_read_memory_test.cpp | 101 |
8 files changed, 586 insertions, 1 deletions
diff --git a/libc/test/shared/CMakeLists.txt b/libc/test/shared/CMakeLists.txt index c5955ec..b0a5ad7 100644 --- a/libc/test/shared/CMakeLists.txt +++ b/libc/test/shared/CMakeLists.txt @@ -35,6 +35,8 @@ add_fp_unittest( libc.src.__support.math.coshf16 libc.src.__support.math.cospif libc.src.__support.math.cospif16 + libc.src.__support.math.dfmaf128 + libc.src.__support.math.dfmal libc.src.__support.math.dsqrtl libc.src.__support.math.exp10m1f libc.src.__support.math.exp10m1f16 @@ -53,15 +55,48 @@ add_fp_unittest( libc.src.__support.math.exp10f16 libc.src.__support.math.expf libc.src.__support.math.expf16 + libc.src.__support.math.f16fma + libc.src.__support.math.f16fmal + libc.src.__support.math.f16sqrtl libc.src.__support.math.frexpf libc.src.__support.math.frexpf128 libc.src.__support.math.frexpf16 + libc.src.__support.math.fsqrt + libc.src.__support.math.fsqrtf128 + libc.src.__support.math.fsqrtl + libc.src.__support.math.hypotf + libc.src.__support.math.ilogb + libc.src.__support.math.ilogbf libc.src.__support.math.ilogbf16 + libc.src.__support.math.ilogbf128 + libc.src.__support.math.ilogbl + libc.src.__support.math.ldexpf + libc.src.__support.math.llogb libc.src.__support.math.log + libc.src.__support.math.log10 + libc.src.__support.math.log1p + libc.src.__support.math.log2 + libc.src.__support.math.logbf + libc.src.__support.math.logbf128 + libc.src.__support.math.logbf16 + libc.src.__support.math.logf libc.src.__support.math.ldexpf libc.src.__support.math.ldexpf128 libc.src.__support.math.ldexpf16 + libc.src.__support.math.llogbf + libc.src.__support.math.llogbf128 + libc.src.__support.math.llogbf16 + libc.src.__support.math.logf16 libc.src.__support.math.rsqrtf libc.src.__support.math.rsqrtf16 + libc.src.__support.math.sqrtf16 libc.src.__support.math.sin + libc.src.__support.math.sinf + libc.src.__support.math.sinf16 + libc.src.__support.math.sinhf + libc.src.__support.math.sinhf16 + libc.src.__support.math.sinpif + libc.src.__support.math.sqrt + libc.src.__support.math.tan + libc.src.__support.math.tanf ) diff --git a/libc/test/shared/shared_math_test.cpp b/libc/test/shared/shared_math_test.cpp index 20a98a1..a21863e 100644 --- a/libc/test/shared/shared_math_test.cpp +++ b/libc/test/shared/shared_math_test.cpp @@ -18,6 +18,7 @@ TEST(LlvmLibcSharedMathTest, AllFloat16) { EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::acoshf16(1.0f16)); EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::acospif16(1.0f16)); EXPECT_FP_EQ(0x1p+0f16, LIBC_NAMESPACE::shared::rsqrtf16(1.0f16)); + EXPECT_FP_EQ(0x1p+0f16, LIBC_NAMESPACE::shared::sqrtf16(1.0f16)); EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::asinf16(0.0f16)); EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::asinhf16(0.0f16)); @@ -32,6 +33,13 @@ TEST(LlvmLibcSharedMathTest, AllFloat16) { EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::exp2m1f16(0.0f16)); EXPECT_FP_EQ(0x1p+0f16, LIBC_NAMESPACE::shared::expf16(0.0f16)); EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::expm1f16(0.0f16)); + EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::logf16(1.0f16)); + EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::sinhf16(0.0f16)); + + EXPECT_FP_EQ(float16(10.0), LIBC_NAMESPACE::shared::f16fma(2.0, 3.0, 4.0)); + + EXPECT_FP_EQ(float16(10.0), + LIBC_NAMESPACE::shared::f16fmal(2.0L, 3.0L, 4.0L)); ASSERT_FP_EQ(float16(8 << 5), LIBC_NAMESPACE::shared::ldexpf16(8.0f16, 5)); ASSERT_FP_EQ(float16(-1 * (8 << 5)), @@ -42,8 +50,12 @@ TEST(LlvmLibcSharedMathTest, AllFloat16) { EXPECT_EQ(exponent, 5); EXPECT_EQ(0, LIBC_NAMESPACE::shared::ilogbf16(1.0f16)); + EXPECT_FP_EQ(0x0p+0f16, LIBC_NAMESPACE::shared::logbf16(1.0f16)); + EXPECT_EQ(0L, LIBC_NAMESPACE::shared::llogbf16(1.0f16)); EXPECT_FP_EQ(0x1.921fb6p+0f16, LIBC_NAMESPACE::shared::acosf16(0.0f16)); + EXPECT_FP_EQ(0x1p+0f16, LIBC_NAMESPACE::shared::f16sqrtl(1.0L)); + EXPECT_FP_EQ(0.0f16, LIBC_NAMESPACE::shared::sinf16(0.0f16)); } #endif // LIBC_TYPES_HAS_FLOAT16 @@ -69,15 +81,25 @@ TEST(LlvmLibcSharedMathTest, AllFloat) { EXPECT_FP_EQ(0x1p+0f, LIBC_NAMESPACE::shared::expf(0.0f)); EXPECT_FP_EQ(0x1p+0f, LIBC_NAMESPACE::shared::exp2f(0.0f)); EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::expm1f(0.0f)); + EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::hypotf(0.0f, 0.0f)); + EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::logf(1.0f)); + EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::sinhf(0.0f)); EXPECT_FP_EQ_ALL_ROUNDING(0.75f, LIBC_NAMESPACE::shared::frexpf(24.0f, &exponent)); EXPECT_EQ(exponent, 5); + EXPECT_EQ(0, LIBC_NAMESPACE::shared::ilogbf(1.0f)); + ASSERT_FP_EQ(float(8 << 5), LIBC_NAMESPACE::shared::ldexpf(8.0f, 5)); ASSERT_FP_EQ(float(-1 * (8 << 5)), LIBC_NAMESPACE::shared::ldexpf(-8.0f, 5)); + EXPECT_EQ(long(0), LIBC_NAMESPACE::shared::llogbf(1.0f)); + EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::logbf(1.0f)); EXPECT_FP_EQ(0x1p+0f, LIBC_NAMESPACE::shared::rsqrtf(1.0f)); + EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::sinpif(0.0f)); + EXPECT_FP_EQ(0.0f, LIBC_NAMESPACE::shared::sinf(0.0f)); + EXPECT_FP_EQ(0.0f, LIBC_NAMESPACE::shared::tanf(0.0f)); } TEST(LlvmLibcSharedMathTest, AllDouble) { @@ -92,8 +114,23 @@ TEST(LlvmLibcSharedMathTest, AllDouble) { EXPECT_FP_EQ(0x1p+0, LIBC_NAMESPACE::shared::exp2(0.0)); EXPECT_FP_EQ(0x1p+0, LIBC_NAMESPACE::shared::exp10(0.0)); EXPECT_FP_EQ(0x0p+0, LIBC_NAMESPACE::shared::expm1(0.0)); + EXPECT_FP_EQ(0x0p+0, LIBC_NAMESPACE::shared::fsqrt(0.0)); EXPECT_FP_EQ(0x0p+0, LIBC_NAMESPACE::shared::log(1.0)); + EXPECT_FP_EQ(0x0p+0, LIBC_NAMESPACE::shared::log10(1.0)); + EXPECT_FP_EQ(0x0p+0, LIBC_NAMESPACE::shared::log1p(0.0)); + EXPECT_FP_EQ(0x0p+0, LIBC_NAMESPACE::shared::log2(1.0)); EXPECT_FP_EQ(0.0, LIBC_NAMESPACE::shared::sin(0.0)); + EXPECT_FP_EQ(0x0p+0, LIBC_NAMESPACE::shared::sqrt(0.0)); + EXPECT_FP_EQ(0.0, LIBC_NAMESPACE::shared::tan(0.0)); + EXPECT_EQ(0, LIBC_NAMESPACE::shared::ilogb(1.0)); + EXPECT_EQ(0L, LIBC_NAMESPACE::shared::llogb(1.0)); +} + +TEST(LlvmLibcSharedMathTest, AllLongDouble) { + EXPECT_FP_EQ(0x0p+0L, + LIBC_NAMESPACE::shared::dfmal(0x0.p+0L, 0x0.p+0L, 0x0.p+0L)); + EXPECT_FP_EQ(0x0p+0f, LIBC_NAMESPACE::shared::fsqrtl(0.0L)); + EXPECT_EQ(0, LIBC_NAMESPACE::shared::ilogbl(0x1.p+0L)); } #ifdef LIBC_TYPES_HAS_FLOAT128 @@ -103,14 +140,21 @@ TEST(LlvmLibcSharedMathTest, AllFloat128) { EXPECT_FP_EQ(float128(0x0p+0), LIBC_NAMESPACE::shared::atan2f128(float128(0.0), float128(0.0))); + EXPECT_FP_EQ(0x1p+0f, LIBC_NAMESPACE::shared::fsqrtf128(float128(1.0f))); EXPECT_FP_EQ_ALL_ROUNDING(float128(0.75), LIBC_NAMESPACE::shared::frexpf128( float128(24), &exponent)); EXPECT_EQ(exponent, 5); + EXPECT_EQ(3, LIBC_NAMESPACE::shared::ilogbf128(float128(8.0))); ASSERT_FP_EQ(float128(8 << 5), LIBC_NAMESPACE::shared::ldexpf128(float128(8), 5)); ASSERT_FP_EQ(float128(-1 * (8 << 5)), LIBC_NAMESPACE::shared::ldexpf128(float128(-8), 5)); + EXPECT_FP_EQ(float128(0.0), LIBC_NAMESPACE::shared::logbf128(float128(1.0))); + EXPECT_FP_EQ(0.0, LIBC_NAMESPACE::shared::dfmaf128( + float128(0.0), float128(0.0), float128(0.0))); + + EXPECT_EQ(0L, LIBC_NAMESPACE::shared::llogbf128(float128(1.0))); } #endif // LIBC_TYPES_HAS_FLOAT128 diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt index 98980ce..b6729ba 100644 --- a/libc/test/src/__support/CMakeLists.txt +++ b/libc/test/src/__support/CMakeLists.txt @@ -280,6 +280,16 @@ add_libc_test( libc.src.__support.CPP.bit ) +add_libc_test( + weak_avl_test + SUITE + libc-support-tests + SRCS + weak_avl_test.cpp + DEPENDS + libc.src.__support.weak_avl +) + add_subdirectory(CPP) add_subdirectory(File) add_subdirectory(RPC) diff --git a/libc/test/src/__support/weak_avl_test.cpp b/libc/test/src/__support/weak_avl_test.cpp new file mode 100644 index 0000000..49ff2e8 --- /dev/null +++ b/libc/test/src/__support/weak_avl_test.cpp @@ -0,0 +1,274 @@ +//===-- Unittests for WeakAVL ---------------------------------------------===// +// +// 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/CPP/optional.h" +#include "src/__support/weak_avl.h" +#include "test/UnitTest/Test.h" + +using Node = LIBC_NAMESPACE::WeakAVLNode<int>; + +namespace { +int ternary_compare(int a, int b) { return (a > b) - (a < b); } +constexpr int TEST_SIZE = 128; +// Validate weak-AVL rank-difference invariant assuming **pure insertion only** +// (i.e. no erasure has occurred). +// +// NOTE: This validator is intentionally *not* correct after erase(), because +// weak-AVL allows transient or permanent 2-2 configurations during deletion +// fixup. +bool validate_pure_insertion(const Node *node) { + if (!node) + return true; + bool left_2 = node->has_rank_diff_2(false); + bool right_2 = node->has_rank_diff_2(true); + return (!left_2 || !right_2) && validate_pure_insertion(node->get_left()) && + validate_pure_insertion(node->get_right()); +} + +// Insert according to pattern `next(i)` +using NextFn = int (*)(int); +using OptionalNodePtr = LIBC_NAMESPACE::cpp::optional<Node *>; +struct Tree { + Node *root = nullptr; + + bool validate_pure_insertion() { return ::validate_pure_insertion(root); } + + bool contains(int value) { + return Node::find(root, value, ternary_compare).has_value(); + } + + OptionalNodePtr insert(int value) { + return Node::find_or_insert(root, value, ternary_compare); + } + + OptionalNodePtr find(int value) { + return Node::find(root, value, ternary_compare); + } + + void erase(int value) { + if (OptionalNodePtr node = Node::find(root, value, ternary_compare)) + Node::erase(root, node.value()); + } + + template <typename NextFn> static Tree build(NextFn next, int N) { + Tree tree; + for (int i = 0; i < N; ++i) + tree.insert(next(i)); + return tree; + } + + bool empty() const { return root == nullptr; } + + ~Tree() { Node::destroy(root); } +}; + +// Insertion patterns +static int seq(int i) { return i; } + +static int rev(int i) { + constexpr int N = TEST_SIZE; + return N - 1 - i; +} + +// Coprime stride permutation: i -> (i * X) % N +static int stride(int i, int prime = 7919) { + constexpr int N = TEST_SIZE; + return (i * prime) % N; +} + +} // namespace + +TEST(LlvmLibcWeakAVLTest, SimpleInsertion) { + Tree tree; + + OptionalNodePtr node10 = tree.insert(10); + ASSERT_TRUE(node10.has_value()); + ASSERT_TRUE(tree.insert(5).has_value()); + ASSERT_TRUE(tree.validate_pure_insertion()); + + OptionalNodePtr node15 = tree.insert(15); + ASSERT_TRUE(node15.has_value()); + ASSERT_TRUE(tree.validate_pure_insertion()); + + OptionalNodePtr node10_again = tree.insert(10); + ASSERT_EQ(*node10, *node10_again); + ASSERT_TRUE(tree.validate_pure_insertion()); +} + +TEST(LlvmLibcWeakAVLTest, SequentialInsertion) { + constexpr int N = TEST_SIZE; + + Tree tree = Tree::build(seq, N); + ASSERT_TRUE(tree.validate_pure_insertion()); + + for (int i = 0; i < N; ++i) { + OptionalNodePtr node = tree.insert(i); + ASSERT_TRUE(node.has_value()); + ASSERT_EQ(node.value()->get_data(), i); + } + + ASSERT_TRUE(tree.validate_pure_insertion()); +} + +TEST(LlvmLibcWeakAVLTest, ReversedInsertion) { + constexpr int N = TEST_SIZE; + + Tree tree = Tree::build(rev, N); + ASSERT_TRUE(tree.validate_pure_insertion()); + + for (int i = 0; i < N; ++i) { + OptionalNodePtr node = tree.insert(i); + ASSERT_TRUE(node.has_value()); + ASSERT_EQ(node.value()->get_data(), i); + } + + ASSERT_TRUE(tree.validate_pure_insertion()); +} + +TEST(LlvmLibcWeakAVLTest, StridedInsertion) { + constexpr int N = TEST_SIZE; + + Tree tree = Tree::build([](int i) { return stride(i); }, N); + ASSERT_TRUE(tree.validate_pure_insertion()); + + for (int i = 0; i < N; ++i) { + OptionalNodePtr node = tree.insert(i); + ASSERT_TRUE(node.has_value()); + ASSERT_EQ(node.value()->get_data(), i); + } + + ASSERT_TRUE(tree.validate_pure_insertion()); +} + +TEST(LlvmLibcWeakAVLTest, FindExistingAndMissing) { + constexpr int N = TEST_SIZE; + + Tree tree = Tree::build(seq, N); + ASSERT_TRUE(tree.validate_pure_insertion()); + + for (int i = 0; i < N; ++i) { + OptionalNodePtr node = tree.find(i); + ASSERT_TRUE(node.has_value()); + ASSERT_EQ(node.value()->get_data(), i); + } + + ASSERT_FALSE(tree.find(-1).has_value()); + ASSERT_FALSE(tree.find(N).has_value()); + ASSERT_FALSE(tree.find(2 * N).has_value()); +} + +TEST(LlvmLibcWeakAVLTest, SequentialErase) { + constexpr int N = TEST_SIZE; + + Tree tree = Tree::build(seq, N); + + for (int i = 0; i < N; ++i) { + ASSERT_TRUE(tree.contains(i)); + tree.erase(i); + ASSERT_FALSE(tree.contains(i)); + } + + ASSERT_TRUE(tree.empty()); +} + +TEST(LlvmLibcWeakAVLTest, ReverseErase) { + constexpr int N = TEST_SIZE; + + Tree tree = Tree::build(seq, N); + + for (int i = N - 1; i >= 0; --i) { + ASSERT_TRUE(tree.contains(i)); + tree.erase(i); + ASSERT_FALSE(tree.contains(i)); + } + + ASSERT_TRUE(tree.empty()); +} + +TEST(LlvmLibcWeakAVLTest, StridedErase) { + constexpr int N = TEST_SIZE; + + Tree tree = Tree::build(seq, N); + + for (int i = 0; i < N; ++i) { + int key = stride(i, 5261); + ASSERT_TRUE(tree.contains(key)); + tree.erase(key); + ASSERT_FALSE(tree.contains(key)); + } + + ASSERT_TRUE(tree.empty()); +} + +TEST(LlvmLibcWeakAVLTest, EraseStructuralCases) { + Tree tree; + int keys[] = {10, 5, 15, 3, 7, 12, 18}; + + // rank1: 10 10 + // / / \ + // rank0: 10 --> 5 --> 5 15 + + // rank2: 10 10 + // / \ / \ + // rank1: 10 5 \ 5 \ + // / \ --> / \ --> /\ \ + // rank0: 5 15 3 15 3 7 15 + + // rank2: 10 10 10 + // / \ / \ / \ + // rank1: 5 \ --> 5 15 --> 5 15 + // /\ \ /\ / /\ / \ + // rank0: 3 7 15 3 7 12 3 7 12 18 + + for (int k : keys) + tree.insert(k); + + // Erase leaf. + // rank2: 10 10 + // / \ / \ + // rank1: 5 15 5 15 + // /\ / \ --> \ / \ + // rank0: 3 7 12 18 7 12 18 + tree.erase(3); + ASSERT_FALSE(tree.contains(3)); + + // Erase internal nodes. + // Erase leaf. + // rank2: 10 10 10 + // / \ / \ / \ + // rank1: 5 15 7 15 / 15 + // \ / \ --> \ / \ --> / /\ + // rank0: 7 12 18 5 12 18 7 12 18 + tree.erase(5); + ASSERT_FALSE(tree.contains(5)); + + // Erase root. + // rank2: 10 12 12 + // / \ / \ / \ + // rank1: / 15 --> / 15 --> / 15 + // / /\ / /\ / \ + // rank0: 7 12 18 7 10 18 7 18 + tree.erase(10); + ASSERT_FALSE(tree.contains(10)); + + int attempts[] = {7, 12, 15, 18}; + for (int k : attempts) + ASSERT_TRUE(tree.contains(k)); +} + +TEST(LlvmLibcTreeWalk, InOrderTraversal) { + Tree tree = Tree::build([](int x) { return stride(x, 1007); }, TEST_SIZE); + int data[TEST_SIZE]; + int counter = 0; + Node::walk(tree.root, [&](Node *node, Node::WalkType type) { + if (type == Node::WalkType::InOrder || type == Node::WalkType::Leaf) + data[counter++] = node->get_data(); + }); + for (int i = 0; i < TEST_SIZE; ++i) + ASSERT_EQ(data[i], i); +} diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt index a39428f..fde2023 100644 --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -137,6 +137,15 @@ if(LIBC_CONF_PRINTF_DISABLE_STRERROR) list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_STRERROR") endif() +if(LIBC_CONF_PRINTF_DISABLE_WIDE) + set(wchar_deps "") +else() + set(wchar_deps + libc.hdr.types.wint_t + libc.hdr.wchar_macros + ) +endif() + add_fp_unittest( sprintf_test UNIT_TEST_ONLY @@ -148,6 +157,7 @@ add_fp_unittest( libc.src.stdio.sprintf libc.src.__support.FPUtil.fp_bits libc.include.inttypes + ${wchar_deps} COMPILE_OPTIONS ${sprintf_test_copts} ) diff --git a/libc/test/src/stdio/sprintf_test.cpp b/libc/test/src/stdio/sprintf_test.cpp index 689a38a4..78186ab 100644 --- a/libc/test/src/stdio/sprintf_test.cpp +++ b/libc/test/src/stdio/sprintf_test.cpp @@ -9,8 +9,13 @@ #include "src/__support/macros/config.h" #include "src/stdio/sprintf.h" +#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE +#include "hdr/types/wint_t.h" +#include "hdr/wchar_macros.h" +#endif // LIBC_COPT_PRINTF_DISABLE_WIDE + #include "src/__support/FPUtil/FPBits.h" -#include "src/__support/libc_errno.h" +#include "test/UnitTest/ErrnoCheckingTest.h" #include "test/UnitTest/RoundingModeUtils.h" #include "test/UnitTest/Test.h" #include <inttypes.h> @@ -3487,3 +3492,94 @@ TEST(LlvmLibcSPrintfTest, IndexModeParsing) { "why would u do this, this is such a pain. %"); } #endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE + +#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE +TEST(LlvmLibcSprintfTest, WideCharConversion) { + char buff[16]; + int written; + + // 1 byte UTF-8 character. + written = LIBC_NAMESPACE::sprintf(buff, "%lc", L'A'); + EXPECT_EQ(written, 1); + ASSERT_STREQ_LEN(written, buff, "A"); + + // 1 byte UTF-8 character left justified. + written = LIBC_NAMESPACE::sprintf(buff, "%-4lc", L'A'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "A "); + + // 1 byte UTF-8 character right justified. + written = LIBC_NAMESPACE::sprintf(buff, "%4lc", L'A'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, " A"); + + // 2 byte UTF-8 character. + written = LIBC_NAMESPACE::sprintf(buff, "%lc", L'¢'); + EXPECT_EQ(written, 2); + ASSERT_STREQ_LEN(written, buff, "¢"); + + // 2 byte UTF-8 character left justified. + written = LIBC_NAMESPACE::sprintf(buff, "%-4lc", L'¢'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "¢ "); + + // 2 byte UTF-8 character right justified. + written = LIBC_NAMESPACE::sprintf(buff, "%4lc", L'¢'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, " ¢"); + + // Euro sign is a 3-byte UTF-8 character. + written = LIBC_NAMESPACE::sprintf(buff, "%lc", L'€'); + EXPECT_EQ(written, 3); + ASSERT_STREQ_LEN(written, buff, "€"); + + // Euro sign left justified. + written = LIBC_NAMESPACE::sprintf(buff, "%-4lc", L'€'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "€ "); + + // Euro sign right justified. + written = LIBC_NAMESPACE::sprintf(buff, "%4lc", L'€'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, " €"); + + // Euro sign right justified. + written = LIBC_NAMESPACE::sprintf(buff, "%+4lc", L'€'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, " €"); + + // Grinning face emoji is a 4-byte UTF-8 character. + written = LIBC_NAMESPACE::sprintf(buff, "%lc", L'😀'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "😀"); + + // Grinning face emoji left justified. + written = LIBC_NAMESPACE::sprintf(buff, "%-4lc", L'😀'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "😀"); + + // Grinning face emoji right justified. + written = LIBC_NAMESPACE::sprintf(buff, "%4lc", L'😀'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "😀"); + + // Grinning face emoji with smaller width, left justified. + written = LIBC_NAMESPACE::sprintf(buff, "%-3lc", L'😀'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "😀"); + + // Grinning face emoji with smaller width, right justified. + written = LIBC_NAMESPACE::sprintf(buff, "%3lc", L'😀'); + EXPECT_EQ(written, 4); + ASSERT_STREQ_LEN(written, buff, "😀"); + + // WEOF test. + EXPECT_EQ(LIBC_NAMESPACE::sprintf(buff, "%lc", WEOF), -1); + ASSERT_ERRNO_EQ(EILSEQ); + + // Invalid wide character test + EXPECT_EQ(LIBC_NAMESPACE::sprintf(buff, "%lc", static_cast<wint_t>(0x12ffff)), + -1); + ASSERT_ERRNO_EQ(EILSEQ); +} +#endif // LIBC_COPT_PRINTF_DISABLE_WIDE diff --git a/libc/test/src/strings/CMakeLists.txt b/libc/test/src/strings/CMakeLists.txt index 5f70dc0..6e1befc 100644 --- a/libc/test/src/strings/CMakeLists.txt +++ b/libc/test/src/strings/CMakeLists.txt @@ -110,5 +110,20 @@ add_libc_test( libc.src.strings.strncasecmp_l ) +add_libc_test( + wide_read_memory_test + SUITE + libc-strings-tests + SRCS + wide_read_memory_test.cpp + DEPENDS + libc.src.string.string_utils + libc.src.sys.mman.mmap + libc.src.sys.mman.mprotect + libc.src.sys.mman.munmap + libc.src.unistd.linux.getpagesize + libc.src.__support.CPP.array +) + add_libc_multi_impl_test(bcmp libc-strings-tests SRCS bcmp_test.cpp) add_libc_multi_impl_test(bzero libc-strings-tests SRCS bzero_test.cpp) diff --git a/libc/test/src/strings/wide_read_memory_test.cpp b/libc/test/src/strings/wide_read_memory_test.cpp new file mode 100644 index 0000000..cc4a2dc --- /dev/null +++ b/libc/test/src/strings/wide_read_memory_test.cpp @@ -0,0 +1,101 @@ +//===-- Memory bounds check test for wide-read functions ------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// For performance, some vector-based libc functions read data outside of, but +// adjacent to, the input address. For example, string_length can read both +// before and after the data in its src parameter. As part of the +// implementation, it is allowed to do this. However, the code must take care to +// avoid address errors. The sanitizers can't distinguish between "the +// implementation" and user-code, and so report an error. Therefore we can't use +// them to check if functions like these have memory errors. +// +// This test uses mprotect to simulate address sanitization. Tests that read too +// far outside data will segfault. +// +// It creates three adjacent pages in memory. The outer two are mprotected +// unreadable, the middle usable normally. By placing test data at the edges +// between the middle page and the others, we can test for bad accesses. + +#include "src/__support/CPP/array.h" +#include "src/string/memory_utils/inline_memset.h" +#include "src/string/string_utils.h" +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/mprotect.h" +#include "src/sys/mman/munmap.h" +#include "src/unistd/getpagesize.h" +#include "test/UnitTest/MemoryMatcher.h" +#include "test/UnitTest/Test.h" + +namespace LIBC_NAMESPACE_DECL { + +using TwoKilobyteBuffer = cpp::array<char, 2048>; +// This could be smaller on a target-basis, but that adds complexity and the +// extra testing is fine. +static constexpr unsigned long kLargestTestVectorSize = 512; + +class LlvmLibcWideAccessMemoryTest : public testing::Test { + char *page0_; + char *page1_; + char *page2_; + size_t page_size; + +public: + void SetUp() override { + page_size = LIBC_NAMESPACE::getpagesize(); + page0_ = static_cast<char *>( + LIBC_NAMESPACE::mmap(nullptr, page_size * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + ASSERT_NE(static_cast<void *>(page0_), MAP_FAILED); + page1_ = page0_ + page_size; + page2_ = page1_ + page_size; + LIBC_NAMESPACE::mprotect(page0_, page_size, PROT_NONE); + LIBC_NAMESPACE::mprotect(page2_, page_size, PROT_NONE); + } + + void TearDown() override { LIBC_NAMESPACE::munmap(page0_, page_size * 3); } + + // Repeatedly runs "func" on copies of the data in "buf", each progressively + // closer to the boundary of valid memory. Test will segfault if function + // under test accesses invalid memory. + // + // Func should test the function in question just as normal. Recommend making + // the amount of test data at least 1.5k, which guarantees a wind-up, multiple + // iterations of the inner loop, and a wind-down, even on systems with + // 512-byte vectors. The termination condition, eg, end-of string or character + // being searched for, should be near the end of the data. + template <typename TestFunc> + void TestMemoryAccess(const TwoKilobyteBuffer &buf, TestFunc func) { + // Run func on data near the start boundary of valid memory. + for (unsigned long offset = 0; offset < kLargestTestVectorSize; ++offset) { + char *test_addr = page1_ + offset; + inline_memcpy(test_addr, buf.data(), buf.size()); + func(test_addr); + } + // Run func on data near the end boundary of valid memory. + for (unsigned long offset = 0; offset < kLargestTestVectorSize; ++offset) { + char *test_addr = page2_ - buf.size() - offset - 1; + ASSERT_LE(test_addr + buf.size(), page2_); + inline_memcpy(test_addr, buf.data(), buf.size()); + func(test_addr); + } + } +}; + +TEST_F(LlvmLibcWideAccessMemoryTest, StringLength) { + // 1.5 k long vector of a's. + TwoKilobyteBuffer buf; + inline_memset(buf.data(), 'a', buf.size()); + // Make sure it is null terminated. + buf[buf.size() - 1] = '\0'; + this->TestMemoryAccess(buf, [this, buf](const char *test_data) { + // -1 for the null character. + ASSERT_EQ(internal::string_length(test_data), size_t(buf.size() - 1)); + }); +} + +} // namespace LIBC_NAMESPACE_DECL |
