[Lldb-commits] [lldb] [lldb][Module] Make eLoadScriptFromSymFileWarn behave as a "dry-run" (PR #189943)

Michael Buch via lldb-commits lldb-commits at lists.llvm.org
Wed Apr 1 08:02:36 PDT 2026


https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/189943

>From dc91f5415f30c870b6e1d04a94102ccd0d6a399a Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 1 Apr 2026 09:32:13 +0100
Subject: [PATCH 1/3] [lldb][Module] Make eLoadScriptFromSymFileWarn behave as
 a "dry-run"

This patch is a follow-up to https://github.com/llvm/llvm-project/pull/189444#issuecomment-4165226875

We want `eLoadScriptFromSymFileWarn` to be a "dry-run" mode which warns but all possible module loads but doesn't actually load them. To do so, this patch removes the short-circuit in the current module loading loop.

The previous warning is verbose and contains instructions that don't need to be printed for every module. So this patch ensures LLDB only prints the verbose warning once per debugger-session. The script paths that would have been loaded are accumulated and printed at the end of the function. We `return false` to preserve the previous semantics of `LoadScriptingResourceInTarget`.

Eventually we want the warning to also indicate whether the module in consideration is a safe/trusted module or not but I wanted to keep that for a separate PR.
---
 lldb/source/Core/Module.cpp                   | 26 +++++++++----
 .../dsym-python-script-name-warnings.test     |  2 +-
 .../AutoLoad/Darwin/dsym-python-script.test   |  4 +-
 .../Darwin/dsym-warn-multiple-modules.test    | 38 ++++++++++++++++++
 .../UNIX/safe-path-warn-multiple-modules.test | 39 +++++++++++++++++++
 5 files changed, 98 insertions(+), 11 deletions(-)
 create mode 100644 lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test
 create mode 100644 lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test

diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index 808c8a347e9b2..3e30e1bb63121 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -1466,6 +1466,8 @@ bool Module::LoadScriptingResourceInTarget(Target *target, Status &error) {
   if (!feedback_stream.Empty())
     debugger.ReportWarning(feedback_stream.GetString().str(), debugger.GetID());
 
+  llvm::SmallVector<std::string> warned_modules;
+
   for (const auto &[scripting_fspec, load_style] : file_specs) {
     if (load_style == eLoadScriptFromSymFileFalse)
       continue;
@@ -1474,23 +1476,22 @@ bool Module::LoadScriptingResourceInTarget(Target *target, Status &error) {
       continue;
 
     if (load_style == eLoadScriptFromSymFileWarn) {
+      static std::once_flag s_warn_once;
       // clang-format off
       debugger.ReportWarning(
-          llvm::formatv(
-R"('{0}' contains a debug script. To run this script in this debug session:
+R"(Found executable debug scripts. To run this script in this debug session:
 
-    command script import "{1}"
+    command script import "/path/to/script.py"
 
 To run all discovered debug scripts in this session:
 
     settings set target.load-script-from-symbol-file true
-)",
-              GetFileSpec().GetFileNameStrippingExtension(),
-              scripting_fspec.GetPath()),
-          debugger.GetID());
+)", debugger.GetID(), &s_warn_once);
       // clang-format on
 
-      return false;
+      warned_modules.emplace_back(scripting_fspec.GetPath());
+
+      continue;
     }
 
     LLDB_LOG(log, "Auto-loading {0}", scripting_fspec.GetPath());
@@ -1503,6 +1504,15 @@ To run all discovered debug scripts in this session:
     }
   }
 
+  if (!warned_modules.empty()) {
+    debugger.ReportWarning(
+        llvm::formatv(
+            "Following debug scripts were detected but not loaded:\n{0}",
+            llvm::join(warned_modules, "\n")),
+        debugger.GetID());
+    return false;
+  }
+
   return true;
 }
 
diff --git a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test
index 9c84045d75932..a1556096ed342 100644
--- a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test
+++ b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test
@@ -33,7 +33,7 @@
 
 ## Also confirm that the warning message about auto-loading scripts is printed afterwards.
 
-# CHECK-REMOVE: warning: 'Test-Module2' contains a debug script. To run this script in this
+# CHECK-REMOVE: warning: Found executable debug scripts. To run this script in this debug session
 
 #--- main.c
 int main() { return 0; }
