Merge "Add a log_allocator_stats_on_exit option." into main
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");