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
 }