Merge "Revert "Workaround app compat issue introduced by global ThinLTO optimization"" 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 be0f878..b37f17b 100644
--- a/libc/bionic/wctype.cpp
+++ b/libc/bionic/wctype.cpp
@@ -109,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"));
@@ -120,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"));
@@ -158,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;
 }
 
@@ -169,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/libm/significandl.c b/libm/significandl.c
index c5d7dd4..b672110 100644
--- a/libm/significandl.c
+++ b/libm/significandl.c
@@ -22,11 +22,13 @@
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
- *
  */
 
 #include <math.h>
 
+// This function is only in glibc.
+// musl and NetBSD/OpenBSD just have the double and float variants,
+// while FreeBSD and iOS/macOS have none.
 long double significandl(long double x) {
   return scalbnl(x, -ilogbl(x));
 }
diff --git a/linker/Android.bp b/linker/Android.bp
index 694d1f5..563cf3d 100644
--- a/linker/Android.bp
+++ b/linker/Android.bp
@@ -14,6 +14,16 @@
     ],
 }
 
+linker_common_flags = [
+    "-fno-stack-protector",
+    "-Wstrict-overflow=5",
+    "-fvisibility=hidden",
+    "-Wall",
+    "-Wextra",
+    "-Wunused",
+    "-Werror",
+]
+
 // ========================================================
 // linker_wrapper - Linux Bionic (on the host)
 // ========================================================
@@ -36,15 +46,7 @@
         },
     },
 
-    cflags: [
-        "-fno-stack-protector",
-        "-Wstrict-overflow=5",
-        "-fvisibility=hidden",
-        "-Wall",
-        "-Wextra",
-        "-Wno-unused",
-        "-Werror",
-    ],
+    cflags: linker_common_flags,
 
     srcs: [
         "linker_wrapper.cpp",
@@ -83,26 +85,8 @@
         },
     },
 
-    cflags: [
-        "-fno-stack-protector",
-        "-Wstrict-overflow=5",
-        "-fvisibility=hidden",
-        "-Wall",
-        "-Wextra",
-        "-Wunused",
-        "-Werror",
-    ],
-
-    // TODO: split out the asflags.
-    asflags: [
-        "-fno-stack-protector",
-        "-Wstrict-overflow=5",
-        "-fvisibility=hidden",
-        "-Wall",
-        "-Wextra",
-        "-Wunused",
-        "-Werror",
-    ],
+    cflags: linker_common_flags,
+    asflags: linker_common_flags,
 
     product_variables: {
         debuggable: {
diff --git a/linker/linker_debug.cpp b/linker/linker_debug.cpp
index b0aae79..e6211f7 100644
--- a/linker/linker_debug.cpp
+++ b/linker/linker_debug.cpp
@@ -30,13 +30,14 @@
 
 #include <unistd.h>
 
-void linker_log_va_list(int prio __unused, const char* fmt, va_list ap) {
-#if LINKER_DEBUG_TO_LOG
-  async_safe_format_log_va_list(5 - prio, "linker", fmt, ap);
-#else
-  async_safe_format_fd_va_list(STDOUT_FILENO, fmt, ap);
-  write(STDOUT_FILENO, "\n", 1);
-#endif
+void linker_log_va_list(int prio, const char* fmt, va_list ap) {
+  va_list ap2;
+  va_copy(ap2, ap);
+  async_safe_format_log_va_list(5 - prio, "linker", fmt, ap2);
+  va_end(ap2);
+
+  async_safe_format_fd_va_list(STDERR_FILENO, fmt, ap);
+  write(STDERR_FILENO, "\n", 1);
 }
 
 void linker_log(int prio, const char* fmt, ...) {
diff --git a/linker/linker_debug.h b/linker/linker_debug.h
index 477b009..3aab185 100644
--- a/linker/linker_debug.h
+++ b/linker/linker_debug.h
@@ -33,11 +33,6 @@
 // INFO, TRACE, and DEBUG calls in the source). This will only
 // affect new processes being launched.
 
-// By default, traces are sent to logcat, with the "linker" tag. You can
-// change this to go to stdout instead by setting the definition of
-// LINKER_DEBUG_TO_LOG to 0.
-#define LINKER_DEBUG_TO_LOG  1
-
 #define TRACE_DEBUG          1
 #define DO_TRACE_LOOKUP      1
 #define DO_TRACE_RELO        1
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 2b230a8..0bc7003 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -333,11 +333,7 @@
 
   if (getenv("LD_SHOW_AUXV") != nullptr) ld_show_auxv(args.auxv);
 
-#if defined(__LP64__)
-  INFO("[ Android dynamic linker (64-bit) ]");
-#else
-  INFO("[ Android dynamic linker (32-bit) ]");
-#endif
+  INFO("[ Android dynamic linker (" ABI_STRING ") ]");
 
   // These should have been sanitized by __libc_init_AT_SECURE, but the test
   // doesn't cost us anything.
diff --git a/linker/linker_relocate.cpp b/linker/linker_relocate.cpp
index 3e36114..5f993ba 100644
--- a/linker/linker_relocate.cpp
+++ b/linker/linker_relocate.cpp
@@ -627,7 +627,7 @@
         android_relocs_[1] == 'P' &&
         android_relocs_[2] == 'S' &&
         android_relocs_[3] == '2') {
-      DEBUG("[ android relocating %s ]", get_realpath());
+      DEBUG("[ relocating %s android rel/rela ]", get_realpath());
 
       const uint8_t* packed_relocs = android_relocs_ + 4;
       const size_t packed_relocs_size = android_relocs_size_ - 4;
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/prebuilt-elf-files/arm64/libtest_empty.so b/tests/prebuilt-elf-files/arm64/libtest_empty.so
index d8775b6..76c569b 100755
--- a/tests/prebuilt-elf-files/arm64/libtest_empty.so
+++ b/tests/prebuilt-elf-files/arm64/libtest_empty.so
Binary files differ
diff --git a/tests/prebuilt-elf-files/x86_64/libtest_empty.so b/tests/prebuilt-elf-files/x86_64/libtest_empty.so
index c3f3638..ce519b2 100755
--- a/tests/prebuilt-elf-files/x86_64/libtest_empty.so
+++ b/tests/prebuilt-elf-files/x86_64/libtest_empty.so
Binary files differ
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
 }