Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/bionic/bionic/tests/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 47 kB image not shown  

Quelle  wchar_test.cpp

  Sprache: C
 

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <gtest/gtest.h>

#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include <wchar.h>

#include <limits>

#include "buffer_tests.h"
#include "utils.h"

constexpr static auto KB = 1024;
constexpr static auto LARGE = 64 * KB;

#define NUM_WCHARS(num_bytes) ((num_bytes)/sizeof(wchar_t))

#ifdef __GLIBC__
// glibc immediately dereferences the locale passed to all wcsto*_l functions,
// even if it won't be used, and even if it's LC_GLOBAL_LOCALE, which isn't a
// pointer to valid memory.
static locale_t SAFE_LC_GLOBAL_LOCALE = duplocale(LC_GLOBAL_LOCALE);
#else
static locale_t SAFE_LC_GLOBAL_LOCALE = LC_GLOBAL_LOCALE;
#endif

// Modern versions of UTF-8 (https://datatracker.ietf.org/doc/html/rfc3629 and
// newer) explicitly disallow code points beyond U+10FFFF, which exclude all 5-
// and 6-byte sequences. Earlier versions of UTF-8 allowed the wider range:
// https://datatracker.ietf.org/doc/html/rfc2279.
//
// Bionic's unicode implementation was written after the high values were
// excluded, so it has never supported them. Other implementations (at least
// as of glibc 2.36), do support those sequences.
#if defined(__ANDROID__) || defined(ANDROID_HOST_MUSL)
constexpr bool kLibcRejectsOverLongUtf8Sequences = true;
#elif defined(__GLIBC__)
constexpr bool kLibcRejectsOverLongUtf8Sequences = false;
#else
#error kLibcRejectsOverLongUtf8Sequences must be configured for this platform
#endif

#if defined(__GLIBC__)
constexpr bool kLibcSupportsParsingBinaryLiterals = __GLIBC_PREREQ(238);
#else
constexpr bool kLibcSupportsParsingBinaryLiterals = true;
#endif

TEST(wchar, sizeof_wchar_t) {
  EXPECT_EQ(4U, sizeof(wchar_t));
}

TEST(wchar, sizeof_wint_t) {
  EXPECT_EQ(4U, sizeof(wint_t));
}

TEST(stdint, wchar_sign) {
#if defined(__arm__) || defined(__aarch64__)
  EXPECT_FALSE(std::numeric_limits<wchar_t>::is_signed);
#else
  EXPECT_TRUE(std::numeric_limits<wchar_t>::is_signed);
#endif
}

#if !defined(__WINT_UNSIGNED__)
#error wint_t is unsigned on Android
#endif

TEST(stdint, wint_sign) {
  EXPECT_FALSE(std::numeric_limits<wint_t>::is_signed);
}

TEST(wchar, mbrlen) {
  char bytes[] = { 'h''e''l''l''o''\0' };
  EXPECT_EQ(static_cast<size_t>(-2), mbrlen(&bytes[0], 0, nullptr));
  EXPECT_EQ(1U, mbrlen(&bytes[0], 1, nullptr));

  EXPECT_EQ(1U, mbrlen(&bytes[4], 1, nullptr));
  EXPECT_EQ(0U, mbrlen(&bytes[5], 1, nullptr));
}

TEST(wchar, wctomb_wcrtomb) {
  // wctomb and wcrtomb behave differently when s == NULL.
  EXPECT_EQ(0, wctomb(nullptr, L'h'));
  EXPECT_EQ(0, wctomb(nullptr, L'\0'));
  EXPECT_EQ(1U, wcrtomb(nullptr, L'\0', nullptr));
  EXPECT_EQ(1U, wcrtomb(nullptr, L'h', nullptr));

  char bytes[MB_LEN_MAX];

  // wctomb and wcrtomb behave similarly for the null wide character.
  EXPECT_EQ(1, wctomb(bytes, L'\0'));
  EXPECT_EQ(1U, wcrtomb(bytes, L'\0', nullptr));

  // ...and for regular characters.
  memset(bytes, 0sizeof(bytes));
  EXPECT_EQ(1, wctomb(bytes, L'h'));
  EXPECT_EQ('h', bytes[0]);
  memset(bytes, 0sizeof(bytes));
  EXPECT_EQ(1U, wcrtomb(bytes, L'h', nullptr));
  EXPECT_EQ('h', bytes[0]);

  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  // 1-byte UTF-8.
  memset(bytes, 0sizeof(bytes));
  EXPECT_EQ(1U, wcrtomb(bytes, L'h', nullptr));
  EXPECT_EQ('h', bytes[0]);
  // 2-byte UTF-8.
  memset(bytes, 0sizeof(bytes));
  EXPECT_EQ(2U, wcrtomb(bytes, 0x00a2, nullptr));
  EXPECT_EQ('\xc2', bytes[0]);
  EXPECT_EQ('\xa2', bytes[1]);
  // 3-byte UTF-8.
  memset(bytes, 0sizeof(bytes));
  EXPECT_EQ(3U, wcrtomb(bytes, 0x20ac, nullptr));
  EXPECT_EQ('\xe2', bytes[0]);
  EXPECT_EQ('\x82', bytes[1]);
  EXPECT_EQ('\xac', bytes[2]);
  // 4-byte UTF-8.
  memset(bytes, 0sizeof(bytes));
  EXPECT_EQ(4U, wcrtomb(bytes, 0x24b62, nullptr));
  EXPECT_EQ('\xf0', bytes[0]);
  EXPECT_EQ('\xa4', bytes[1]);
  EXPECT_EQ('\xad', bytes[2]);
  EXPECT_EQ('\xa2', bytes[3]);
  // Invalid code point.
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcrtomb(bytes, 0xffffffff, nullptr));
}

TEST(wchar, wcrtomb_start_state) {
  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  char out[MB_LEN_MAX];
  mbstate_t ps = {};

  // Any non-initial state is invalid when calling wcrtomb.
  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(nullptr, "\xc2"1, &ps));
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcrtomb(out, 0x00a2, &ps));

  // If the first argument to wcrtomb is NULL or the second is L'\0' the shift
  // state should be reset.
  ps = {};
  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(nullptr, "\xc2"1, &ps));
  EXPECT_EQ(1U, wcrtomb(nullptr, 0x00a2, &ps));
  EXPECT_TRUE(mbsinit(&ps));

  ps = {};
  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(nullptr, "\xf0\xa4"1, &ps));
  EXPECT_EQ(1U, wcrtomb(out, L'\0', &ps));
  EXPECT_TRUE(mbsinit(&ps));
}