diff --git a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test
index d19199f7e55be..0e4cb37f4fcd4 100644
--- a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test
+++ b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test
@@ -23,9 +23,9 @@
 # RUN:   -o 'target create %t/TestModule.out' 2>&1 \
 # RUN:   | FileCheck %s --implicit-check-not=warning --check-prefix=CHECK-LOADED
 
-# CHECK-WARN:      warning: 'TestModule' contains a debug script. To run this script in this debug session:
+# CHECK-WARN:      warning: Found executable debug scripts. To run this script in this debug session:
 # CHECK-WARN-EMPTY:
-# CHECK-WARN-NEXT:{{^}}    command script import "{{.*}}/TestModule.out.dSYM/Contents/Resources/Python/TestModule.py"
+# CHECK-WARN-NEXT:{{^}}    command script import "/path/to/script.py"
 # CHECK-WARN-EMPTY:
 # CHECK-WARN-NEXT: To run all discovered debug scripts in this session:
 # CHECK-WARN-EMPTY:
diff --git a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test
new file mode 100644
index 0000000000000..508d0999d609d
--- /dev/null
+++ b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test
@@ -0,0 +1,38 @@
+# REQUIRES: python, system-darwin
+#
+# Test that when load-script-from-symbol-file is set to 'warn', LLDB emits a
+# warning for dSYM scripts without executing them. Verify that it doesn't
+# abort early by loading a second module whose dSYM script should also trigger
+# a warning.
+
+# RUN: split-file %s %t
+# RUN: %clang_host -g %t/main.c -o %t/TestModule.out
+# RUN: %clang_host -g %t/main.c -o %t/TestModule2.out
+# RUN: mkdir -p %t/TestModule.out.dSYM/Contents/Resources/Python
+# RUN: mkdir -p %t/TestModule2.out.dSYM/Contents/Resources/Python
+
+# RUN: cp %t/dsym_script.py %t/TestModule.out.dSYM/Contents/Resources/Python/TestModule.py
+# RUN: cp %t/dsym_script2.py %t/TestModule2.out.dSYM/Contents/Resources/Python/TestModule2.py
+# RUN: %lldb -b \
+# RUN:   -o 'settings set target.load-script-from-symbol-file warn' \
+# RUN:   -o 'target create %t/TestModule.out' \
+# RUN:   -o 'target modules add %t/TestModule2.out' 2>&1 \
+# RUN:   | FileCheck %s --implicit-check-not=DSYM_SCRIPT --implicit-check-not=DSYM_SCRIPT2
+
+# CHECK-COUNT-1: warning: Found executable debug scripts. To run this script in this debug session
+# CHECK:      Following debug scripts were detected but not loaded:
+# CHECK-DAG: {{.*}}/TestModule.out.dSYM/Contents/Resources/Python/TestModule.py
+# CHECK-DAG: {{.*}}/TestModule2.out.dSYM/Contents/Resources/Python/TestModule2.py
+
+#--- main.c
+int main() { return 0; }
+
+#--- dsym_script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("DSYM_SCRIPT", file=sys.stderr)
+
+#--- dsym_script2.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("DSYM_SCRIPT2", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test b/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test
new file mode 100644
index 0000000000000..ca75cfd1e0bbb
--- /dev/null
+++ b/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test
@@ -0,0 +1,39 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that when load-script-from-symbol-file is set to 'warn', LLDB emits a
+# warning for safe-path scripts without executing them. Verify that it doesn't
+# abort early by loading a second module whose script should also trigger a
+# warning.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: %clang_host %t/main.c -o %t/TestModule2.out
+# RUN: mkdir -p %t/safe-path/TestModule
+# RUN: mkdir -p %t/safe-path/TestModule2
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/TestModule.py
+# RUN: cp %t/script2.py %t/safe-path/TestModule2/TestModule2.py
+# RUN: %lldb -b \
+# RUN:   -o 'settings set target.load-script-from-symbol-file warn' \
+# RUN:   -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN:   -o 'target create %t/TestModule.out' \
+# RUN:   -o 'target modules add %t/TestModule2.out' 2>&1 \
+# RUN:   | FileCheck %s --implicit-check-not=SCRIPT_LOADED --implicit-check-not=SCRIPT2_LOADED
+
+# CHECK-COUNT-1: warning: Found executable debug scripts. To run this script in this debug session
+# CHECK:      Following debug scripts were detected but not loaded:
+# CHECK-DAG: {{.*}}/safe-path/TestModule/TestModule.py
+# CHECK-DAG: {{.*}}/safe-path/TestModule2/TestModule2.py
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("SCRIPT_LOADED", file=sys.stderr)
+
+#--- script2.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("SCRIPT2_LOADED", file=sys.stderr)

