Merge "linker/Android.bp: factor out duplicated flags." into main
diff --git a/benchmarks/README.md b/benchmarks/README.md
index 3819b1a..6b6c448 100644
--- a/benchmarks/README.md
+++ b/benchmarks/README.md
@@ -19,6 +19,12 @@
By default, `bionic-benchmarks` runs all of the benchmarks in alphabetical order. Pass
`--benchmark_filter=getpid` to run just the benchmarks with "getpid" in their name.
+Note that we also build _static_ benchmark binaries.
+They're useful for testing on devices running different versions of Android, or running non-Android OSes.
+Those binaries are called `bionic-benchmarks-static` instead.
+Copy from `out/target/product/<device>/symbols/data/benchmarktest64/bionic-benchmarks-static` instead of
+`out/target/product/<device>/data/benchmarktest64/bionic-benchmarks-static` if you want symbols for perf(1).
+
### Host benchmarks
See the `benchmarks/run-on-host.sh` script. The host benchmarks can be run with 32-bit or 64-bit
diff --git a/docs/status.md b/docs/status.md
index e0364a8..7ebd195 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -283,23 +283,31 @@
libc function count over time:
-| OS | API level | Function count |
-|-------|-----------|----------------|
-| J | 16 | 842 |
-| J MR1 | 17 | 870 |
-| J MR2 | 18 | 878 |
-| K | 19 | 893 |
-| L | 21 | 1118 |
-| M | 23 | 1183 |
-| N | 24 | 1228 |
-| O | 26 | 1280 |
-| P | 28 | 1378 |
-| Q | 29 | 1394 |
+| API level | Function count |
+|-----------|----------------|
+| 16 | 842 |
+| 17 | 870 |
+| 18 | 878 |
+| 19 | 893 |
+| 21 | 1016 |
+| 22 | 1038 |
+| 23 | 1103 |
+| 24 | 1147 |
+| 25 | 1147 |
+| 26 | 1199 |
+| 27 | 1199 |
+| 28 | 1298 |
+| 29 | 1312 |
+| 30 | 1368 |
+| 31 | 1379 |
+| 32 | 1379 |
+| 33 | 1386 |
+| 34 | 1392 |
Data collected by:
```
-ndk-r21$ for i in `ls -1v platforms/android-*/arch-arm/usr/lib/libc.so` ; do \
- echo $i; nm $i | grep -w T | wc -l ; done
+ndk-r26c$ for i in `ls -1v toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/*/libc.so` ; \
+ do echo $i; nm $i | grep -w T | wc -l ; done
```
### libm
diff --git a/libc/bionic/wctype.cpp b/libc/bionic/wctype.cpp
index 082dada..b37f17b 100644
--- a/libc/bionic/wctype.cpp
+++ b/libc/bionic/wctype.cpp
@@ -34,6 +34,7 @@
#include <string.h>
#include <wchar.h>
+#include "bionic/macros.h"
#include "private/icu.h"
enum {
@@ -95,21 +96,12 @@
int iswxdigit_l(wint_t c, locale_t) { return iswxdigit(c); }
int iswctype(wint_t wc, wctype_t char_class) {
- switch (char_class) {
- case WC_TYPE_ALNUM: return iswalnum(wc);
- case WC_TYPE_ALPHA: return iswalpha(wc);
- case WC_TYPE_BLANK: return iswblank(wc);
- case WC_TYPE_CNTRL: return iswcntrl(wc);
- case WC_TYPE_DIGIT: return iswdigit(wc);
- case WC_TYPE_GRAPH: return iswgraph(wc);
- case WC_TYPE_LOWER: return iswlower(wc);
- case WC_TYPE_PRINT: return iswprint(wc);
- case WC_TYPE_PUNCT: return iswpunct(wc);
- case WC_TYPE_SPACE: return iswspace(wc);
- case WC_TYPE_UPPER: return iswupper(wc);
- case WC_TYPE_XDIGIT: return iswxdigit(wc);
- default: return 0;
- }
+ if (char_class < WC_TYPE_ALNUM || char_class > WC_TYPE_XDIGIT) return 0;
+ static int (*fns[])(wint_t) = {
+ iswalnum, iswalpha, iswblank, iswcntrl, iswdigit, iswgraph,
+ iswlower, iswprint, iswpunct, iswspace, iswupper, iswxdigit
+ };
+ return fns[char_class - WC_TYPE_ALNUM](wc);
}
int iswctype_l(wint_t wc, wctype_t char_class, locale_t) {
@@ -117,10 +109,7 @@
}
wint_t towlower(wint_t wc) {
- if (wc < 0x80) {
- if (wc >= 'A' && wc <= 'Z') return wc | 0x20;
- return wc;
- }
+ if (wc < 0x80) return tolower(wc);
typedef UChar32 (*FnT)(UChar32);
static auto u_tolower = reinterpret_cast<FnT>(__find_icu_symbol("u_tolower"));
@@ -128,12 +117,7 @@
}
wint_t towupper(wint_t wc) {
- if (wc < 0x80) {
- // Using EOR rather than AND makes no difference on arm, but saves an
- // instruction on arm64.
- if (wc >= 'a' && wc <= 'z') return wc ^ 0x20;
- return wc;
- }
+ if (wc < 0x80) return toupper(wc);
typedef UChar32 (*FnT)(UChar32);
static auto u_toupper = reinterpret_cast<FnT>(__find_icu_symbol("u_toupper"));
@@ -144,14 +128,13 @@
wint_t towlower_l(wint_t c, locale_t) { return towlower(c); }
wctype_t wctype(const char* property) {
- static const char* const properties[WC_TYPE_MAX] = {
- "<invalid>",
+ static const char* const properties[WC_TYPE_MAX - 1] = {
"alnum", "alpha", "blank", "cntrl", "digit", "graph",
"lower", "print", "punct", "space", "upper", "xdigit"
};
- for (size_t i = 0; i < WC_TYPE_MAX; ++i) {
+ for (size_t i = 0; i < arraysize(properties); ++i) {
if (!strcmp(properties[i], property)) {
- return static_cast<wctype_t>(i);
+ return static_cast<wctype_t>(WC_TYPE_ALNUM + i);
}
}
return static_cast<wctype_t>(0);
@@ -167,6 +150,7 @@
wctrans_t wctrans(const char* name) {
if (strcmp(name, "tolower") == 0) return wctrans_tolower;
if (strcmp(name, "toupper") == 0) return wctrans_toupper;
+ errno = EINVAL;
return nullptr;
}
@@ -178,7 +162,7 @@
if (t == wctrans_tolower) return towlower(c);
if (t == wctrans_toupper) return towupper(c);
errno = EINVAL;
- return 0;
+ return c;
}
wint_t towctrans_l(wint_t c, wctrans_t t, locale_t) {
diff --git a/libc/include/dlfcn.h b/libc/include/dlfcn.h
index a90c4f8..d65a409 100644
--- a/libc/include/dlfcn.h
+++ b/libc/include/dlfcn.h
@@ -146,7 +146,11 @@
*/
#define RTLD_LOCAL 0
-/** Not supported on Android; Android always uses RTLD_NOW. */
+/**
+ * Not supported on Android. Android always uses RTLD_NOW for security reasons.
+ * Resolving all undefined symbols before dlopen() returns means that RELRO
+ * protections can be applied to the PLT before dlopen() returns.
+ */
#define RTLD_LAZY 0x00001
/** A dlopen() flag to resolve all undefined symbols before dlopen() returns. */
diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp
index 0d442b4..6be899d 100644
--- a/libc/malloc_debug/Config.cpp
+++ b/libc/malloc_debug/Config.cpp
@@ -212,6 +212,10 @@
"log_allocator_stats_on_signal",
{LOG_ALLOCATOR_STATS_ON_SIGNAL, &Config::VerifyValueEmpty},
},
+ {
+ "log_allocator_stats_on_exit",
+ {LOG_ALLOCATOR_STATS_ON_EXIT, &Config::VerifyValueEmpty},
+ },
};
bool Config::ParseValue(const std::string& option, const std::string& value, size_t min_value,
diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h
index 8551712..4840d43 100644
--- a/libc/malloc_debug/Config.h
+++ b/libc/malloc_debug/Config.h
@@ -49,6 +49,7 @@
constexpr uint64_t CHECK_UNREACHABLE_ON_SIGNAL = 0x2000;
constexpr uint64_t BACKTRACE_SPECIFIC_SIZES = 0x4000;
constexpr uint64_t LOG_ALLOCATOR_STATS_ON_SIGNAL = 0x8000;
+constexpr uint64_t LOG_ALLOCATOR_STATS_ON_EXIT = 0x10000;
// In order to guarantee posix compliance, set the minimum alignment
// to 8 bytes for 32 bit systems and 16 bytes for 64 bit systems.
diff --git a/libc/malloc_debug/LogAllocatorStats.cpp b/libc/malloc_debug/LogAllocatorStats.cpp
index 6d1434e..ee6bfdf 100644
--- a/libc/malloc_debug/LogAllocatorStats.cpp
+++ b/libc/malloc_debug/LogAllocatorStats.cpp
@@ -43,13 +43,17 @@
g_call_mallopt = true;
}
+void Log() {
+ info_log("Logging allocator stats...");
+ if (mallopt(M_LOG_STATS, 0) == 0) {
+ error_log("mallopt(M_LOG_STATS, 0) call failed.");
+ }
+}
+
void CheckIfShouldLog() {
bool expected = true;
if (g_call_mallopt.compare_exchange_strong(expected, false)) {
- info_log("Logging allocator stats...");
- if (mallopt(M_LOG_STATS, 0) == 0) {
- error_log("mallopt(M_LOG_STATS, 0) call failed.");
- }
+ Log();
}
}
diff --git a/libc/malloc_debug/LogAllocatorStats.h b/libc/malloc_debug/LogAllocatorStats.h
index 99e0738..ded4f94 100644
--- a/libc/malloc_debug/LogAllocatorStats.h
+++ b/libc/malloc_debug/LogAllocatorStats.h
@@ -35,6 +35,8 @@
bool Initialize(const Config& config);
+void Log();
+
void CheckIfShouldLog();
} // namespace LogAllocatorStats
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp
index 6d88092..3743852 100644
--- a/libc/malloc_debug/malloc_debug.cpp
+++ b/libc/malloc_debug/malloc_debug.cpp
@@ -461,6 +461,10 @@
getpid()).c_str());
}
+ if (g_debug->config().options() & LOG_ALLOCATOR_STATS_ON_EXIT) {
+ LogAllocatorStats::Log();
+ }
+
backtrace_shutdown();
// In order to prevent any issues of threads freeing previous pointers
diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
index c79d052..d33f9cd 100644
--- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
@@ -861,6 +861,24 @@
ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
}
+TEST_F(MallocDebugConfigTest, log_allocator_stats_on_exit) {
+ ASSERT_TRUE(InitConfig("log_allocator_stats_on_exit")) << getFakeLogPrint();
+ ASSERT_EQ(LOG_ALLOCATOR_STATS_ON_EXIT, config->options());
+
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugConfigTest, trigger_log_allocator_stats_on_exit_fail) {
+ ASSERT_FALSE(InitConfig("log_allocator_stats_on_exit=200")) << getFakeLogPrint();
+
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ std::string log_msg(
+ "6 malloc_debug malloc_testing: value set for option 'log_allocator_stats_on_exit' "
+ "which does not take a value\n");
+ ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
+}
+
TEST_F(MallocDebugConfigTest, size) {
ASSERT_TRUE(InitConfig("backtrace_size=37")) << getFakeLogPrint();
ASSERT_EQ(BACKTRACE_SPECIFIC_SIZES, config->options());
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index ef8d235..c808dc0 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -426,7 +426,7 @@
Init(
"guard backtrace backtrace_enable_on_signal fill expand_alloc free_track leak_track "
"record_allocs verify_pointers abort_on_error verbose check_unreachable_on_signal "
- "log_allocator_stats_on_signal");
+ "log_allocator_stats_on_signal log_allocator_stats_on_exit");
VerifyAllocCalls(true);
}
@@ -2844,6 +2844,25 @@
}
}
+TEST_F(MallocDebugTest, log_allocator_stats_on_exit) {
+ Init("log_allocator_stats_on_exit");
+
+ void* pointer = debug_malloc(110);
+ ASSERT_TRUE(pointer != nullptr);
+ debug_free(pointer);
+
+ debug_finalize();
+
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ if (!running_with_hwasan()) {
+ // Do an exact match because the mallopt should not fail in normal operation.
+ ASSERT_STREQ("4 malloc_debug Logging allocator stats...\n", getFakeLogPrint().c_str());
+ } else {
+ // mallopt fails with hwasan, so just verify that the message is present.
+ ASSERT_MATCH(getFakeLogPrint(), "4 malloc_debug Logging allocator stats...\\n");
+ }
+}
+
TEST_F(MallocDebugTest, backtrace_only_some_sizes_with_backtrace_size) {
Init("leak_track backtrace backtrace_size=120");
diff --git a/libc/system_properties/prop_area.cpp b/libc/system_properties/prop_area.cpp
index a816a38..9b153ca 100644
--- a/libc/system_properties/prop_area.cpp
+++ b/libc/system_properties/prop_area.cpp
@@ -339,8 +339,7 @@
uint_least32_t left_offset = atomic_load_explicit(&trie->left, memory_order_relaxed);
if (left_offset != 0) {
- const int err = foreach_property(to_prop_trie_node(&trie->left), propfn, cookie);
- if (err < 0) return false;
+ if (!foreach_property(to_prop_trie_node(&trie->left), propfn, cookie)) return false;
}
uint_least32_t prop_offset = atomic_load_explicit(&trie->prop, memory_order_relaxed);
if (prop_offset != 0) {
@@ -350,13 +349,11 @@
}
uint_least32_t children_offset = atomic_load_explicit(&trie->children, memory_order_relaxed);
if (children_offset != 0) {
- const int err = foreach_property(to_prop_trie_node(&trie->children), propfn, cookie);
- if (err < 0) return false;
+ if (!foreach_property(to_prop_trie_node(&trie->children), propfn, cookie)) return false;
}
uint_least32_t right_offset = atomic_load_explicit(&trie->right, memory_order_relaxed);
if (right_offset != 0) {
- const int err = foreach_property(to_prop_trie_node(&trie->right), propfn, cookie);
- if (err < 0) return false;
+ if (!foreach_property(to_prop_trie_node(&trie->right), propfn, cookie)) return false;
}
return true;
@@ -371,6 +368,6 @@
return find_property(root_node(), name, namelen, value, valuelen, true);
}
-bool prop_area::foreach (void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
+bool prop_area::foreach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
return foreach_property(root_node(), propfn, cookie);
}
diff --git a/linker/Android.bp b/linker/Android.bp
index 563cf3d..d82e687 100644
--- a/linker/Android.bp
+++ b/linker/Android.bp
@@ -394,6 +394,10 @@
},
afdo: true,
+
+ // FIXME: Workaround compat issue with obfuscation libraries.
+ // http://b/352456802
+ lto_O0: true,
}
// ========================================================
diff --git a/tests/Android.bp b/tests/Android.bp
index ee34666..d2a3110 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -361,8 +361,7 @@
cc_test_library {
name: "clang_diagnostic_tests",
cflags: [
- "-Xclang",
- "-verify",
+ "-Xclang -verify",
],
srcs: ["sys_ioctl_diag_test.cpp"],
}
diff --git a/tests/ctype_test.cpp b/tests/ctype_test.cpp
index 826d39a..18fbfc0 100644
--- a/tests/ctype_test.cpp
+++ b/tests/ctype_test.cpp
@@ -293,40 +293,57 @@
}
TEST(ctype, toascii) {
- EXPECT_EQ('a', toascii('a'));
- EXPECT_EQ('a', toascii(0x80 | 'a'));
+ // POSIX explicitly says that toascii() returns (c & 0x7f),
+ // so there's no EOF-preserving behavior here and we start from 0.
+ for (int i = 0; i < kMax; ++i) {
+ if (i <= 0x7f) {
+ EXPECT_EQ(i, toascii(i));
+ } else {
+ EXPECT_EQ(i & 0x7f, toascii(i));
+ }
+ }
}
TEST(ctype, tolower) {
EXPECT_EQ('!', tolower('!'));
EXPECT_EQ('a', tolower('a'));
EXPECT_EQ('a', tolower('A'));
+ EXPECT_EQ('z', tolower('z'));
+ EXPECT_EQ('z', tolower('Z'));
}
TEST(ctype, tolower_l) {
EXPECT_EQ('!', tolower_l('!', LC_GLOBAL_LOCALE));
EXPECT_EQ('a', tolower_l('a', LC_GLOBAL_LOCALE));
EXPECT_EQ('a', tolower_l('A', LC_GLOBAL_LOCALE));
+ EXPECT_EQ('z', tolower_l('z', LC_GLOBAL_LOCALE));
+ EXPECT_EQ('z', tolower_l('Z', LC_GLOBAL_LOCALE));
}
TEST(ctype, _tolower) {
// _tolower may mangle characters for which isupper is false.
EXPECT_EQ('a', _tolower('A'));
+ EXPECT_EQ('z', _tolower('Z'));
}
TEST(ctype, toupper) {
EXPECT_EQ('!', toupper('!'));
EXPECT_EQ('A', toupper('a'));
EXPECT_EQ('A', toupper('A'));
+ EXPECT_EQ('Z', toupper('z'));
+ EXPECT_EQ('Z', toupper('Z'));
}
TEST(ctype, toupper_l) {
EXPECT_EQ('!', toupper_l('!', LC_GLOBAL_LOCALE));
EXPECT_EQ('A', toupper_l('a', LC_GLOBAL_LOCALE));
EXPECT_EQ('A', toupper_l('A', LC_GLOBAL_LOCALE));
+ EXPECT_EQ('Z', toupper_l('z', LC_GLOBAL_LOCALE));
+ EXPECT_EQ('Z', toupper_l('Z', LC_GLOBAL_LOCALE));
}
TEST(ctype, _toupper) {
// _toupper may mangle characters for which islower is false.
EXPECT_EQ('A', _toupper('a'));
+ EXPECT_EQ('Z', _toupper('z'));
}
diff --git a/tests/wctype_test.cpp b/tests/wctype_test.cpp
index 0f07956..f4b7a8f 100644
--- a/tests/wctype_test.cpp
+++ b/tests/wctype_test.cpp
@@ -109,6 +109,8 @@
EXPECT_EQ(wint_t('!'), towlower(L'!'));
EXPECT_EQ(wint_t('a'), towlower(L'a'));
EXPECT_EQ(wint_t('a'), towlower(L'A'));
+ EXPECT_EQ(wint_t('z'), towlower(L'z'));
+ EXPECT_EQ(wint_t('z'), towlower(L'Z'));
if (have_dl()) {
EXPECT_EQ(wint_t(L'ç'), towlower(L'ç'));
EXPECT_EQ(wint_t(L'ç'), towlower(L'Ç'));
@@ -125,6 +127,8 @@
EXPECT_EQ(wint_t('!'), towlower_l(L'!', l.l));
EXPECT_EQ(wint_t('a'), towlower_l(L'a', l.l));
EXPECT_EQ(wint_t('a'), towlower_l(L'A', l.l));
+ EXPECT_EQ(wint_t('z'), towlower_l(L'z', l.l));
+ EXPECT_EQ(wint_t('z'), towlower_l(L'Z', l.l));
if (have_dl()) {
EXPECT_EQ(wint_t(L'ç'), towlower_l(L'ç', l.l));
EXPECT_EQ(wint_t(L'ç'), towlower_l(L'Ç', l.l));
@@ -140,6 +144,8 @@
EXPECT_EQ(wint_t('!'), towupper(L'!'));
EXPECT_EQ(wint_t('A'), towupper(L'a'));
EXPECT_EQ(wint_t('A'), towupper(L'A'));
+ EXPECT_EQ(wint_t('Z'), towupper(L'z'));
+ EXPECT_EQ(wint_t('Z'), towupper(L'Z'));
if (have_dl()) {
EXPECT_EQ(wint_t(L'Ç'), towupper(L'ç'));
EXPECT_EQ(wint_t(L'Ç'), towupper(L'Ç'));
@@ -156,6 +162,8 @@
EXPECT_EQ(wint_t('!'), towupper_l(L'!', l.l));
EXPECT_EQ(wint_t('A'), towupper_l(L'a', l.l));
EXPECT_EQ(wint_t('A'), towupper_l(L'A', l.l));
+ EXPECT_EQ(wint_t('Z'), towupper_l(L'z', l.l));
+ EXPECT_EQ(wint_t('Z'), towupper_l(L'Z', l.l));
if (have_dl()) {
EXPECT_EQ(wint_t(L'Ç'), towupper_l(L'ç', l.l));
EXPECT_EQ(wint_t(L'Ç'), towupper_l(L'Ç', l.l));
@@ -218,34 +226,64 @@
EXPECT_EQ(0, iswctype_l(WEOF, wctype_l("alnum", l.l), l.l));
}
-TEST(wctype, towctrans) {
+TEST(wctype, wctrans) {
EXPECT_TRUE(wctrans("tolower") != nullptr);
EXPECT_TRUE(wctrans("toupper") != nullptr);
+ errno = 0;
EXPECT_TRUE(wctrans("monkeys") == nullptr);
-}
-
-TEST(wctype, towctrans_l) {
- UtfLocale l;
- EXPECT_TRUE(wctrans_l("tolower", l.l) != nullptr);
- EXPECT_TRUE(wctrans_l("toupper", l.l) != nullptr);
-
- EXPECT_TRUE(wctrans_l("monkeys", l.l) == nullptr);
-}
-
-TEST(wctype, wctrans) {
- EXPECT_EQ(wint_t('a'), towctrans(L'A', wctrans("tolower")));
- EXPECT_EQ(WEOF, towctrans(WEOF, wctrans("tolower")));
-
- EXPECT_EQ(wint_t('A'), towctrans(L'a', wctrans("toupper")));
- EXPECT_EQ(WEOF, towctrans(WEOF, wctrans("toupper")));
+ #if defined(__BIONIC__)
+ // Android/FreeBSD/iOS set errno, but musl/glibc don't.
+ EXPECT_ERRNO(EINVAL);
+ #endif
}
TEST(wctype, wctrans_l) {
UtfLocale l;
- EXPECT_EQ(wint_t('a'), towctrans_l(L'A', wctrans_l("tolower", l.l), l.l));
- EXPECT_EQ(WEOF, towctrans_l(WEOF, wctrans_l("tolower", l.l), l.l));
+ EXPECT_TRUE(wctrans_l("tolower", l.l) != nullptr);
+ EXPECT_TRUE(wctrans_l("toupper", l.l) != nullptr);
- EXPECT_EQ(wint_t('A'), towctrans_l(L'a', wctrans_l("toupper", l.l), l.l));
- EXPECT_EQ(WEOF, towctrans_l(WEOF, wctrans_l("toupper", l.l), l.l));
+ errno = 0;
+ EXPECT_TRUE(wctrans_l("monkeys", l.l) == nullptr);
+ #if defined(__BIONIC__)
+ // Android/FreeBSD/iOS set errno, but musl/glibc don't.
+ EXPECT_ERRNO(EINVAL);
+ #endif
+}
+
+TEST(wctype, towctrans) {
+ wctrans_t lower = wctrans("tolower");
+ EXPECT_EQ(wint_t('a'), towctrans(L'A', lower));
+ EXPECT_EQ(WEOF, towctrans(WEOF, lower));
+
+ wctrans_t upper = wctrans("toupper");
+ EXPECT_EQ(wint_t('A'), towctrans(L'a', upper));
+ EXPECT_EQ(WEOF, towctrans(WEOF, upper));
+
+ wctrans_t invalid = wctrans("monkeys");
+ errno = 0;
+ EXPECT_EQ(wint_t('a'), towctrans(L'a', invalid));
+ #if defined(__BIONIC__)
+ // Android/FreeBSD/iOS set errno, but musl/glibc don't.
+ EXPECT_ERRNO(EINVAL);
+ #endif
+}
+
+TEST(wctype, towctrans_l) {
+ UtfLocale l;
+ wctrans_t lower = wctrans_l("tolower", l.l);
+ EXPECT_EQ(wint_t('a'), towctrans_l(L'A', lower, l.l));
+ EXPECT_EQ(WEOF, towctrans_l(WEOF, lower, l.l));
+
+ wctrans_t upper = wctrans_l("toupper", l.l);
+ EXPECT_EQ(wint_t('A'), towctrans_l(L'a', upper, l.l));
+ EXPECT_EQ(WEOF, towctrans_l(WEOF, upper, l.l));
+
+ wctrans_t invalid = wctrans_l("monkeys", l.l);
+ errno = 0;
+ EXPECT_EQ(wint_t('a'), towctrans_l(L'a', invalid, l.l));
+ #if defined(__BIONIC__)
+ // Android/FreeBSD/iOS set errno, but musl/glibc don't.
+ EXPECT_ERRNO(EINVAL);
+ #endif
}