TEST(wchar, wcstombs_wcrtombs) {
  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  const wchar_t chars[] = { L'h', L'e', L'l', L'l', L'o'0 };
  const wchar_t bad_chars[] = { L'h', L'i'static_cast<wchar_t>(0xffffffff), 0 };
  const wchar_t* src;
  char bytes[BUFSIZ];

  // Given a NULL destination, these functions count valid characters.
  EXPECT_EQ(5U, wcstombs(nullptr, chars, 0));
  EXPECT_EQ(5U, wcstombs(nullptr, chars, 4));
  EXPECT_EQ(5U, wcstombs(nullptr, chars, 256));
  src = chars;
  EXPECT_EQ(5U, wcsrtombs(nullptr, &src, 0, nullptr));
  EXPECT_EQ(&chars[0], src);
  src = chars;
  EXPECT_EQ(5U, wcsrtombs(nullptr, &src, 4, nullptr));
  EXPECT_EQ(&chars[0], src);
  src = chars;
  EXPECT_EQ(5U, wcsrtombs(nullptr, &src, 256, nullptr));
  EXPECT_EQ(&chars[0], src);

  // An unrepresentable char just returns an error from wcstombs...
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcstombs(nullptr, bad_chars, 0));
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcstombs(nullptr, bad_chars, 256));

  // And wcsrtombs doesn't tell us where it got stuck because we didn't ask it
  // to actually convert anything...
  src = bad_chars;
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcsrtombs(nullptr, &src, 0, nullptr));
  EXPECT_EQ(&bad_chars[0], src);
  src = bad_chars;
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcsrtombs(nullptr, &src, 256, nullptr));
  EXPECT_EQ(&bad_chars[0], src);

  // Okay, now let's test actually converting something...
  memset(bytes, 'x'sizeof(bytes));
  EXPECT_EQ(0U, wcstombs(bytes, chars, 0));
  memset(bytes, 'x'sizeof(bytes));
  EXPECT_EQ(4U, wcstombs(bytes, chars, 4));
  bytes[5] = 0;
  EXPECT_STREQ("hellx", bytes);
  memset(bytes, 'x'sizeof(bytes));
  EXPECT_EQ(5U, wcstombs(bytes, chars, 256));
  EXPECT_STREQ("hello", bytes);
  memset(bytes, 'x'sizeof(bytes));
  EXPECT_EQ(5U, wcstombs(bytes, chars, 6));
  EXPECT_STREQ("hello", bytes);
  memset(bytes, 'x'sizeof(bytes));
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcstombs(bytes, bad_chars, 256));
  bytes[3] = 0;
  EXPECT_STREQ("hix", bytes);

  // wcsrtombs is a bit more informative...
  memset(bytes, 'x'sizeof(bytes));
  src = chars;
  EXPECT_EQ(0U, wcsrtombs(bytes, &src, 0, nullptr));
  EXPECT_EQ(&chars[0], src); // No input consumed.

  memset(bytes, 'x'sizeof(bytes));
  src = chars;
  EXPECT_EQ(4U, wcsrtombs(bytes, &src, 4, nullptr));
  EXPECT_EQ(&chars[4], src); // Some input consumed.
  bytes[5] = 0;
  EXPECT_STREQ("hellx", bytes);

  memset(bytes, 'x'sizeof(bytes));
  src = chars;
  EXPECT_EQ(5U, wcsrtombs(bytes, &src, 256, nullptr));
  EXPECT_EQ(nullptr, src); // All input consumed!
  EXPECT_STREQ("hello", bytes);

  memset(bytes, 'x'sizeof(bytes));
  src = chars;
  EXPECT_EQ(5U, wcsrtombs(bytes, &src, 6, nullptr));
  EXPECT_EQ(nullptr, src); // All input consumed.
  EXPECT_STREQ("hello", bytes);

  memset(bytes, 'x'sizeof(bytes));
  src = bad_chars;
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcsrtombs(bytes, &src, 256, nullptr));
  EXPECT_EQ(&bad_chars[2], src);
  bytes[3] = 0;
  EXPECT_STREQ("hix", bytes);

  // Any non-initial state is invalid when calling wcsrtombs.
  mbstate_t ps = {};
  src = chars;
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(nullptr, "\xc2"1, &ps));
  EXPECT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), wcsrtombs(nullptr, &src, 0, &ps));
}

TEST(wchar, limits) {
  ASSERT_LT(WCHAR_MIN, WCHAR_MAX);
}

TEST(wchar, wcsstr) {
  const wchar_t* haystack = L"big daddy/giant haystacks!";
  const wchar_t* empty_haystack = L"";

  // The empty needle is a special case.
  ASSERT_EQ(haystack, wcsstr(haystack, L""));
  ASSERT_EQ(empty_haystack, wcsstr(empty_haystack, L""));

  ASSERT_EQ(haystack, wcsstr(haystack, L"b"));
  ASSERT_EQ(haystack, wcsstr(haystack, L"big"));
  ASSERT_EQ(haystack + 9, wcsstr(haystack, L"/"));
  ASSERT_EQ(haystack + 9, wcsstr(haystack, L"/giant"));
  ASSERT_EQ(haystack + 25, wcsstr(haystack, L"!"));
  ASSERT_EQ(haystack + 19, wcsstr(haystack, L"stacks!"));

  ASSERT_EQ(nullptr, wcsstr(haystack, L"monkey"));
  ASSERT_EQ(nullptr, wcsstr(empty_haystack, L"monkey"));
}

TEST(wchar, wcsstr_80199) {
  // https://code.google.com/p/android/issues/detail?id=80199
  ASSERT_TRUE(wcsstr(L"romrom", L"rom") != nullptr);
}

TEST(wchar, mbtowc) {
  wchar_t out[8];

  // mbtowc and all the mbrto* APIs behave slightly differently when n is 0:
  //
  // mbrtowc returns 0 "if the next n or fewer bytes complete the multibyte
  // character that corresponds to the null wide character"
  //
  // mbrtoc (C23 7.24.7.2.4) says:
  //
  //     If s is not a null pointer, the mbtowc function either returns 0 (if s
  //     points to the null character), or returns the number of bytes that are
  //     contained in the converted multibyte character (if the next n or fewer
  //     bytes form a valid multibyte character), or returns -1 (if they do not
  //     form a valid multibyte character).
  //
  // glibc's interpretation differs from all the BSDs (including macOS) and
  // bionic (by way of openbsd). glibc returns 0 since s does point to the null
  // character, whereas the BSDs return -1 because the next 0 bytes do not form
  // a valid multibyte chatacter. glibc's interpretation is probably more
  // correct from a strict interpretation of the spec, but considering the other
  // APIs behave more like the BSD interpretation that may be a bug in the spec.
#ifdef __GLIBC__
  int expected_result_for_zero_length_empty_string = 0;
#else
  int expected_result_for_zero_length_empty_string = -1;
#endif

  out[0] = 'x';
  EXPECT_EQ(-1, mbtowc(out, "hello"0));
  EXPECT_EQ('x', out[0]);

  EXPECT_EQ(-1, mbtowc(out, "hello"0));
  EXPECT_EQ(expected_result_for_zero_length_empty_string, mbtowc(out, ""0));
  EXPECT_EQ(1, mbtowc(out, "hello"1));
  EXPECT_EQ(L'h', out[0]);

  EXPECT_EQ(-1, mbtowc(nullptr, "hello"0));
  EXPECT_EQ(expected_result_for_zero_length_empty_string, mbtowc(nullptr, ""0));
  EXPECT_EQ(1, mbtowc(nullptr, "hello"1));

  EXPECT_EQ(0, mbtowc(nullptr, nullptr, 0));
}

TEST(wchar, mbrtowc) {
  wchar_t out[8];

  out[0] = 'x';
  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(out, "hello"0, nullptr));
  EXPECT_EQ('x', out[0]);

  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(out, "hello"0, nullptr));
  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(out, ""0, nullptr));
  EXPECT_EQ(1U, mbrtowc(out, "hello"1, nullptr));
  EXPECT_EQ(L'h', out[0]);

  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(nullptr, "hello"0, nullptr));
  EXPECT_EQ(static_cast<size_t>(-2), mbrtowc(nullptr, ""0, nullptr));
  EXPECT_EQ(1U, mbrtowc(nullptr, "hello"1, nullptr));

  EXPECT_EQ(0U, mbrtowc(nullptr, nullptr, 0, nullptr));

  EXPECT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  // 1-byte UTF-8.
  EXPECT_EQ(1U, mbrtowc(out, "abcdef"6, nullptr));
  EXPECT_EQ(L'a', out[0]);
  // 2-byte UTF-8.
  EXPECT_EQ(2U, mbrtowc(out,
                        "\xc2\xa2"
                        "cdef",
                        6, nullptr));
  EXPECT_EQ(static_cast<wchar_t>(0x00a2), out[0]);
  // 3-byte UTF-8.
  EXPECT_EQ(3U, mbrtowc(out,
                        "\xe2\x82\xac"
                        "def",
                        6, nullptr));
  EXPECT_EQ(static_cast<wchar_t>(0x20ac), out[0]);
  // 4-byte UTF-8.
  EXPECT_EQ(4U, mbrtowc(out,
                        "\xf0\xa4\xad\xa2"
                        "ef",
                        6, nullptr));
  EXPECT_EQ(static_cast<wchar_t>(0x24b62), out[0]);
#if defined(__BIONIC__) // glibc allows this.
  // Illegal 5-byte UTF-8.
  EXPECT_ERRNO_FAILURE(EILSEQ,
                       static_cast<size_t>(-1), mbrtowc(out,
                       "\xf8\xa1\xa2\xa3\xa4"
                       "f",
                       6, nullptr));
#endif
  // Illegal over-long sequence.
  EXPECT_ERRNO_FAILURE(EILSEQ,
                       static_cast<size_t>(-1), mbrtowc(out,
                       "\xf0\x82\x82\xac"
                       "ef",
                       6, nullptr));
}

TEST(wchar, mbrtowc_valid_non_characters) {
  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  wchar_t out[8] = {};

  ASSERT_EQ(3U, mbrtowc(out, "\xef\xbf\xbe"3, nullptr));
  ASSERT_EQ(static_cast<wchar_t>(0xfffe), out[0]);
  ASSERT_EQ(3U, mbrtowc(out, "\xef\xbf\xbf"3, nullptr));
  ASSERT_EQ(static_cast<wchar_t>(0xffff), out[0]);
}