>From 9284bb09eef71f05b617a70e2c4453ebde6e6177 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 1 Apr 2026 12:45:36 +0100
Subject: [PATCH 2/3] fixup! move std::once_flag usage to end of function

---
 lldb/source/Core/Module.cpp                   | 36 +++++++++----------
 .../dsym-python-script-name-warnings.test     |  2 +-
 .../AutoLoad/Darwin/dsym-python-script.test   |  2 +-
 .../Darwin/dsym-warn-multiple-modules.test    | 34 +++++++++++++-----
 .../UNIX/safe-path-warn-multiple-modules.test | 34 +++++++++++++-----
 5 files changed, 70 insertions(+), 38 deletions(-)

diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index 3e30e1bb63121..305c33df2eafc 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -1476,21 +1476,7 @@ bool Module::LoadScriptingResourceInTarget(Target *target, Status &error) {
       continue;
 
     if (load_style == eLoadScriptFromSymFileWarn) {
-      static std::once_flag s_warn_once;
-      // clang-format off
-      debugger.ReportWarning(
-R"(Found executable debug scripts. To run this script in this debug session:
-
-    command script import "/path/to/script.py"
-
-To run all discovered debug scripts in this session:
-
-    settings set target.load-script-from-symbol-file true
-)", debugger.GetID(), &s_warn_once);
-      // clang-format on
-
       warned_modules.emplace_back(scripting_fspec.GetPath());
-
       continue;
     }
 
@@ -1505,11 +1491,25 @@ To run all discovered debug scripts in this session:
   }
 
   if (!warned_modules.empty()) {
+    static std::once_flag s_warn_once;
+    // clang-format off
     debugger.ReportWarning(
-        llvm::formatv(
-            "Following debug scripts were detected but not loaded:\n{0}",
-            llvm::join(warned_modules, "\n")),
-        debugger.GetID());
+R"(Found executable debug scripts. To run a script in this debug session:
+
+    command script import "/path/to/script.py"
+
+To run all discovered debug scripts in this session:
+
+    settings set target.load-script-from-symbol-file true
+)", debugger.GetID(), &s_warn_once);
+
+    debugger.ReportWarning(llvm::formatv(
+R"(The following debug scripts were detected but not loaded:
+
+    - {0}
+)", llvm::join(warned_modules, "\n    -")));
+    // clang-format on
+
     return false;
   }
 
diff --git a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test
index a1556096ed342..cb494c4436a4f 100644
--- a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test
+++ b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script-name-warnings.test
@@ -33,7 +33,7 @@
 
 ## Also confirm that the warning message about auto-loading scripts is printed afterwards.
 
-# CHECK-REMOVE: warning: Found executable debug scripts. To run this script in this debug session
+# CHECK-REMOVE: warning: Found executable debug scripts. To run a script in this debug session
 
 #--- main.c
 int main() { return 0; }
diff --git a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test
index 0e4cb37f4fcd4..fc28d4737422e 100644
--- a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test
+++ b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-python-script.test
@@ -23,7 +23,7 @@
 # RUN:   -o 'target create %t/TestModule.out' 2>&1 \
 # RUN:   | FileCheck %s --implicit-check-not=warning --check-prefix=CHECK-LOADED
 
-# CHECK-WARN:      warning: Found executable debug scripts. To run this script in this debug session:
+# CHECK-WARN:      warning: Found executable debug scripts. To run a script in this debug session:
 # CHECK-WARN-EMPTY:
 # CHECK-WARN-NEXT:{{^}}    command script import "/path/to/script.py"
 # CHECK-WARN-EMPTY:
