Add support for reallocarray as proper function.

Scudo added reallocarray direct support, so use it rather than our
own wrapper.

Add this support everywhere and update tests and other code.

Test: All unit tests pass (malloc_hooks/malloc_debug/bionic).
Test: Built with jemalloc and all above unit tests pass.
Change-Id: Id3634b38ce549046251f97e5f1339afd37a19cb8
diff --git a/libc/bionic/gwp_asan_wrappers.cpp b/libc/bionic/gwp_asan_wrappers.cpp
index d2dc979..896f2d8 100644
--- a/libc/bionic/gwp_asan_wrappers.cpp
+++ b/libc/bionic/gwp_asan_wrappers.cpp
@@ -129,6 +129,15 @@
   return prev_dispatch->realloc(old_mem, bytes);
 }
 
+void* gwp_asan_reallocarray(void* old_mem, size_t item_count, size_t item_size) {
+  size_t new_size;
+  if (__builtin_mul_overflow(item_count, item_size, &new_size)) {
+    errno = ENOMEM;
+    return nullptr;
+  }
+  return gwp_asan_realloc(old_mem, new_size);
+}
+
 int gwp_asan_malloc_iterate(uintptr_t base, size_t size,
                             void (*callback)(uintptr_t base, size_t size, void* arg), void* arg) {
   if (__predict_false(GuardedAlloc.pointerIsMine(reinterpret_cast<void*>(base)))) {
@@ -162,6 +171,7 @@
     Malloc(pvalloc),
 #endif
     gwp_asan_realloc,
+    gwp_asan_reallocarray,
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
     Malloc(valloc),
 #endif
diff --git a/libc/bionic/jemalloc.h b/libc/bionic/jemalloc.h
index 4ce51c0..d2527dc 100644
--- a/libc/bionic/jemalloc.h
+++ b/libc/bionic/jemalloc.h
@@ -29,6 +29,7 @@
 __BEGIN_DECLS
 
 void* je_aligned_alloc_wrapper(size_t, size_t);
+void* je_reallocarray(void*, size_t, size_t);
 int je_malloc_iterate(uintptr_t, size_t, void (*)(uintptr_t, size_t, void*), void*);
 int je_mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) __attribute__((nothrow));
 struct mallinfo je_mallinfo();
diff --git a/libc/bionic/jemalloc_wrapper.cpp b/libc/bionic/jemalloc_wrapper.cpp
index eb76cf4..ef0ffb1 100644
--- a/libc/bionic/jemalloc_wrapper.cpp
+++ b/libc/bionic/jemalloc_wrapper.cpp
@@ -196,3 +196,15 @@
 
   return 0;
 }
