[Lldb-commits] [lldb] 8b46bd5 - [lldb] Skip plugin teardown when exiting without Terminate (#201739)

via lldb-commits lldb-commits at lists.llvm.org
Fri Jun 5 17:10:30 PDT 2026


Author: Jonas Devlieghere
Date: 2026-06-05T17:10:25-07:00
New Revision: 8b46bd51399d3e49c601d9958b2e3ff5c1a706cd

URL: https://github.com/llvm/llvm-project/commit/8b46bd51399d3e49c601d9958b2e3ff5c1a706cd
DIFF: https://github.com/llvm/llvm-project/commit/8b46bd51399d3e49c601d9958b2e3ff5c1a706cd.diff

LOG: [lldb] Skip plugin teardown when exiting without Terminate (#201739)

`import lldb` auto-calls SBDebugger::Initialize() but never Terminate().
The g_debugger_lifetime is a deliberately-leaked ManagedStatic, so the
PluginInstances containers are still populated when their static
destructors run at process exit. That tripped the "forgot to unregister
plugin?" assert, and once the assert was gated the dynamically-loaded
plugin map's PluginInfo terminate callbacks ran against PluginInstances
mutexes that had already been destroyed.

This only surfaces with LLDB_ENABLE_DYNAMIC_SCRIPTINTERPRETERS, where
`_lldb` is the script-interpreter plugin dylib and liblldb comes in as a
dependency that exit() finalizes. A static build leaks identically but
never reaches those destructors, so the bug stayed latent.

Track an explicit lifecycle (Uninitialized/Initialized/Terminated) in a
single never-destroyed PluginRegistry. ~PluginInstances only checks for
leftover registrations once Terminate() has run, and the map (never torn
down at exit) only runs its terminate callbacks during an explicit
clear, while every container is still alive.

Added: 
    

Modified: 
    lldb/source/Core/PluginManager.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 7e334ea1c008b..d1a2f41ca99a2 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -76,16 +76,51 @@ struct PluginInfo {
 
 typedef llvm::SmallDenseMap<FileSpec, PluginInfo> DynamicPluginMap;
 
-static std::recursive_mutex &GetPluginMapMutex() {
-  static std::recursive_mutex g_plugin_map_mutex;
-  return g_plugin_map_mutex;
+namespace {
+enum class PluginLifecycle { Uninitialized, Initialized, Terminated };
+
+struct PluginRegistry {
+  std::recursive_mutex mutex;
+  DynamicPluginMap map;
+
+  void Initialize() {
+    std::lock_guard<std::recursive_mutex> guard(mutex);
+    lifecycle = PluginLifecycle::Initialized;
+  }
+
+  void Terminate() {
+    std::lock_guard<std::recursive_mutex> guard(mutex);
+    lifecycle = PluginLifecycle::Terminated;
+    map.clear();
+  }
+
+  // Only after Terminate() is a leftover PluginInstances registration a bug;
+  // exiting in any other state (e.g. `import lldb`, which never calls
+  // Terminate()) is supported and leaves plugins registered.
+  bool IsTerminated() const { return lifecycle == PluginLifecycle::Terminated; }
+
+private:
+  PluginLifecycle lifecycle = PluginLifecycle::Uninitialized;
+};
+} // namespace
+
+// Never destroyed: at static-destruction time the PluginInstances containers
+// (separate statics, arbitrary teardown order) still call IsTerminated(), and
+// the map's PluginInfo terminate callbacks must not run when the containers
+// they unregister from may already be gone. Terminate() clears the map
+// explicitly while everything is alive. The static pointer keeps it reachable,
+// so this is not a LeakSanitizer leak.
+static PluginRegistry &GetPluginRegistry() {
+  static PluginRegistry *g_registry = new PluginRegistry();
+  return *g_registry;
 }
 
-static DynamicPluginMap &GetPluginMap() {
-  static DynamicPluginMap g_plugin_map;
-  return g_plugin_map;
+static std::recursive_mutex &GetPluginMapMutex() {
+  return GetPluginRegistry().mutex;
 }
 
+static DynamicPluginMap &GetPluginMap() { return GetPluginRegistry().map; }
+
 static bool PluginIsLoaded(const FileSpec &plugin_file_spec) {
   std::lock_guard<std::recursive_mutex> guard(GetPluginMapMutex());
   return GetPluginMap().contains(plugin_file_spec);
@@ -234,6 +269,8 @@ LoadPluginCallback(void *baton, llvm::sys::fs::file_type ft,
 }
 
 void PluginManager::Initialize() {
+  GetPluginRegistry().Initialize();
+
   static const bool find_directories = true;
   static const bool find_files = true;
   static const bool find_other = true;
@@ -256,10 +293,7 @@ void PluginManager::Initialize() {
   }
 }
 
-void PluginManager::Terminate() {
-  std::lock_guard<std::recursive_mutex> guard(GetPluginMapMutex());
-  GetPluginMap().clear();
-}
+void PluginManager::Terminate() { GetPluginRegistry().Terminate(); }
 
 llvm::ArrayRef<PluginNamespace> PluginManager::GetPluginNamespaces() {
   static PluginNamespace PluginNamespaces[] = {
@@ -494,6 +528,9 @@ template <typename Callback> struct PluginInstance {
 template <typename Instance> class PluginInstances {
 public:
   ~PluginInstances() {
+    // Only meaningful after a real teardown; see PluginRegistry::IsTerminated.
+    if (!GetPluginRegistry().IsTerminated())
+      return;
 #ifndef NDEBUG
     for (const auto &instance : m_instances)
       llvm::errs() << llvm::formatv("Use `image lookup -va {0:x}` to find out "


        


More information about the lldb-commits mailing list