TEST(wchar, mbrtowc_out_of_range) {
  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  wchar_t out[8] = {};
  errno = 0;
  auto result = mbrtowc(out, "\xf5\x80\x80\x80"4, nullptr);
  if (kLibcRejectsOverLongUtf8Sequences) {
    ASSERT_EQ(static_cast<size_t>(-1), result);
    ASSERT_ERRNO(EILSEQ);
  } else {
    ASSERT_EQ(4U, result);
    ASSERT_ERRNO(0);
  }
}

static void test_mbrtowc_incomplete(mbstate_t* ps) {
  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  wchar_t out;
  // 2-byte UTF-8.
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(&out, "\xc2"1, ps));
  ASSERT_EQ(1U, mbrtowc(&out, "\xa2" "cdef"5, ps));
  ASSERT_EQ(static_cast<wchar_t>(0x00a2), out);
  ASSERT_TRUE(mbsinit(ps));
  // 3-byte UTF-8.
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(&out, "\xe2"1, ps));
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(&out, "\x82"1, ps));
  ASSERT_EQ(1U, mbrtowc(&out, "\xac" "def"4, ps));
  ASSERT_EQ(static_cast<wchar_t>(0x20ac), out);
  ASSERT_TRUE(mbsinit(ps));
  // 4-byte UTF-8.
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(&out, "\xf0"1, ps));
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(&out, "\xa4\xad"2, ps));
  ASSERT_EQ(1U, mbrtowc(&out, "\xa2" "ef"3, ps));
  ASSERT_EQ(static_cast<wchar_t>(0x24b62), out);
  ASSERT_TRUE(mbsinit(ps));

  // Invalid 2-byte
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(&out, "\xc2"1, ps));
  ASSERT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), mbrtowc(&out, "\x20" "cdef"5, ps));
}

TEST(wchar, mbrtowc_incomplete) {
  mbstate_t ps = {};

  test_mbrtowc_incomplete(&ps);
  test_mbrtowc_incomplete(nullptr);
}

static void test_mbsrtowcs(mbstate_t* ps) {
  constexpr const char* VALID = "A" "\xc2\xa2" "\xe2\x82\xac" "\xf0\xa4\xad\xa2" "ef";
  constexpr const char* INVALID = "A" "\xc2\x20" "ef";
  constexpr const char* INCOMPLETE = "A" "\xc2";
  wchar_t out[4];

  const char* valid = VALID;
  ASSERT_EQ(4U, mbsrtowcs(out, &valid, 4, ps));
  ASSERT_EQ(L'A', out[0]);
  ASSERT_EQ(static_cast<wchar_t>(0x00a2), out[1]);
  ASSERT_EQ(static_cast<wchar_t>(0x20ac), out[2]);
  ASSERT_EQ(static_cast<wchar_t>(0x24b62), out[3]);
  // Check that valid has advanced to the next unread character.
  ASSERT_EQ('e', *valid);

  wmemset(out, L'x', NUM_WCHARS(sizeof(out)));
  ASSERT_EQ(2U, mbsrtowcs(out, &valid, 4, ps));
  ASSERT_EQ(L'e', out[0]);
  ASSERT_EQ(L'f', out[1]);
  ASSERT_EQ(L'\0', out[2]);
  // Check that we didn't clobber the rest of out.
  ASSERT_EQ(L'x', out[3]);
  // Check that valid has advanced to the end of the string.
  ASSERT_EQ(nullptr, valid);

  const char* invalid = INVALID;
  ASSERT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), mbsrtowcs(out, &invalid, 4, ps));
  ASSERT_EQ('\xc2', *invalid);

  const char* incomplete = INCOMPLETE;
  ASSERT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), mbsrtowcs(out, &incomplete, 2, ps));
  ASSERT_EQ('\xc2', *incomplete);

  // If dst is null, *src shouldn't be updated.
  // https://code.google.com/p/android/issues/detail?id=166381
  const char* mbs = VALID;
  EXPECT_EQ(6U, mbsrtowcs(nullptr, &mbs, 0, ps));
  EXPECT_EQ(VALID, mbs);
  mbs = INVALID;
  EXPECT_EQ(static_cast<size_t>(-1), mbsrtowcs(nullptr, &mbs, 0, ps));
  EXPECT_EQ(INVALID, mbs);
  mbs = INCOMPLETE;
  EXPECT_EQ(static_cast<size_t>(-1), mbsrtowcs(nullptr, &mbs, 0, ps));
  EXPECT_EQ(INCOMPLETE, mbs);
}

TEST(wchar, mbsrtowcs) {
  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  mbstate_t ps = {};
  test_mbsrtowcs(&ps);
  test_mbsrtowcs(nullptr);

  // Invalid multi byte continuation.
  const char* invalid = "\x20";
  wchar_t out;
  ASSERT_EQ(static_cast<size_t>(-2), mbrtowc(&out, "\xc2"1, &ps));
  ASSERT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), mbsrtowcs(&out, &invalid, 1, &ps));
  ASSERT_EQ('\x20', *invalid);
}

template <typename T>
using WcsToIntFn = T (*)(const wchar_t*, wchar_t**, int);

template <typename T>
void TestSingleWcsToInt(WcsToIntFn<T> fn, const wchar_t* str, int base,
                        T expected_value, ptrdiff_t expected_len) {
  wchar_t* p;
  EXPECT_EQ(expected_value, fn(str, &p, base)) << str << " " << base;
  EXPECT_EQ(expected_len, p - str) << str << " " << base;
}

template <typename T>
void TestWcsToInt(WcsToIntFn<T> fn) {
  TestSingleWcsToInt(fn, L"123"10static_cast<T>(123), 3);
  TestSingleWcsToInt(fn, L"123"0static_cast<T>(123), 3);
  TestSingleWcsToInt(fn, L"123#"10static_cast<T>(123), 3);
  TestSingleWcsToInt(fn, L"01000"8static_cast<T>(512), 5);
  TestSingleWcsToInt(fn, L"01000"0static_cast<T>(512), 5);
  TestSingleWcsToInt(fn, L"   123 45"0static_cast<T>(123), 6);
  TestSingleWcsToInt(fn, L"  -123"0static_cast<T>(-123), 6);
  TestSingleWcsToInt(fn, L"0x10000"0static_cast<T>(65536), 7);
  if (kLibcSupportsParsingBinaryLiterals) {
    TestSingleWcsToInt(fn, L"0b1011"0static_cast<T>(0b1011), 6);
  }
}

template <typename T>
void TestWcsToIntLimits(WcsToIntFn<T> fn, const wchar_t* min_str,
                        const wchar_t* max_str) {
  if (std::is_signed<T>::value) {
    ASSERT_EQ(std::numeric_limits<T>::min(), fn(min_str, nullptr, 0)) << min_str;
  } else {
    // If the subject sequence begins with a <hyphen-minus>, the value resulting
    // from the conversion shall be negated.
    // https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/strtoul.html
    ASSERT_EQ(std::numeric_limits<T>::max(), fn(min_str, nullptr, 0)) << min_str;
  }
  ASSERT_EQ(std::numeric_limits<T>::max(), fn(max_str, nullptr, 0)) << max_str;
}

TEST(wchar, wcstol) {
  TestWcsToInt(wcstol);
}

TEST(wchar, wcstol_limits) {
  if (sizeof(long) == 8) {
    TestWcsToIntLimits(wcstol, L"-9223372036854775809", L"9223372036854775808");
  } else {
    TestWcsToIntLimits(wcstol, L"-2147483649", L"2147483648");
  }
}

TEST(wchar, wcstoul) {
  TestWcsToInt(wcstoul);
}

TEST(wchar, wcstoul_limits) {
  if (sizeof(long) == 8) {
    TestWcsToIntLimits(wcstoul, L"-1", L"18446744073709551616");
  } else {
    TestWcsToIntLimits(wcstoul, L"-1", L"4294967296");
  }
}

TEST(wchar, wcstoll) {
  TestWcsToInt(wcstoll);
}

TEST(wchar, wcstoll_limits) {
  TestWcsToIntLimits(wcstoll, L"-9223372036854775809", L"9223372036854775808");
}

TEST(wchar, wcstoull) {
  TestWcsToInt(wcstoull);
}