diff --git a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test
index 508d0999d609d..40a284dbea393 100644
--- a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test
+++ b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-multiple-modules.test
@@ -1,28 +1,39 @@
 # REQUIRES: python, system-darwin
 #
 # Test that when load-script-from-symbol-file is set to 'warn', LLDB emits a
-# warning for dSYM scripts without executing them. Verify that it doesn't
-# abort early by loading a second module whose dSYM script should also trigger
-# a warning.
+# warning for each dSYM script without executing them. Verify that warnings
+# are emitted for all modules, not just the first.
 
 # RUN: split-file %s %t
 # RUN: %clang_host -g %t/main.c -o %t/TestModule.out
 # RUN: %clang_host -g %t/main.c -o %t/TestModule2.out
+# RUN: %clang_host -g %t/main.c -o %t/TestModule3.out
 # RUN: mkdir -p %t/TestModule.out.dSYM/Contents/Resources/Python
 # RUN: mkdir -p %t/TestModule2.out.dSYM/Contents/Resources/Python
+# RUN: mkdir -p %t/TestModule3.out.dSYM/Contents/Resources/Python
 
 # RUN: cp %t/dsym_script.py %t/TestModule.out.dSYM/Contents/Resources/Python/TestModule.py
 # RUN: cp %t/dsym_script2.py %t/TestModule2.out.dSYM/Contents/Resources/Python/TestModule2.py
+# RUN: cp %t/dsym_script3.py %t/TestModule3.out.dSYM/Contents/Resources/Python/TestModule3.py
 # RUN: %lldb -b \
 # RUN:   -o 'settings set target.load-script-from-symbol-file warn' \
 # RUN:   -o 'target create %t/TestModule.out' \
-# RUN:   -o 'target modules add %t/TestModule2.out' 2>&1 \
-# RUN:   | FileCheck %s --implicit-check-not=DSYM_SCRIPT --implicit-check-not=DSYM_SCRIPT2
+# RUN:   -o 'target modules add %t/TestModule2.out %t/TestModule3.out' 2>&1 \
+# RUN:   | FileCheck %s --strict-whitespace \
+# RUN:                  --implicit-check-not=DSYM_SCRIPT --implicit-check-not=DSYM_SCRIPT2 --implicit-check-not=DSYM_SCRIPT3
 
-# CHECK-COUNT-1: warning: Found executable debug scripts. To run this script in this debug session
-# CHECK:      Following debug scripts were detected but not loaded:
-# CHECK-DAG: {{.*}}/TestModule.out.dSYM/Contents/Resources/Python/TestModule.py
-# CHECK-DAG: {{.*}}/TestModule2.out.dSYM/Contents/Resources/Python/TestModule2.py
+# CHECK: (lldb) target create
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-NEXT:{{^}}    - {{.*}}/TestModule.out.dSYM/Contents/Resources/Python/TestModule.py
+
+# CHECK: (lldb) target modules add
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-NEXT:{{^}}    - {{.*}}/TestModule2.out.dSYM/Contents/Resources/Python/TestModule2.py
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-NEXT:{{^}}    - {{.*}}/TestModule3.out.dSYM/Contents/Resources/Python/TestModule3.py
 
 #--- main.c
 int main() { return 0; }
@@ -36,3 +47,8 @@ def __lldb_init_module(debugger, internal_dict):
 import sys
 def __lldb_init_module(debugger, internal_dict):
     print("DSYM_SCRIPT2", file=sys.stderr)
+
+#--- dsym_script3.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("DSYM_SCRIPT3", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test b/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test
index ca75cfd1e0bbb..10c18583eb813 100644
--- a/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test
+++ b/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-multiple-modules.test
@@ -1,29 +1,40 @@
 # REQUIRES: python, asserts, !system-windows
 #
 # Test that when load-script-from-symbol-file is set to 'warn', LLDB emits a
-# warning for safe-path scripts without executing them. Verify that it doesn't
-# abort early by loading a second module whose script should also trigger a
-# warning.
+# warning for each safe-path script without executing them. Verify that warnings
+# are emitted for all modules, not just the first.
 
 # RUN: split-file %s %t
 # RUN: %clang_host %t/main.c -o %t/TestModule.out
 # RUN: %clang_host %t/main.c -o %t/TestModule2.out