+
+void* je_reallocarray(void* old_mem, size_t item_count, size_t item_size) {
+  size_t new_size;
+  if (__builtin_mul_overflow(item_count, item_size, &new_size)) {
+    async_safe_format_log(ANDROID_LOG_WARN, "jemalloc",
+                          "reallocarray(%p, %zu, %zu) failed: returning null pointer", old_mem,
+                          item_count, item_size);
+    errno = ENOMEM;
+    return nullptr;
+  }
+  return je_realloc(old_mem, new_size);
+}
diff --git a/libc/bionic/malloc_common.cpp b/libc/bionic/malloc_common.cpp
index 422fecc..2ae9068 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -218,14 +218,12 @@
 }
 
 extern "C" void* reallocarray(void* old_mem, size_t item_count, size_t item_size) {
-  size_t new_size;
-  if (__builtin_mul_overflow(item_count, item_size, &new_size)) {
-    warning_log("reallocaray(%p, %zu, %zu) failed: returning null pointer",
-                old_mem, item_count, item_size);
-    errno = ENOMEM;
-    return nullptr;
+  auto dispatch_table = GetDispatchTable();
+  old_mem = MaybeUntagAndCheckPointer(old_mem);
+  if (__predict_false(dispatch_table != nullptr)) {
+    return MaybeTagPointer(dispatch_table->reallocarray(old_mem, item_count, item_size));
   }
-  return realloc(old_mem, new_size);
+  return MaybeTagPointer(Malloc(reallocarray)(old_mem, item_count, item_size));
 }
 
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
@@ -342,26 +340,27 @@
 // =============================================================================
 
 static constexpr MallocDispatch __libc_malloc_default_dispatch __attribute__((unused)) = {
-  Malloc(calloc),
-  Malloc(free),
-  Malloc(mallinfo),
-  Malloc(malloc),
-  Malloc(malloc_usable_size),
-  Malloc(memalign),
-  Malloc(posix_memalign),
+    Malloc(calloc),
+    Malloc(free),
+    Malloc(mallinfo),
+    Malloc(malloc),
+    Malloc(malloc_usable_size),
+    Malloc(memalign),
+    Malloc(posix_memalign),
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-  Malloc(pvalloc),
+    Malloc(pvalloc),
 #endif
-  Malloc(realloc),
+    Malloc(realloc),
+    Malloc(reallocarray),
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-  Malloc(valloc),
+    Malloc(valloc),
 #endif
-  Malloc(malloc_iterate),
-  Malloc(malloc_disable),
-  Malloc(malloc_enable),
-  Malloc(mallopt),
-  Malloc(aligned_alloc),
-  Malloc(malloc_info),
+    Malloc(malloc_iterate),
+    Malloc(malloc_disable),
+    Malloc(malloc_enable),
+    Malloc(mallopt),
+    Malloc(aligned_alloc),
+    Malloc(malloc_info),
 };
 
 const MallocDispatch* NativeAllocatorDispatch() {
diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp
index 8925e98..dec8f9f 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -174,6 +174,10 @@
   if (!InitMallocFunction<MallocRealloc>(impl_handler, &table->realloc, prefix, "realloc")) {
     return false;
   }
+  if (!InitMallocFunction<MallocReallocArray>(impl_handler, &table->reallocarray, prefix,
+                                              "reallocarray")) {
+    return false;
+  }
   if (!InitMallocFunction<MallocIterate>(impl_handler, &table->malloc_iterate, prefix,
                                          "malloc_iterate")) {
     return false;
diff --git a/libc/bionic/malloc_limit.cpp b/libc/bionic/malloc_limit.cpp
index 5128a35..85ddc79 100644
--- a/libc/bionic/malloc_limit.cpp
+++ b/libc/bionic/malloc_limit.cpp
@@ -51,6 +51,7 @@
 static void* LimitMemalign(size_t alignment, size_t bytes);
 static int LimitPosixMemalign(void** memptr, size_t alignment, size_t size);
 static void* LimitRealloc(void* old_mem, size_t bytes);
+static void* LimitReallocArray(void* old_mem, size_t item_count, size_t item_size);
 static void* LimitAlignedAlloc(size_t alignment, size_t size);
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
 static void* LimitPvalloc(size_t bytes);
@@ -67,8 +68,7 @@
 static int LimitMallopt(int param, int value);
 __END_DECLS
 
-static constexpr MallocDispatch __limit_dispatch
-  __attribute__((unused)) = {
+static constexpr MallocDispatch __limit_dispatch __attribute__((unused)) = {
     LimitCalloc,
     LimitFree,
     LimitMallinfo,
@@ -80,6 +80,7 @@
     LimitPvalloc,
 #endif
     LimitRealloc,
+    LimitReallocArray,
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
     LimitValloc,
 #endif
@@ -89,7 +90,7 @@
     LimitMallopt,
     LimitAlignedAlloc,
     LimitMallocInfo,
-  };
+};
 
 static _Atomic uint64_t gAllocated;
 static uint64_t gAllocLimit;
@@ -228,6 +229,17 @@
   return new_ptr;
 }
 
+static void* LimitReallocArray(void* old_mem, size_t item_count, size_t item_size) {
+  size_t new_size;
+  if (__builtin_mul_overflow(item_count, item_size, &new_size)) {
+    warning_log("malloc_limit: reallocarray(%p, %zu, %zu) failed: returning null pointer", old_mem,
+                item_count, item_size);
+    errno = ENOMEM;
+    return nullptr;
+  }
+  return LimitRealloc(old_mem, new_size);
+}
+
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
 static void* LimitPvalloc(size_t bytes) {
   if (!CheckLimit(bytes)) {
diff --git a/libc/bionic/scudo.h b/libc/bionic/scudo.h
index 946a497..bf8e029 100644
--- a/libc/bionic/scudo.h
+++ b/libc/bionic/scudo.h
@@ -46,6 +46,7 @@
 int scudo_mallopt(int, int);
 void* scudo_memalign(size_t, size_t);
 void* scudo_realloc(void*, size_t);
+void* scudo_reallocarray(void*, size_t, size_t);
 int scudo_posix_memalign(void**, size_t, size_t);
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
 void* scudo_pvalloc(size_t);
diff --git a/libc/memory/malloc_debug/exported32.map b/libc/memory/malloc_debug/exported32.map
index f75d173..3947840 100644
--- a/libc/memory/malloc_debug/exported32.map
+++ b/libc/memory/malloc_debug/exported32.map
@@ -21,6 +21,7 @@
     debug_posix_memalign;
     debug_pvalloc;
     debug_realloc;
+    debug_reallocarray;
     debug_valloc;
     debug_write_malloc_leak_info;
 
diff --git a/libc/memory/malloc_debug/exported64.map b/libc/memory/malloc_debug/exported64.map
index 6dea58c..d9e32da 100644
--- a/libc/memory/malloc_debug/exported64.map
+++ b/libc/memory/malloc_debug/exported64.map
@@ -20,6 +20,7 @@
     debug_memalign;
     debug_posix_memalign;
     debug_realloc;
+    debug_reallocarray;
     debug_write_malloc_leak_info;
 
   local:
diff --git a/libc/memory/malloc_debug/malloc_debug.cpp b/libc/memory/malloc_debug/malloc_debug.cpp
index 390d07b..cbed2e3 100644
--- a/libc/memory/malloc_debug/malloc_debug.cpp
+++ b/libc/memory/malloc_debug/malloc_debug.cpp
@@ -183,6 +183,7 @@
 void* debug_aligned_alloc(size_t alignment, size_t size);
 void* debug_memalign(size_t alignment, size_t bytes);
 void* debug_realloc(void* pointer, size_t bytes);
+void* debug_reallocarray(void* pointer, size_t item_count, size_t item_size);
 void* debug_calloc(size_t nmemb, size_t bytes);
 struct mallinfo debug_mallinfo();
 int debug_mallopt(int param, int value);
@@ -992,6 +993,19 @@
   return new_pointer;
 }
 
+void* debug_reallocarray(void* pointer, size_t item_count, size_t item_size) {
+  if (DebugCallsDisabled()) {
+    return g_dispatch->reallocarray(pointer, item_count, item_size);
+  }
+
+  size_t new_size;
+  if (__builtin_mul_overflow(item_count, item_size, &new_size)) {
+    errno = ENOMEM;
+    return nullptr;
+  }
+  return debug_realloc(pointer, new_size);
+}
+
 void* debug_calloc(size_t nmemb, size_t bytes) {
   Unreachable::CheckIfRequested(g_debug->config());
 
@@ -1154,6 +1168,7 @@
 
 void debug_malloc_disable() {
   ScopedConcurrentLock lock;
+  ScopedDisableDebugCalls disable;
   if (g_debug->pointer) {
     // Acquire the pointer locks first, otherwise, the code can be holding
     // the allocation lock and deadlock trying to acquire a pointer lock.
diff --git a/libc/memory/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/memory/malloc_debug/tests/malloc_debug_unit_tests.cpp
index 08a3a1f..3556ded 100644
--- a/libc/memory/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/memory/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -64,6 +64,7 @@
 void debug_free(void*);
 void* debug_calloc(size_t, size_t);
 void* debug_realloc(void*, size_t);
+void* debug_reallocarray(void*, size_t, size_t);
 int debug_posix_memalign(void**, size_t, size_t);
 void* debug_memalign(size_t, size_t);
 void* debug_aligned_alloc(size_t, size_t);
@@ -154,26 +155,27 @@
 };
 
 MallocDispatch MallocDebugTest::dispatch = {
-  calloc,
-  free,
-  mallinfo,
-  malloc,
-  malloc_usable_size,
-  memalign,
-  posix_memalign,
+    calloc,
+    free,
+    mallinfo,
+    malloc,
+    malloc_usable_size,
+    memalign,
+    posix_memalign,
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-  nullptr,
+    nullptr,
 #endif
-  realloc,
+    realloc,
+    reallocarray,
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
-  nullptr,
+    nullptr,
 #endif
-  nullptr,
-  nullptr,
-  nullptr,
-  mallopt,
-  aligned_alloc,
-  malloc_info,
+    nullptr,
+    nullptr,
+    nullptr,
+    mallopt,
+    aligned_alloc,
+    malloc_info,
 };
 
 std::string ShowDiffs(uint8_t* a, uint8_t* b, size_t size) {
@@ -295,6 +297,13 @@
   pointer = reinterpret_cast<uint8_t*>(debug_realloc(pointer, 0));
   ASSERT_TRUE(pointer == nullptr);
 
+  pointer = reinterpret_cast<uint8_t*>(debug_reallocarray(nullptr, 1, alloc_size));
+  ASSERT_TRUE(pointer != nullptr);
+  for (size_t i = 0; i < debug_malloc_usable_size(pointer); i++) {
+    ASSERT_EQ(0xeb, pointer[i]) << "Failed at byte " << i;
+  }
+  debug_free(pointer);
+
   ASSERT_STREQ("", getFakeLogBuf().c_str());
   std::string expected_log;
   if (all_options) {
@@ -500,6 +509,13 @@
   ASSERT_LE(1124U, debug_malloc_usable_size(pointer));
   debug_free(pointer);
 
+  pointer = debug_reallocarray(nullptr, 2, 30);
+  ASSERT_TRUE(pointer != nullptr);
+  ASSERT_LE(1084U, debug_malloc_usable_size(pointer));
+  pointer = debug_reallocarray(pointer, 2, 100);
+  ASSERT_LE(1224U, debug_malloc_usable_size(pointer));
+  debug_free(pointer);
+
   ASSERT_STREQ("", getFakeLogBuf().c_str());
   ASSERT_STREQ("", getFakeLogPrint().c_str());
 }
@@ -551,6 +567,17 @@
   pointer = reinterpret_cast<uint8_t*>(debug_realloc(pointer, 0));
   ASSERT_TRUE(pointer == nullptr);
 
+  pointer = reinterpret_cast<uint8_t*>(debug_reallocarray(nullptr, 2, 50));
+  ASSERT_TRUE(pointer != nullptr);
+  ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0)
+      << ShowDiffs(buffer.data(), &pointer[-buffer.size()], buffer.size());
+  memset(pointer, 0xff, 100);
+  pointer = reinterpret_cast<uint8_t*>(debug_reallocarray(pointer, 2, 100));
+  ASSERT_TRUE(memcmp(buffer.data(), &pointer[-buffer.size()], buffer.size()) == 0)
+      << ShowDiffs(buffer.data(), &pointer[-buffer.size()], buffer.size());
+  memset(pointer, 0xff, 200);
+  debug_free(pointer);
+
   ASSERT_STREQ("", getFakeLogBuf().c_str());
   ASSERT_STREQ("", getFakeLogPrint().c_str());
 }
diff --git a/libc/memory/malloc_hooks/exported32.map b/libc/memory/malloc_hooks/exported32.map
index 2b02806..5654de2 100644
--- a/libc/memory/malloc_hooks/exported32.map
+++ b/libc/memory/malloc_hooks/exported32.map
@@ -20,6 +20,7 @@
     hooks_posix_memalign;
     hooks_pvalloc;
     hooks_realloc;
+    hooks_reallocarray;
     hooks_valloc;
     hooks_write_malloc_leak_info;
 
diff --git a/libc/memory/malloc_hooks/exported64.map b/libc/memory/malloc_hooks/exported64.map
index 59ec1f0..2b77831 100644
--- a/libc/memory/malloc_hooks/exported64.map
+++ b/libc/memory/malloc_hooks/exported64.map
@@ -19,6 +19,7 @@
     hooks_memalign;
     hooks_posix_memalign;
     hooks_realloc;
+    hooks_reallocarray;
     hooks_write_malloc_leak_info;
 
   local:
diff --git a/libc/memory/malloc_hooks/malloc_hooks.cpp b/libc/memory/malloc_hooks/malloc_hooks.cpp
index e039065..0e7b05e 100644
--- a/libc/memory/malloc_hooks/malloc_hooks.cpp
+++ b/libc/memory/malloc_hooks/malloc_hooks.cpp
@@ -63,6 +63,7 @@
 void* hooks_memalign(size_t alignment, size_t bytes);
 void* hooks_aligned_alloc(size_t alignment, size_t bytes);
 void* hooks_realloc(void* pointer, size_t bytes);
+void* hooks_reallocarray(void* pointer, size_t item_count, size_t item_size);
 void* hooks_calloc(size_t nmemb, size_t bytes);
 struct mallinfo hooks_mallinfo();
 int hooks_mallopt(int param, int value);
@@ -153,6 +154,15 @@
   return g_dispatch->realloc(pointer, bytes);
 }
 
+void* hooks_reallocarray(void* pointer, size_t item_count, size_t item_size) {
+  size_t new_size;
+  if (__builtin_mul_overflow(item_count, item_size, &new_size)) {
+    errno = ENOMEM;
+    return nullptr;
+  }
+  return hooks_realloc(pointer, new_size);
+}
+
 void* hooks_calloc(size_t nmemb, size_t bytes) {
   if (__malloc_hook != nullptr && __malloc_hook != default_malloc_hook) {
     size_t size;
diff --git a/libc/memory/malloc_hooks/tests/malloc_hooks_tests.cpp b/libc/memory/malloc_hooks/tests/malloc_hooks_tests.cpp
index 582872b..b9b45cb 100644
--- a/libc/memory/malloc_hooks/tests/malloc_hooks_tests.cpp
+++ b/libc/memory/malloc_hooks/tests/malloc_hooks_tests.cpp
@@ -281,6 +281,25 @@
   EXPECT_TRUE(void_arg_ != nullptr) << "The realloc hook was called with a nullptr.";
 }
 
+TEST_F(MallocHooksTest, realloc_hook_for_reallocarray) {
+  RunTest("*.DISABLED_realloc_hook_for_reallocarray");
+}
+
+TEST_F(MallocHooksTest, DISABLED_realloc_hook_for_reallocarray) {
+  Init();
+  ASSERT_TRUE(__realloc_hook != nullptr);
+  __realloc_hook = test_realloc_hook;
+
+  void* ptr = malloc(1024);
+  ASSERT_TRUE(ptr != nullptr);
+  ptr = reallocarray(ptr, 2, 1048);
+  free(ptr);
+  write(0, ptr, 0);
+
+  EXPECT_TRUE(realloc_hook_called_) << "The realloc hook was not called.";
+  EXPECT_TRUE(void_arg_ != nullptr) << "The realloc hook was called with a nullptr.";
+}
+
 TEST_F(MallocHooksTest, memalign_hook) {
   RunTest("*.DISABLED_memalign_hook");
 }
diff --git a/libc/private/bionic_malloc_dispatch.h b/libc/private/bionic_malloc_dispatch.h
index f76b75b..29a34db 100644
--- a/libc/private/bionic_malloc_dispatch.h
+++ b/libc/private/bionic_malloc_dispatch.h
@@ -45,6 +45,7 @@
 typedef void* (*MallocMemalign)(size_t, size_t);
 typedef int (*MallocPosixMemalign)(void**, size_t, size_t);
 typedef void* (*MallocRealloc)(void*, size_t);
+typedef void* (*MallocReallocArray)(void*, size_t, size_t);
 typedef int (*MallocIterate)(uintptr_t, size_t, void (*)(uintptr_t, size_t, void*), void*);
 typedef void (*MallocMallocDisable)();
 typedef void (*MallocMallocEnable)();
@@ -68,6 +69,7 @@
   MallocPvalloc pvalloc;
 #endif
   MallocRealloc realloc;
+  MallocReallocArray reallocarray;
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
   MallocValloc valloc;
 #endif
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index 070585d..c0a8eea 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -531,6 +531,8 @@
 
 TEST(malloc, reallocarray_overflow) {
 #if HAVE_REALLOCARRAY
+  SKIP_WITH_HWASAN;
+
   // Values that cause overflow to a result small enough (8 on LP64) that malloc would "succeed".
   size_t a = static_cast<size_t>(INTPTR_MIN + 4);
   size_t b = 2;