TEST(wchar, wcstoull_limits) {
  TestWcsToIntLimits(wcstoull, L"-1", L"18446744073709551616");
}

TEST(wchar, wcstoimax) {
  TestWcsToInt(wcstoimax);
}

TEST(wchar, wcstoimax_limits) {
  TestWcsToIntLimits(wcstoimax, L"-9223372036854775809",
                     L"9223372036854775808");
}

TEST(wchar, wcstoumax) {
  TestWcsToInt(wcstoumax);
}

TEST(wchar, wcstoumax_limits) {
  TestWcsToIntLimits(wcstoumax, L"-1", L"18446744073709551616");
}

TEST(wchar, mbsnrtowcs) {
  wchar_t dst[128];
  const char* s = "hello, world!";
  const char* src;

  memset(dst, 0sizeof(dst));
  src = s;
  ASSERT_EQ(0U, mbsnrtowcs(dst, &src, 00, nullptr));

  memset(dst, 0sizeof(dst));
  src = s;
  ASSERT_EQ(2U, mbsnrtowcs(dst, &src, 2123, nullptr)); // glibc chokes on SIZE_MAX here.
  ASSERT_EQ(L'h', dst[0]);
  ASSERT_EQ(L'e', dst[1]);
  ASSERT_EQ(&s[2], src);

  memset(dst, 0sizeof(dst));
  src = s;
  ASSERT_EQ(3U, mbsnrtowcs(dst, &src, SIZE_MAX, 3, nullptr));
  ASSERT_EQ(L'h', dst[0]);
  ASSERT_EQ(L'e', dst[1]);
  ASSERT_EQ(L'l', dst[2]);
  ASSERT_EQ(&s[3], src);

  memset(dst, 0sizeof(dst));
  const char* incomplete = "\xc2"// Incomplete UTF-8 sequence.
  src = incomplete;
  ASSERT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), mbsnrtowcs(dst, &src, SIZE_MAX, 3, nullptr));

  src = incomplete;
  ASSERT_ERRNO_FAILURE(EILSEQ, static_cast<size_t>(-1), mbsnrtowcs(nullptr, &src, SIZE_MAX, 3, nullptr));
}

TEST(wchar, wcsftime__wcsftime_l) {
  setenv("TZ""UTC"1);

  struct tm t = {.tm_year = 200, .tm_mon = 2, .tm_mday = 10};
  wchar_t buf[64];

  EXPECT_EQ(24U, wcsftime(buf, sizeof(buf), L"%c", &t));
  EXPECT_STREQ(L"Sun Mar 10 00:00:00 2100", buf);
  EXPECT_EQ(24U, wcsftime_l(buf, sizeof(buf), L"%c", &t, SAFE_LC_GLOBAL_LOCALE));
  EXPECT_STREQ(L"Sun Mar 10 00:00:00 2100", buf);
}

TEST(wchar, wmemmove_smoke) {
  const wchar_t const_wstr[] = L"This is a test of something or other.....";
  wchar_t wstr[NUM_WCHARS(sizeof(const_wstr))];

  EXPECT_EQ(wstr, wmemmove(wstr, const_wstr, NUM_WCHARS(sizeof(const_wstr))));
  EXPECT_STREQ(const_wstr, wstr);

  EXPECT_EQ(wstr+5, wmemmove(wstr+5, wstr, NUM_WCHARS(sizeof(const_wstr)) - 6));
  EXPECT_STREQ(L"This This is a test of something or other", wstr);
}

TEST(wchar, wmemcpy_smoke) {
  const wchar_t src[] = L"Source string";
  wchar_t dst[NUM_WCHARS(sizeof(src))];

  EXPECT_EQ(dst, wmemcpy(dst, src, NUM_WCHARS(sizeof(src))));
  EXPECT_STREQ(dst, src);
}

TEST(wchar, wcpcpy_smoke) {
  const wchar_t src[] = L"Source string";
  wchar_t dst[NUM_WCHARS(sizeof(src))];

  EXPECT_EQ(dst + NUM_WCHARS(sizeof(src)) - 1, wcpcpy(dst, src));
  EXPECT_STREQ(dst, src);
}

TEST(wchar, wcpncpy_smoke) {
  const wchar_t src[] = L"Source string";
  wchar_t dst[NUM_WCHARS(sizeof(src)) + 5];

  size_t src_len = NUM_WCHARS(sizeof(src)) - 1;
  EXPECT_EQ(dst + src_len, wcpncpy(dst, src, src_len + 1));
  EXPECT_STREQ(dst, src);

  EXPECT_EQ(dst + 6, wcpncpy(dst, src, 6));
  dst[6] = L'\0';
  EXPECT_STREQ(dst, L"Source");

  wmemset(dst, L'x', NUM_WCHARS(sizeof(dst)));
  EXPECT_EQ(dst + src_len, wcpncpy(dst, src, src_len + 4));
  EXPECT_STREQ(dst, src);
  EXPECT_EQ(dst[src_len], L'\0');
  EXPECT_EQ(dst[src_len+1], L'\0');
  EXPECT_EQ(dst[src_len+2], L'\0');
  EXPECT_EQ(dst[src_len+3], L'\0');
  EXPECT_EQ(dst[src_len+4], L'x');
}

TEST(wchar, wcscpy_smoke) {
  const wchar_t src[] = L"Source string";
  wchar_t dst[NUM_WCHARS(sizeof(src))];

  EXPECT_EQ(dst, wcscpy(dst, src));
  EXPECT_STREQ(src, dst);
}

TEST(wchar, wcsncpy_smoke) {
  const wchar_t src[] = L"Source string";
  wchar_t dst[NUM_WCHARS(sizeof(src)) + 5];

  size_t src_len = NUM_WCHARS(sizeof(src)) - 1;
  EXPECT_EQ(dst, wcsncpy(dst, src, src_len + 1));
  EXPECT_STREQ(dst, src);

  EXPECT_EQ(dst, wcsncpy(dst, src, 6));
  dst[6] = L'\0';
  EXPECT_STREQ(dst, L"Source");
  EXPECT_EQ(dst, wcsncpy(dst, L"clobber"0));
  EXPECT_STREQ(dst, L"Source");

  wmemset(dst, L'x', NUM_WCHARS(sizeof(dst)));
  EXPECT_EQ(dst, wcsncpy(dst, src, src_len + 4));
  EXPECT_STREQ(dst, src);
  EXPECT_EQ(dst[src_len], L'\0');
  EXPECT_EQ(dst[src_len+1], L'\0');
  EXPECT_EQ(dst[src_len+2], L'\0');
  EXPECT_EQ(dst[src_len+3], L'\0');
  EXPECT_EQ(dst[src_len+4], L'x');
}

TEST(wchar, mbrtowc_15439554) {
  // http://b/15439554
  ASSERT_STREQ("C.UTF-8", setlocale(LC_CTYPE, "C.UTF-8"));
  uselocale(LC_GLOBAL_LOCALE);

  ASSERT_GE(static_cast<size_t>(MB_LEN_MAX), MB_CUR_MAX);
  ASSERT_GE(MB_CUR_MAX, 4U);

  wchar_t wc;
  size_t n;

  // 1-byte character.
  n = mbrtowc(&wc, "x", MB_CUR_MAX, nullptr);
  EXPECT_EQ(1U, n);
  EXPECT_EQ(L'x', wc);
  // 2-byte character.
  n = mbrtowc(&wc, "\xc2\xa2", MB_CUR_MAX, nullptr);
  EXPECT_EQ(2U, n);
  EXPECT_EQ(L'¢', wc);
  // 3-byte character.
  n = mbrtowc(&wc, "\xe2\x82\xac", MB_CUR_MAX, nullptr);
  EXPECT_EQ(3U, n);
  EXPECT_EQ(L'€', wc);
  // 4-byte character.
  n = mbrtowc(&wc, "\xf0\xa4\xad\xa2", MB_CUR_MAX, nullptr);
  EXPECT_EQ(4U, n);
  EXPECT_EQ(L'��', wc);
}

TEST(wchar, open_wmemstream) {
  wchar_t* p = nullptr;
  size_t size = 0;
  FILE* fp = open_wmemstream(&p, &size);
  ASSERT_NE(EOF, fputws(L"hello, world!", fp));
  fclose(fp);

  ASSERT_STREQ(L"hello, world!", p);
  ASSERT_EQ(wcslen(L"hello, world!"), size);
  free(p);
}