+# RUN: %clang_host %t/main.c -o %t/TestModule3.out
 # RUN: mkdir -p %t/safe-path/TestModule
 # RUN: mkdir -p %t/safe-path/TestModule2
+# RUN: mkdir -p %t/safe-path/TestModule3
 
 # RUN: cp %t/script.py %t/safe-path/TestModule/TestModule.py
 # RUN: cp %t/script2.py %t/safe-path/TestModule2/TestModule2.py
+# RUN: cp %t/script3.py %t/safe-path/TestModule3/TestModule3.py
 # RUN: %lldb -b \
 # RUN:   -o 'settings set target.load-script-from-symbol-file warn' \
 # RUN:   -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
 # RUN:   -o 'target create %t/TestModule.out' \
-# RUN:   -o 'target modules add %t/TestModule2.out' 2>&1 \
-# RUN:   | FileCheck %s --implicit-check-not=SCRIPT_LOADED --implicit-check-not=SCRIPT2_LOADED
+# RUN:   -o 'target modules add %t/TestModule2.out %t/TestModule3.out' 2>&1 \
+# RUN:   | FileCheck %s --strict-whitespace \
+# RUN:                  --implicit-check-not=SCRIPT_LOADED --implicit-check-not=SCRIPT2_LOADED --implicit-check-not=SCRIPT3_LOADED
 
-# CHECK-COUNT-1: warning: Found executable debug scripts. To run this script in this debug session
-# CHECK:      Following debug scripts were detected but not loaded:
-# CHECK-DAG: {{.*}}/safe-path/TestModule/TestModule.py
-# CHECK-DAG: {{.*}}/safe-path/TestModule2/TestModule2.py
+# CHECK: (lldb) target create
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-NEXT:{{^}}    - {{.*}}/safe-path/TestModule/TestModule.py
+
+# CHECK: (lldb) target modules add
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-NEXT:{{^}}    - {{.*}}/safe-path/TestModule2/TestModule2.py
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-NEXT:{{^}}    - {{.*}}/safe-path/TestModule3/TestModule3.py
 
 #--- main.c
 int main() { return 0; }
@@ -37,3 +48,8 @@ def __lldb_init_module(debugger, internal_dict):
 import sys
 def __lldb_init_module(debugger, internal_dict):
     print("SCRIPT2_LOADED", file=sys.stderr)
+
+#--- script3.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("SCRIPT3_LOADED", file=sys.stderr)

>From c7ab9a75739b9a3af9229b9a69c8ca7fb68ea959 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 1 Apr 2026 15:31:25 +0100
Subject: [PATCH 3/3] fixup! batch list of scripts into single warning

---
 lldb/include/lldb/Core/Module.h               |  4 +-
 lldb/include/lldb/Target/Target.h             |  2 +
 lldb/source/Commands/CommandObjectTarget.cpp  |  5 ++-
 lldb/source/Core/Module.cpp                   | 31 ++-----------
 lldb/source/Core/ModuleList.cpp               |  9 +++-
 lldb/source/Target/Target.cpp                 | 40 +++++++++++++++--
 .../Darwin/dsym-warn-batch-dependents.test    | 42 ++++++++++++++++++
 .../UNIX/safe-path-warn-batch-dependents.test | 44 +++++++++++++++++++
 8 files changed, 144 insertions(+), 33 deletions(-)
 create mode 100644 lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-batch-dependents.test
 create mode 100644 lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-batch-dependents.test

diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h
index cfe006629ae99..f7e22dbb169a8 100644
--- a/lldb/include/lldb/Core/Module.h
+++ b/lldb/include/lldb/Core/Module.h
@@ -510,7 +510,9 @@ class Module : public std::enable_shared_from_this<Module>,
   ///     \b true if it is, \b false otherwise.
   bool IsLoadedInTarget(Target *target);
 
-  bool LoadScriptingResourceInTarget(Target *target, Status &error);
+  bool LoadScriptingResourceInTarget(
+      Target *target, Status &error,
+      llvm::SmallVector<std::string> &warned_script_paths);
 
   /// Get the number of compile units for this module.
   ///
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 77b8f04a4b3b8..cccc6fb81a3ca 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -800,6 +800,8 @@ class Target : public std::enable_shared_from_this<Target>,
   const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
   GetScriptedFrameProviderDescriptors() const;
 