TEST(wchar, open_wmemstream_EINVAL) {
#if defined(__BIONIC__)
  wchar_t* p;
  size_t size;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
  // Invalid buffer.
  ASSERT_ERRNO_FAILURE(EINVAL, nullptr, open_wmemstream(nullptr, &size));

  // Invalid size.
  ASSERT_ERRNO_FAILURE(EINVAL, nullptr, open_wmemstream(&p, nullptr));
#pragma clang diagnostic pop
#else
  GTEST_SKIP() << "This test is bionic-specific";
#endif
}

TEST(wchar, wcstol_EINVAL) {
  errno = 0;
  wcstol(L"123", nullptr, -1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstol(L"123", nullptr, 1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstol(L"123", nullptr, 37);
  ASSERT_ERRNO(EINVAL);
}

TEST(wchar, wcstoll_EINVAL) {
  errno = 0;
  wcstoll(L"123", nullptr, -1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoll(L"123", nullptr, 1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoll(L"123", nullptr, 37);
  ASSERT_ERRNO(EINVAL);
}

TEST(wchar, wcstoul_EINVAL) {
  errno = 0;
  wcstoul(L"123", nullptr, -1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoul(L"123", nullptr, 1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoul(L"123", nullptr, 37);
  ASSERT_ERRNO(EINVAL);
}

TEST(wchar, wcstoull_EINVAL) {
  errno = 0;
  wcstoull(L"123", nullptr, -1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoull(L"123", nullptr, 1);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoull(L"123", nullptr, 37);
  ASSERT_ERRNO(EINVAL);
}

TEST(wchar, wcstoll_l_EINVAL) {
  errno = 0;
  wcstoll_l(L"123", nullptr, -1, SAFE_LC_GLOBAL_LOCALE);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoll_l(L"123", nullptr, 1, SAFE_LC_GLOBAL_LOCALE);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoll_l(L"123", nullptr, 37, SAFE_LC_GLOBAL_LOCALE);
  ASSERT_ERRNO(EINVAL);
}

TEST(wchar, wcstoull_l_EINVAL) {
  errno = 0;
  wcstoull_l(L"123", nullptr, -1, SAFE_LC_GLOBAL_LOCALE);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoull_l(L"123", nullptr, 1, SAFE_LC_GLOBAL_LOCALE);
  ASSERT_ERRNO(EINVAL);
  errno = 0;
  wcstoull_l(L"123", nullptr, 37, SAFE_LC_GLOBAL_LOCALE);
  ASSERT_ERRNO(EINVAL);
}

TEST(wchar, wmempcpy) {
#if !defined(ANDROID_HOST_MUSL)
  wchar_t dst[6];
  ASSERT_EQ(&dst[4], wmempcpy(dst, L"hello"4));
#else
  GTEST_SKIP() << "musl doesn't have wmempcpy";
#endif
}

template <typename T>
using WcsToFloatFn = T (*)(const wchar_t*, wchar_t**);

template <typename T>
void TestSingleWcsToFloat(WcsToFloatFn<T> fn, const wchar_t* str,
                          T expected_value, ptrdiff_t expected_len) {
  wchar_t* p;
  ASSERT_EQ(expected_value, fn(str, &p));
  ASSERT_EQ(expected_len, p - str);
}

template <typename T>
void TestWcsToFloat(WcsToFloatFn<T> fn) {
  TestSingleWcsToFloat(fn, L"123"static_cast<T>(123.0L), 3);
  TestSingleWcsToFloat(fn, L"123#"static_cast<T>(123.0L), 3);
  TestSingleWcsToFloat(fn, L"   123 45"static_cast<T>(123.0L), 6);
  TestSingleWcsToFloat(fn, L"9.0"static_cast<T>(9.0L), 3);
  TestSingleWcsToFloat(fn, L"-9.0"static_cast<T>(-9.0L), 4);
  TestSingleWcsToFloat(fn, L" \t\v\f\r\n9.0"static_cast<T>(9.0L), 9);
}

template <typename T>
void TestWcsToFloatHexFloats(WcsToFloatFn<T> fn) {
  TestSingleWcsToFloat(fn, L"0.9e1"static_cast<T>(9.0L), 5);
  TestSingleWcsToFloat(fn, L"0x1.2p3"static_cast<T>(9.0L), 7);
  TestSingleWcsToFloat(fn, L"+1e+100"static_cast<T>(1e100L), 7);
  TestSingleWcsToFloat(fn, L"0x10000.80"static_cast<T>(65536.50L), 10);
}

template <typename T>
void TestWcsToFloatInfNan(WcsToFloatFn<T> fn) {
  ASSERT_TRUE(isnan(fn(L"+nan", nullptr)));
  ASSERT_TRUE(isnan(fn(L"nan", nullptr)));
  ASSERT_TRUE(isnan(fn(L"-nan", nullptr)));

  ASSERT_TRUE(isnan(fn(L"+nan(0xff)", nullptr)));
  ASSERT_TRUE(isnan(fn(L"nan(0xff)", nullptr)));
  ASSERT_TRUE(isnan(fn(L"-nan(0xff)", nullptr)));

  wchar_t* p;
  ASSERT_TRUE(isnan(fn(L"+nanny", &p)));
  ASSERT_STREQ(L"ny", p);
  ASSERT_TRUE(isnan(fn(L"nanny", &p)));
  ASSERT_STREQ(L"ny", p);
  ASSERT_TRUE(isnan(fn(L"-nanny", &p)));
  ASSERT_STREQ(L"ny", p);

  ASSERT_EQ(0, fn(L"muppet", &p));
  ASSERT_STREQ(L"muppet", p);
  ASSERT_EQ(0, fn(L"  muppet", &p));
  ASSERT_STREQ(L"  muppet", p);

  ASSERT_EQ(std::numeric_limits<T>::infinity(), fn(L"+inf", nullptr));
  ASSERT_EQ(std::numeric_limits<T>::infinity(), fn(L"inf", nullptr));
  ASSERT_EQ(-std::numeric_limits<T>::infinity(), fn(L"-inf", nullptr));

  ASSERT_EQ(std::numeric_limits<T>::infinity(), fn(L"+infinity", nullptr));
  ASSERT_EQ(std::numeric_limits<T>::infinity(), fn(L"infinity", nullptr));
  ASSERT_EQ(-std::numeric_limits<T>::infinity(), fn(L"-infinity", nullptr));

  ASSERT_EQ(std::numeric_limits<T>::infinity(), fn(L"+infinitude", &p));
  ASSERT_STREQ(L"initude", p);
  ASSERT_EQ(std::numeric_limits<T>::infinity(), fn(L"infinitude", &p));
  ASSERT_STREQ(L"initude", p);
  ASSERT_EQ(-std::numeric_limits<T>::infinity(), fn(L"-infinitude", &p));
  ASSERT_STREQ(L"initude", p);

  // Check case-insensitivity.
  ASSERT_EQ(std::numeric_limits<T>::infinity(), fn(L"InFiNiTy", nullptr));
  ASSERT_TRUE(isnan(fn(L"NaN", nullptr)));
}

TEST(wchar, wcstof) {
  TestWcsToFloat(wcstof);
}

TEST(wchar, wcstof_hex_floats) {
  TestWcsToFloatHexFloats(wcstof);
}

TEST(wchar, wcstof_hex_inf_nan) {
  TestWcsToFloatInfNan(wcstof);
}

TEST(wchar, wcstod) {
  TestWcsToFloat(wcstod);
}

TEST(wchar, wcstod_hex_floats) {
  TestWcsToFloatHexFloats(wcstod);
}

TEST(wchar, wcstod_hex_inf_nan) {
  TestWcsToFloatInfNan(wcstod);
}

TEST(wchar, wcstold) {
  TestWcsToFloat(wcstold);
}

TEST(wchar, wcstold_hex_floats) {
  TestWcsToFloatHexFloats(wcstold);
}

TEST(wchar, wcstold_hex_inf_nan) {
  TestWcsToFloatInfNan(wcstold);
}

TEST(wchar, wcstod_l) {
#if !defined(ANDROID_HOST_MUSL)
  EXPECT_EQ(1.23, wcstod_l(L"1.23", nullptr, SAFE_LC_GLOBAL_LOCALE));
#else
  GTEST_SKIP() << "musl doesn't have wcstod_l";
#endif
}

TEST(wchar, wcstof_l) {
#if !defined(ANDROID_HOST_MUSL)
  EXPECT_EQ(1.23f, wcstof_l(L"1.23", nullptr, SAFE_LC_GLOBAL_LOCALE));
#else
  GTEST_SKIP() << "musl doesn't have wcstof_l";
#endif
}

TEST(wchar, wcstol_l) {
#if !defined(ANDROID_HOST_MUSL)
  EXPECT_EQ(123L, wcstol_l(L"123", nullptr, 10, SAFE_LC_GLOBAL_LOCALE));
#else
  GTEST_SKIP() << "musl doesn't have wcstol_l";
#endif
}

TEST(wchar, wcstold_l) {
  EXPECT_EQ(1.23L, wcstold_l(L"1.23", nullptr, SAFE_LC_GLOBAL_LOCALE));
}

TEST(wchar, wcstoll_l) {
  EXPECT_EQ(123LL, wcstoll_l(L"123", nullptr, 10, SAFE_LC_GLOBAL_LOCALE));
}

TEST(wchar, wcstoul_l) {
#if !defined(ANDROID_HOST_MUSL)
  EXPECT_EQ(123UL, wcstoul_l(L"123", nullptr, 10, SAFE_LC_GLOBAL_LOCALE));
#else
  GTEST_SKIP() << "musl doesn't have wcstoul_l";
#endif
}

TEST(wchar, wcstoull_l) {
  EXPECT_EQ(123ULL, wcstoull_l(L"123", nullptr, 10, SAFE_LC_GLOBAL_LOCALE));
}

static void AssertWcwidthRange(wchar_t begin, wchar_t end, int expected) {
  for (wchar_t i = begin; i < end; ++i) {
    EXPECT_EQ(expected, wcwidth(i)) << static_cast<int>(i);
  }
}

TEST(wchar, wcwidth_NUL) {
  // NUL is defined to return 0 rather than -1, despite being a C0 control.
  EXPECT_EQ(0, wcwidth(0));
}

TEST(wchar, wcwidth_ascii) {
  AssertWcwidthRange(0x20, 0x7f, 1); // Non-C0 non-DEL ASCII.
}

TEST(wchar, wcwidth_controls) {
  AssertWcwidthRange(0x01, 0x20, -1); // C0 controls.
  EXPECT_EQ(-1, wcwidth(0x7f)); // DEL.
  AssertWcwidthRange(0x80, 0xa0, -1); // C1 controls.
}

TEST(wchar, wcwidth_non_spacing_and_enclosing_marks_and_format) {
  EXPECT_EQ(0, wcwidth(0x0300)); // Combining grave.
  EXPECT_EQ(0, wcwidth(0x20dd)); // Combining enclosing circle.
  EXPECT_EQ(0, wcwidth(0x200b)); // Zero width space.
}

TEST(wchar, wcwidth_non_spacing_special_cases) {
  // U+00AD is a soft hyphen, which normally shouldn't be rendered at all.
  // I think the assumption here is that you elide the soft hyphen character
  // completely in that case, and never call wcwidth() if you don't want to
  // render it as an actual hyphen. Whereas if you do want to render it,
  // you call wcwidth(), and 1 is the right answer. This is what Markus Kuhn's
  // original https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c did,
  // and glibc and iOS do the same.
  // See also: https://en.wikipedia.org/wiki/Soft_hyphen#Text_to_be_formatted_by_the_recipient
  EXPECT_EQ(1, wcwidth(0x00ad)); // Soft hyphen (SHY).

  // U+115F is the Hangeul choseong filler (for a degenerate composed
  // character missing an initial consonant (as opposed to one with a
  // leading ieung). Since the code points for combining jungseong (medial
  // vowels) and jongseong (trailing consonants) have width 0, the choseong
  // (initial consonant) has width 2 to cover the entire syllable. So unless
  // U+115f has width 2, a degenerate composed "syllable" without an initial
  // consonant or ieung would have a total width of 0, which is silly.
  // The following sequence is effectively "약" without the leading ieung...
  EXPECT_EQ(2, wcwidth(0x115f)); // Hangeul choseong filler.
  EXPECT_EQ(0, wcwidth(0x1163)); // Hangeul jungseong "ya".
  EXPECT_EQ(0, wcwidth(0x11a8)); // Hangeul jongseong "kiyeok".

  // U+1160, the jungseong filler, has width 0 because it must have been
  // preceded by either a choseong or choseong filler.
  EXPECT_EQ(0, wcwidth(0x1160));
}

TEST(wchar, wcwidth_cjk) {
  EXPECT_EQ(2, wcwidth(0x4e00)); // Start of CJK unified block.
  EXPECT_EQ(2, wcwidth(0x9fff)); // End of CJK unified block.
  EXPECT_EQ(2, wcwidth(0x3400)); // Start of CJK extension A block.
  EXPECT_EQ(2, wcwidth(0x4dbf)); // End of CJK extension A block.
  EXPECT_EQ(2, wcwidth(0x20000)); // Start of CJK extension B block.
  EXPECT_EQ(2, wcwidth(0x2a6df)); // End of CJK extension B block.
}

TEST(wchar, wcwidth_korean_combining_jamo) {
  AssertWcwidthRange(0x1160, 0x1200, 0); // Original range.
  EXPECT_EQ(0, wcwidth(0xd7b0)); // Newer.
  EXPECT_EQ(0, wcwidth(0xd7cb));
}

TEST(wchar, wcwidth_korean_jeongeul_syllables) {
  EXPECT_EQ(2, wcwidth(0xac00)); // Start of block.
  EXPECT_EQ(2, wcwidth(0xd7a3)); // End of defined code points as of Unicode 15.

  // Undefined characters at the end of the block currently have width 1,
  // but since they're undefined, we don't test that.
}

TEST(wchar, wcwidth_kana) {
  // Hiragana (most, not undefined).
  AssertWcwidthRange(0x3041, 0x3097, 2);
  // Katakana.
  AssertWcwidthRange(0x30a0, 0x3100, 2);
}

TEST(wchar, wcwidth_circled_two_digit_cjk) {
  // Circled two-digit CJK "speed sign" numbers are wide,
  // though EastAsianWidth is ambiguous.
  AssertWcwidthRange(0x3248, 0x3250, 2);
}

TEST(wchar, wcwidth_hexagrams) {
  // Hexagrams are wide, though EastAsianWidth is neutral.
  AssertWcwidthRange(0x4dc0, 0x4e00, 2);
}

TEST(wchar, wcwidth_default_ignorables) {
  AssertWcwidthRange(0xfff0, 0xfff8, 0); // Unassigned by default ignorable.
  EXPECT_EQ(0, wcwidth(0xe0000)); // ...through 0xe0fff.
}

TEST(wchar, wcwidth_hangeul_compatibility_jamo) {
  // These are actually the *compatibility* jamo code points, *not* the regular
  // jamo code points (U+1100-U+11FF) using a jungseong filler. If you use the
  // Android IME to type any of these, you get these code points.

  // (Half of) the Korean "crying" emoticon "ㅠㅠ".
  // Actually U+3160 "Hangeul Letter Yu" from Hangeul Compatibility Jamo.
  EXPECT_EQ(2, wcwidth(L'ㅠ'));
  // The two halves of the Korean internet shorthand "ㄱㅅ" (short for 감사).
  // Actually U+3131 "Hangeul Letter Kiyeok" and U+3145 "Hangeul Letter Sios"
  // from Hangeul Compatibility Jamo.
  EXPECT_EQ(2, wcwidth(L'ㄱ'));
  EXPECT_EQ(2, wcwidth(L'ㅅ'));
}

TEST(wchar, wcslen) {
  constexpr size_t array_len = 256 / sizeof(wchar_t);
  wchar_t wide_str[array_len];
  for (size_t i = 0; i < array_len; ++i) {
    wide_str[i] = i + 1;
  }

  for (size_t i = 0; i < array_len - 1; ++i) {
    const wchar_t old = wide_str[i];
    wide_str[i] = 0;
    EXPECT_EQ(wcslen(wide_str), i);
    wide_str[i] = old;
  }
}

static void DoWcslenTest(uint8_t* byte_buf, size_t byte_len) {
  // NOTE: This is intentionally left potentially-misaligned, because
  // historically bionic (and other BSD-derived libcs including iOS) allowed
  // it, though glibc does not.
  size_t len = byte_len / sizeof(wchar_t);
  if (len >= 1) {
    size_t pre_nul_len = len * sizeof(wchar_t) - sizeof(wchar_t);
    memset(byte_buf, (32 + (byte_len % 96)), pre_nul_len);
    // Because this is unaligned, use `memset` to set the 0, rather than
    // casting to `wchar_t *` and doing an unaligned store. The `memset` is
    // preferred by the standard & sanitizers.
    memset(byte_buf + pre_nul_len, 0sizeof(wchar_t));
    EXPECT_EQ(len - 1, wcslen(reinterpret_cast<const wchar_t*>(byte_buf)));
  }
}

TEST(wchar, wcslen_align) {
  RunSingleBufferAlignTest(LARGE, DoWcslenTest);
}

TEST(wchar, wcslen_overread) {
  RunSingleBufferOverreadTest(DoWcslenTest);
}

TEST(wchar, wcswidth) {
  EXPECT_EQ(2, wcswidth(L"abc"2));
  EXPECT_EQ(2, wcswidth(L"ab\t"2));
  EXPECT_EQ(-1, wcswidth(L"a\tb"2));
}

TEST(wchar, wcslcpy) {
#if defined(__BIONIC__)
  wchar_t dst[32];
  ASSERT_EQ(11U, wcslcpy(dst, L"hello world"3));
  ASSERT_STREQ(L"he", dst);
  ASSERT_EQ(11U, wcslcpy(dst, L"hello world"32));
  ASSERT_STREQ(L"hello world", dst);
#else
  GTEST_SKIP() << "no wcslcpy in glibc";
#endif
}

TEST(wchar, wcscat) {
  wchar_t dst[32];
  ASSERT_EQ(dst, wcscat(dst, L"hello"));
  ASSERT_STREQ(dst, L"hello");
  ASSERT_EQ(dst, wcscat(dst, L" world"));
  ASSERT_STREQ(dst, L"hello world");
}

TEST(wchar, wcscpy) {
  wchar_t dst[32];
  ASSERT_EQ(dst, wcscpy(dst, L"hello"));
  ASSERT_STREQ(dst, L"hello");
  ASSERT_EQ(dst, wcscpy(dst, L"world"));
  ASSERT_STREQ(dst, L"world");
}

TEST(wchar, wcscasecmp) {
  ASSERT_EQ(0, wcscasecmp(L"hello", L"HELLO"));
  ASSERT_TRUE(wcscasecmp(L"hello1", L"HELLO2") < 0);
  ASSERT_TRUE(wcscasecmp(L"hello2", L"HELLO1") > 0);
  ASSERT_TRUE(wcscasecmp(L"hello", L"HELL") > 0);
  ASSERT_TRUE(wcscasecmp(L"hell", L"HELLO") < 0);
}

TEST(wchar, wcsdup) {
  wchar_t* s = wcsdup(L"hello");
  ASSERT_STREQ(s, L"hello");
  free(s);
}

TEST(wchar, wcslcat) {
#if defined(__BIONIC__)
  wchar_t dst[4] = {};
  ASSERT_EQ(1U, wcslcat(dst, L"a"4));
  ASSERT_EQ(7U, wcslcat(dst, L"bcdefg"4));
  ASSERT_STREQ(dst, L"abc");
#else
  GTEST_SKIP() << "no wcslcpy in glibc";
#endif
}

TEST(wchar, wcsncasecmp) {
  ASSERT_EQ(0, wcsncasecmp(L"foo", L"bar"0));

  ASSERT_EQ(0, wcsncasecmp(L"hello1", L"HELLO2"5));
  ASSERT_TRUE(wcsncasecmp(L"hello1", L"HELLO2"6) < 0);
  ASSERT_TRUE(wcsncasecmp(L"hello2", L"HELLO1"6) > 0);
  ASSERT_TRUE(wcsncasecmp(L"hello", L"HELL"5) > 0);
  ASSERT_TRUE(wcsncasecmp(L"hell", L"HELLO"5) < 0);
}

TEST(wchar, wcsncat) {
  wchar_t dst[32];
  ASSERT_EQ(dst, wcsncat(dst, L"hello, world!"5));
  ASSERT_STREQ(dst, L"hello");
  ASSERT_EQ(dst, wcsncat(dst, L"hello, world!"0));
  ASSERT_STREQ(dst, L"hello");
  ASSERT_EQ(dst, wcsncat(dst, L", world!"8));
  ASSERT_STREQ(dst, L"hello, world!");
}

TEST(wchar, wcsncmp) {
  ASSERT_EQ(0, wcsncmp(L"foo", L"bar"0));
  ASSERT_EQ(0, wcsncmp(L"aaaa", L"aaab"3));
  ASSERT_TRUE(wcsncmp(L"aaaa", L"aaab"4) < 0);
  ASSERT_TRUE(wcsncmp(L"aaab", L"aaaa"4) > 0);
}

TEST(wchar, wcsnlen) {
  ASSERT_EQ(2U, wcsnlen(L"hello"2));
  ASSERT_EQ(5U, wcsnlen(L"hello"5));
  ASSERT_EQ(5U, wcsnlen(L"hello"666));
}

TEST(wchar, wmemchr) {
  const wchar_t* s = L"hello, world!";
  ASSERT_EQ(s, wmemchr(s, L'h'13));
  ASSERT_EQ(s + 5, wmemchr(s, L','13));
  ASSERT_EQ(s + 12, wmemchr(s, L'!'13));
  ASSERT_EQ(nullptr, wmemchr(s, L'a'13));
}

static void DoWmemchrTest(uint8_t* byte_buf, size_t byte_len) {
  // Much like DoWcslenTest, this intentionally supports unaligned buffers, for
  // compatibility with other libcs.
  const size_t len = byte_len / sizeof(wchar_t);
  if (!len) {
    return;
  }

  const size_t pre_nul_len = len * sizeof(wchar_t) - sizeof(wchar_t);
  memset(byte_buf, (32 + (byte_len % 96)), pre_nul_len);
  const wchar_t needle = L'a';
  memcpy(byte_buf + pre_nul_len, &needle, sizeof(needle));
  EXPECT_EQ(reinterpret_cast<const wchar_t*>(byte_buf + pre_nul_len),
            wmemchr(reinterpret_cast<const wchar_t*>(byte_buf), L'a', len));
}

TEST(wchar, wmemchr_align) {
  RunSingleBufferAlignTest(LARGE, DoWmemchrTest);
}

TEST(wchar, wmemchr_overread) {
  RunSingleBufferOverreadTest(DoWmemchrTest);
}

TEST(wchar, wmemcmp) {
  ASSERT_EQ(0, wmemcmp(L"aaaa", L"aaab"3));
  ASSERT_TRUE(wmemcmp(L"aaaa", L"aaab"4) < 0);
  ASSERT_TRUE(wmemcmp(L"aaab", L"aaaa"4) > 0);
}

TEST(wchar, wmemcpy) {
  wchar_t dst[32] = {};
  ASSERT_EQ(dst, wmemcpy(dst, L"hello"5));
  ASSERT_STREQ(dst, L"hello");
}

TEST(wchar, wmemmove) {
  wchar_t dst[32] = {};
  ASSERT_EQ(dst, wmemmove(dst, L"hello"5));
  ASSERT_STREQ(dst, L"hello");
}

TEST(wchar, wmemset) {
  wchar_t dst[4] = {};
  ASSERT_EQ(dst, wmemset(dst, 0x12345678, 3));
  ASSERT_EQ(dst[0], wchar_t(0x12345678));
  ASSERT_EQ(dst[1], wchar_t(0x12345678));
  ASSERT_EQ(dst[2], wchar_t(0x12345678));
  ASSERT_EQ(dst[3], wchar_t(0));
  // A zero length touches nothing.
  ASSERT_EQ(dst, wmemset(dst, L'y'0));
  ASSERT_EQ(dst[0], wchar_t(0x12345678));
}

// We special-case wmemset() to 0, so we need to test that explicitly.
TEST(wchar, wmemset_0) {
  wchar_t dst[4] = { 0x12345678, 0x12345678, 0x12345678, 0x12345678 };
  ASSERT_EQ(dst, wmemset(dst, 03));
  ASSERT_EQ(dst[0], wchar_t(0));
  ASSERT_EQ(dst[1], wchar_t(0));
  ASSERT_EQ(dst[2], wchar_t(0));
  ASSERT_EQ(dst[3], wchar_t(0x12345678));
  // A zero length touches nothing.
  dst[0] = wchar_t(0x12345678);
  ASSERT_EQ(dst, wmemset(dst, 00));
  ASSERT_EQ(dst[0], wchar_t(0x12345678));
}

TEST(wchar, btowc) {
  // This function only works for single-byte "wide" characters.
  ASSERT_EQ(wint_t('a'), btowc('a'));
  // It _truncates_ the input to unsigned char.
  ASSERT_EQ(wint_t(0x66), btowc(0x666));
  // And rejects anything with the top bit set.
  ASSERT_EQ(WEOF, btowc(0xa0));
}

TEST(wchar, wctob) {
  // This function only works for single-byte "wide" characters.
  ASSERT_EQ('a', wctob(L'a'));
  // And rejects anything that would have the top bit set.
  ASSERT_EQ(EOF, wctob(0xa0));
  // There's no truncation here (unlike btowc()),
  // so this is rejected rather than seen as 0x66 ('f').
  ASSERT_EQ(EOF, wctob(0x666));
}

TEST(wchar, wcscoll_smoke) {
  ASSERT_TRUE(wcscoll(L"aab", L"aac") < 0);
  ASSERT_TRUE(wcscoll(L"aab", L"aab") == 0);
  ASSERT_TRUE(wcscoll(L"aac", L"aab") > 0);
}

TEST(wchar, strcoll_l_smoke) {
  // bionic just forwards to wcscoll(3).
  ASSERT_TRUE(wcscoll_l(L"aab", L"aac", LC_GLOBAL_LOCALE) < 0);
  ASSERT_TRUE(wcscoll_l(L"aab", L"aab", LC_GLOBAL_LOCALE) == 0);
  ASSERT_TRUE(wcscoll_l(L"aac", L"aab", LC_GLOBAL_LOCALE) > 0);
}

TEST(wchar, wcsxfrm_smoke) {
  const wchar_t* src1 = L"aab";
  wchar_t dst1[16] = {};
  // Dry run.
  ASSERT_EQ(wcsxfrm(dst1, src1, 0), 3U);
  ASSERT_STREQ(dst1, L"");
  // Really do it.
  ASSERT_EQ(wcsxfrm(dst1, src1, sizeof(dst1)/sizeof(wchar_t)), 3U);

  const wchar_t* src2 = L"aac";
  wchar_t dst2[16] = {};
  // Dry run.
  ASSERT_EQ(wcsxfrm(dst2, src2, 0), 3U);
  ASSERT_STREQ(dst2, L"");
  // Really do it.
  ASSERT_EQ(wcsxfrm(dst2, src2, sizeof(dst2)/sizeof(wchar_t)), 3U);

  // The "transform" of two different strings should cause different outputs.
  ASSERT_TRUE(wcscmp(dst1, dst2) < 0);
}

TEST(wchar, strxfrm_l_smoke) {
  // bionic just forwards to wcsxfrm(3), so this is a subset of the
  // strxfrm test.
  const wchar_t* src1 = L"aab";
  wchar_t dst1[16] = {};
  ASSERT_EQ(wcsxfrm_l(dst1, src1, 0, LC_GLOBAL_LOCALE), 3U);
  ASSERT_STREQ(dst1, L"");
  ASSERT_EQ(wcsxfrm_l(dst1, src1, sizeof(dst1)/sizeof(wchar_t), LC_GLOBAL_LOCALE), 3U);
}

TEST(wchar, wcspbrk) {
  EXPECT_EQ(nullptr, wcspbrk(L"hello", L""));
  EXPECT_STREQ(L"hello", wcspbrk(L"hello", L"ehl"));
  EXPECT_STREQ(L"llo", wcspbrk(L"hello", L"l"));
  EXPECT_STREQ(L" world", wcspbrk(L"hello world", L"\t "));
}

TEST(wchar, wcsspn) {
  EXPECT_EQ(0u, wcsspn(L"hello", L""));
  EXPECT_EQ(4u, wcsspn(L"hello", L"ehl"));
  EXPECT_EQ(5u, wcsspn(L"hello", L"ehlo"));
  EXPECT_EQ(5u, wcsspn(L"hello world", L"abcdefghijklmnopqrstuvwxyz"));
}

TEST(wchar, wcscspn) {
  EXPECT_EQ(5u, wcscspn(L"hello", L""));
  EXPECT_EQ(0u, wcscspn(L"hello", L"ehl"));
  EXPECT_EQ(5u, wcscspn(L"hello", L"abc"));
  EXPECT_EQ(5u, wcscspn(L"hello world", L" "));
}

TEST(wchar, wcstok) {
  wchar_t* p;

  wchar_t empty[] = L"";
  EXPECT_EQ(nullptr, wcstok(empty, L":", &p));

  wchar_t only_delimiters[] = L":::";
  EXPECT_EQ(nullptr, wcstok(only_delimiters, L":", &p));

  // wcstok() doesn't return empty strings.
  wchar_t str[] = L":hello:world:::foo:";
  EXPECT_STREQ(L"hello", wcstok(str, L":", &p));
  EXPECT_STREQ(L"world", wcstok(nullptr, L":", &p));
  EXPECT_STREQ(L"foo", wcstok(nullptr, L":", &p));
  EXPECT_EQ(nullptr, wcstok(nullptr, L":", &p));
  // Repeated calls after the first nullptr keep returning nullptr.
  for (size_t i = 0; i < 1024; ++i) {
    EXPECT_EQ(nullptr, p);
    EXPECT_EQ(nullptr, wcstok(nullptr, L":", &p));
  }

  // Some existing implementations have a separate return path for this.
  wchar_t non_empty_at_end[] = L"hello:world";
  EXPECT_STREQ(L"hello", wcstok(non_empty_at_end, L":", &p));
  EXPECT_STREQ(L"world", wcstok(nullptr, L":", &p));
  for (size_t i = 0; i < 1024; ++i) {
    EXPECT_EQ(nullptr, p);
    EXPECT_EQ(nullptr, wcstok(nullptr, L":", &p));
  }
}

TEST(wchar, fwide_byte) {
  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/version""re"), fclose);
  // Unknown orientation.
  EXPECT_EQ(0, fwide(fp.get(), 0));
  // Getting a byte sets the orientation to bytes.
  EXPECT_EQ('L', fgetc(fp.get()));
  EXPECT_TRUE(fwide(fp.get(), 0) < 0);
  // We don't prevent you from mixing and matching...
  EXPECT_EQ(wint_t(L'i'), fgetwc(fp.get()));
  // ...but fwide() remembers what you _first_ did.
  EXPECT_TRUE(fwide(fp.get(), 0) < 0);
}

TEST(wchar, fwide_wide_char) {
  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/version""re"), fclose);
  // Unknown orientation.
  EXPECT_EQ(0, fwide(fp.get(), 0));
  // Getting a wide character sets the orientation to wide.
  EXPECT_EQ(wint_t(L'L'), fgetwc(fp.get()));
  EXPECT_TRUE(fwide(fp.get(), 0) > 0);
  // We don't prevent you from mixing and matching...
  EXPECT_EQ('i', fgetc(fp.get()));
  // ...but fwide() remembers what you _first_ did.
  EXPECT_TRUE(fwide(fp.get(), 0) > 0);
}

TEST(wchar, fwide_set_byte) {
  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/version""re"), fclose);
  // Unknown orientation.
  EXPECT_EQ(0, fwide(fp.get(), 0));
  // You can set it to what you want...
  EXPECT_TRUE(fwide(fp.get(), -123) < 0);
  // But only once...
  EXPECT_TRUE(fwide(fp.get(), 123) < 0);
  // And you can still use the stream however you like.
  EXPECT_EQ('L', fgetc(fp.get()));
  EXPECT_EQ(wint_t(L'i'), fgetwc(fp.get()));
}

TEST(wchar, fwide_set_wide_char) {
  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("/proc/version""re"), fclose);
  // Unknown orientation.
  EXPECT_EQ(0, fwide(fp.get(), 0));
  // You can set it to what you want...
  EXPECT_TRUE(fwide(fp.get(), 123) > 0);
  // But only once...
  EXPECT_TRUE(fwide(fp.get(), -123) > 0);
  // And you can still use the stream however you like.
  EXPECT_EQ('L', fgetc(fp.get()));
  EXPECT_EQ(wint_t(L'i'), fgetwc(fp.get()));
}

Messung V0.5 in Prozent
C=91 H=94 G=92

¤ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet am  2026-06-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.