+  void ReportScriptLoading(const llvm::SmallVector<std::string> &warned_script_paths);
+
 protected:
   /// Invalidate all potentially cached frame providers for all threads
   /// and trigger a stack changed event for all threads.
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index dccb7256b1cb7..1ed0c39426e6c 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -4401,7 +4401,8 @@ class CommandObjectTargetSymbolsAdd : public CommandObjectParsed {
           // Make sure we load any scripting resources that may be embedded
           // in the debug info files in case the platform supports that.
           Status error;
-          module_sp->LoadScriptingResourceInTarget(target, error);
+          llvm::SmallVector<std::string> warned_script_paths;
+          module_sp->LoadScriptingResourceInTarget(target, error, warned_script_paths);
           if (error.Fail() && error.AsCString())
             result.AppendWarningWithFormat(
                 "unable to load scripting data for module %s - error "
@@ -4411,6 +4412,8 @@ class CommandObjectTargetSymbolsAdd : public CommandObjectParsed {
                     .GetCString(),
                 error.AsCString());
 
+          target->ReportScriptLoading(warned_script_paths);
+
           flush = true;
           result.SetStatus(eReturnStatusSuccessFinishResult);
           return true;
diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index 305c33df2eafc..d503427fd3553 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -1432,7 +1432,9 @@ static bool LoadScriptingModule(const FileSpec &scripting_fspec,
       /*module_sp*/ nullptr, /*extra_path*/ {}, target.shared_from_this());
 }
 
-bool Module::LoadScriptingResourceInTarget(Target *target, Status &error) {
+bool Module::LoadScriptingResourceInTarget(
+    Target *target, Status &error,
+    llvm::SmallVector<std::string> &warned_script_paths) {
   Log *log = GetLog(LLDBLog::Modules);
 
   if (!target) {
@@ -1466,8 +1468,6 @@ bool Module::LoadScriptingResourceInTarget(Target *target, Status &error) {
   if (!feedback_stream.Empty())
     debugger.ReportWarning(feedback_stream.GetString().str(), debugger.GetID());
 
-  llvm::SmallVector<std::string> warned_modules;
-
   for (const auto &[scripting_fspec, load_style] : file_specs) {
     if (load_style == eLoadScriptFromSymFileFalse)
       continue;
@@ -1476,7 +1476,7 @@ bool Module::LoadScriptingResourceInTarget(Target *target, Status &error) {
       continue;
 
     if (load_style == eLoadScriptFromSymFileWarn) {
-      warned_modules.emplace_back(scripting_fspec.GetPath());
+      warned_script_paths.emplace_back(scripting_fspec.GetPath());
       continue;
     }
 
@@ -1490,29 +1490,6 @@ bool Module::LoadScriptingResourceInTarget(Target *target, Status &error) {
     }
   }
 
-  if (!warned_modules.empty()) {
-    static std::once_flag s_warn_once;
-    // clang-format off
-    debugger.ReportWarning(
-R"(Found executable debug scripts. To run a script in this debug session:
-
-    command script import "/path/to/script.py"
-
-To run all discovered debug scripts in this session:
-
-    settings set target.load-script-from-symbol-file true
-)", debugger.GetID(), &s_warn_once);
-
-    debugger.ReportWarning(llvm::formatv(
-R"(The following debug scripts were detected but not loaded:
-
-    - {0}
-)", llvm::join(warned_modules, "\n    -")));
-    // clang-format on
-
-    return false;
-  }
-
   return true;
 }
 
diff --git a/lldb/source/Core/ModuleList.cpp b/lldb/source/Core/ModuleList.cpp
index b54d787de3512..6159060c3b532 100644
--- a/lldb/source/Core/ModuleList.cpp
+++ b/lldb/source/Core/ModuleList.cpp
@@ -36,8 +36,10 @@
 #endif
 
 #include "clang/Driver/Driver.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/Threading.h"
 #include "llvm/Support/raw_ostream.h"
 
@@ -1344,10 +1346,12 @@ bool ModuleList::LoadScriptingResourcesInTarget(Target *target,
   const ModuleList tmp_module_list(*this);
   m_modules_mutex.unlock();
 
+  llvm::SmallVector<std::string> warned_script_paths;
+
   for (auto module : tmp_module_list.ModulesNoLocking()) {
     if (module) {
       Status error;
-      if (!module->LoadScriptingResourceInTarget(target, error)) {
+      if (!module->LoadScriptingResourceInTarget(target, error, warned_script_paths)) {
         if (error.Fail() && error.AsCString()) {
           error = Status::FromErrorStringWithFormat(
               "unable to load scripting data for "
@@ -1363,6 +1367,9 @@ bool ModuleList::LoadScriptingResourcesInTarget(Target *target,
       }
     }
   }
+
+  target->ReportScriptLoading(warned_script_paths);
+
   return errors.empty();
 }
 
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 2a3832225f9b7..faa966a618902 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -70,7 +70,9 @@
 
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/ErrorExtras.h"
+#include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/ThreadPool.h"
 
 #include <memory>
@@ -1543,9 +1545,10 @@ Module *Target::GetExecutableModulePointer() {
 }
 
 static void LoadScriptingResourceForModule(const ModuleSP &module_sp,
-                                           Target *target) {
+                                           Target *target,
+                                           llvm::SmallVector<std::string> &warned) {
   Status error;
-  if (module_sp && !module_sp->LoadScriptingResourceInTarget(target, error)) {
+  if (module_sp && !module_sp->LoadScriptingResourceInTarget(target, error, warned)) {
     if (error.AsCString())
       target->GetDebugger().GetAsyncErrorStream()->Printf(
           "unable to load scripting data for module %s - error reported was "
@@ -1857,12 +1860,16 @@ void Target::ModulesDidLoad(ModuleList &module_list) {
 
   const size_t num_images = module_list.GetSize();
   if (m_valid && num_images) {
+    llvm::SmallVector<std::string> warned_script_paths;
     for (size_t idx = 0; idx < num_images; ++idx) {
       ModuleSP module_sp(module_list.GetModuleAtIndex(idx));
-      LoadScriptingResourceForModule(module_sp, this);
+      LoadScriptingResourceForModule(module_sp, this, warned_script_paths);
       LoadTypeSummariesForModule(module_sp);
       LoadFormattersForModule(module_sp);
     }
+
+    ReportScriptLoading(warned_script_paths);
+
     m_breakpoint_list.UpdateBreakpoints(module_list, true, false);
     m_internal_breakpoint_list.UpdateBreakpoints(module_list, true, false);
     if (m_process_sp) {
@@ -5387,6 +5394,33 @@ void Target::NotifyBreakpointChanged(
     BroadcastEvent(Target::eBroadcastBitBreakpointChanged, breakpoint_data_sp);
 }
 
+void Target::ReportScriptLoading(const llvm::SmallVector<std::string> &warned_script_paths) {
+  if (warned_script_paths.empty())
+    return;
+
+  static std::once_flag s_warn_once;
+
+  auto &debugger = GetDebugger();
+
+  // clang-format off
+  debugger.ReportWarning(
+R"(Found executable debug scripts. To run a script in this debug session:
+
+    command script import "/path/to/script.py"
+
+To run all discovered debug scripts in this session:
+
+    settings set target.load-script-from-symbol-file true
+)", debugger.GetID(), &s_warn_once);
+
+      debugger.ReportWarning(llvm::formatv(
+R"(The following debug scripts were detected but not loaded:
+
+    - {0}
+)", llvm::join(warned_script_paths, "\n    - ")), debugger.GetID());
+  // clang-format on
+}
+
 // FIXME: the language plugin should expression options dynamically and
 // we should validate here (by asking the language plugin) that the options
 // being set/retrieved are actually valid options.
diff --git a/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-batch-dependents.test b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-batch-dependents.test
new file mode 100644
index 0000000000000..904fe550e43cc
--- /dev/null
+++ b/lldb/test/Shell/Platform/AutoLoad/Darwin/dsym-warn-batch-dependents.test
@@ -0,0 +1,42 @@
+# REQUIRES: python, system-darwin
+#
+# Test that when load-script-from-symbol-file is set to 'warn' and multiple
+# dependent modules with dSYM scripts are loaded in a single batch (via
+# target create), LLDB emits a single warning listing all detected scripts.
+
+# RUN: split-file %s %t
+# RUN: %clang_host -g -dynamiclib %t/lib.c -o %t/libTestLib1.dylib
+# RUN: %clang_host -g -dynamiclib %t/lib.c -o %t/libTestLib2.dylib
+# RUN: %clang_host -g %t/main.c -o %t/main.out %t/libTestLib1.dylib %t/libTestLib2.dylib
+# RUN: mkdir -p %t/libTestLib1.dylib.dSYM/Contents/Resources/Python
+# RUN: mkdir -p %t/libTestLib2.dylib.dSYM/Contents/Resources/Python
+# RUN: cp %t/dsym_script1.py %t/libTestLib1.dylib.dSYM/Contents/Resources/Python/libTestLib1.py
+# RUN: cp %t/dsym_script2.py %t/libTestLib2.dylib.dSYM/Contents/Resources/Python/libTestLib2.py
+# RUN: %lldb -b \
+# RUN:   -o 'settings set target.load-script-from-symbol-file warn' \
+# RUN:   -o 'target create %t/main.out' 2>&1 \
+# RUN:   | FileCheck %s --strict-whitespace \
+# RUN:                  --implicit-check-not=DSYM_SCRIPT1 --implicit-check-not=DSYM_SCRIPT2
+
+# Both dependent libraries should appear in a single warning.
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-DAG:{{^}}    - {{.*}}/libTestLib1.dylib.dSYM/Contents/Resources/Python/libTestLib1.py
+# CHECK-DAG:{{^}}    - {{.*}}/libTestLib2.dylib.dSYM/Contents/Resources/Python/libTestLib2.py
+
+#--- main.c
+extern int lib_func(void);
+int main() { return lib_func(); }
+
+#--- lib.c
+int lib_func(void) { return 0; }
+
+#--- dsym_script1.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("DSYM_SCRIPT1", file=sys.stderr)
+
+#--- dsym_script2.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("DSYM_SCRIPT2", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-batch-dependents.test b/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-batch-dependents.test
new file mode 100644
index 0000000000000..f46e725338131
--- /dev/null
+++ b/lldb/test/Shell/Platform/AutoLoad/UNIX/safe-path-warn-batch-dependents.test
@@ -0,0 +1,44 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that when load-script-from-symbol-file is set to 'warn' and multiple
+# dependent modules with safe-path scripts are loaded in a single batch (via
+# target create), LLDB emits a single warning listing all detected scripts.
+
+# RUN: split-file %s %t
+# RUN: %clang_host -shared -fPIC %t/lib.c -o %t/libTestLib1.so
+# RUN: %clang_host -shared -fPIC %t/lib.c -o %t/libTestLib2.so
+# RUN: %clang_host %t/main.c -o %t/main.out %t/libTestLib1.so %t/libTestLib2.so \
+# RUN:             -Wl,-rpath,%t
+# RUN: mkdir -p %t/safe-path/libTestLib1
+# RUN: mkdir -p %t/safe-path/libTestLib2
+# RUN: cp %t/script1.py %t/safe-path/libTestLib1/libTestLib1.py
+# RUN: cp %t/script2.py %t/safe-path/libTestLib2/libTestLib2.py
+# RUN: %lldb -b \
+# RUN:   -o 'settings set target.load-script-from-symbol-file warn' \
+# RUN:   -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN:   -o 'target create %t/main.out' 2>&1 \
+# RUN:   | FileCheck %s --strict-whitespace \
+# RUN:                  --implicit-check-not=SCRIPT_LOADED1 --implicit-check-not=SCRIPT_LOADED2
+
+# Both dependent libraries should appear in a single warning.
+# CHECK:      warning: {{.*}}The following debug scripts were detected but not loaded:
+# CHECK-EMPTY:
+# CHECK-DAG:{{^}}    - {{.*}}/safe-path/libTestLib1/libTestLib1.py
+# CHECK-DAG:{{^}}    - {{.*}}/safe-path/libTestLib2/libTestLib2.py
+
+#--- main.c
+extern int lib_func(void);
+int main() { return lib_func(); }
+
+#--- lib.c
+int lib_func(void) { return 0; }
+
+#--- script1.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("SCRIPT_LOADED1", file=sys.stderr)
+
+#--- script2.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+    print("SCRIPT_LOADED2", file=sys.stderr)



More information about the lldb-commits mailing list