[Lldb-commits] [lldb] [WIP][lldb] Add mechanism for auto-loading Python scripts from pre-configured paths (PR #187031)
Michael Buch via lldb-commits
lldb-commits at lists.llvm.org
Wed Mar 18 06:15:05 PDT 2026
https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/187031
>From b76f9ce3cb7562563787c1b5c8c5c57c5b5a5b7a Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 18 Mar 2026 09:31:45 +0000
Subject: [PATCH 1/2] [lldb][Platform][NFC] Move SanitizedScriptingModuleName
into ScriptInterpreter
In preparation for https://github.com/llvm/llvm-project/pull/187031
The `SanitizedScriptingModuleName` will be re-used from `Platform` (in
addition to `PlatformDarwin` where it currently lives). To do that we'll
need to move it to a common place. `ScriptInterpreter` seems like the
most natural place for this to live.
I introduced a new virtual `GetSanitizedScriptingModuleName` that
`ScriptInterpreter`s can override in the future if they have their own
sanitization logic. I made the default implementation the one we've been
using for Python because that way the unit-tests that currently mock the
`ScriptInterpreter` don't need to copy the implementation.
Also made `WarnIfInvalidUnsanitizedScriptExists` a protected static
function on `Platform` because it didn't seem right for that to also
live in `ScriptInterpreter`.
---
.../lldb/Interpreter/ScriptInterpreter.h | 45 ++++++++
lldb/include/lldb/Target/Platform.h | 9 ++
lldb/source/Interpreter/ScriptInterpreter.cpp | 25 +++++
.../Platform/MacOSX/PlatformDarwin.cpp | 103 ++----------------
lldb/source/Target/Platform.cpp | 31 ++++++
5 files changed, 117 insertions(+), 96 deletions(-)
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 557d73a415452..dac74cc8ab1da 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -572,6 +572,51 @@ class ScriptInterpreter : public PluginInterface {
return {};
}
+ /// Holds an lldb_private::Module name and a "sanitized" version
+ /// of it for the purposes of loading a script of that name by
+ /// the relevant ScriptInterpreter.
+ ///
+ /// E.g., for Python the sanitized name can't include:
+ /// * Special characters: '-', ' ', '.'
+ /// * Python keywords
+ class SanitizedScriptingModuleName {
+ public:
+ SanitizedScriptingModuleName(std::string name, std::string sanitized_name,
+ std::string conflicting_keyword)
+ : m_original_name(std::move(name)),
+ m_sanitized_name(std::move(sanitized_name)),
+ m_conflicting_keyword(std::move(conflicting_keyword)) {}
+
+ /// Returns \c true if this name is a keyword in the associated scripting
+ /// language.
+ bool IsKeyword() const { return !m_conflicting_keyword.empty(); }
+
+ /// Returns \c true if the original name has been sanitized (i.e., required
+ /// changes).
+ bool RequiredSanitization() const {
+ return m_sanitized_name != m_original_name;
+ }
+
+ llvm::StringRef GetSanitizedName() const { return m_sanitized_name; }
+ llvm::StringRef GetOriginalName() const { return m_original_name; }
+ llvm::StringRef GetConflictingKeyword() const {
+ return m_conflicting_keyword;
+ }
+
+ private:
+ std::string m_original_name;
+ std::string m_sanitized_name;
+
+ /// If the m_sanitized_name conflicts with a keyword for the
+ /// ScriptInterpreter language associated with this
+ /// SanitizedScriptingModuleName, is set to the conflicting keyword. Empty
+ /// otherwise.
+ std::string m_conflicting_keyword;
+ };
+
+ virtual SanitizedScriptingModuleName
+ GetSanitizedScriptingModuleName(llvm::StringRef name);
+
lldb::DataExtractorSP
GetDataExtractorFromSBData(const lldb::SBData &data) const;
diff --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h
index 1ba7516f0102c..1c30bc1003ec9 100644
--- a/lldb/include/lldb/Target/Platform.h
+++ b/lldb/include/lldb/Target/Platform.h
@@ -21,6 +21,7 @@
#include "lldb/Core/UserSettingsController.h"
#include "lldb/Host/File.h"
#include "lldb/Interpreter/Options.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Target/StopInfo.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/ConstString.h"
@@ -1060,6 +1061,14 @@ class Platform : public PluginInterface {
virtual const char *GetCacheHostname();
+ /// If we did some replacements of reserved characters, and a
+ /// file with the untampered name exists, then warn the user
+ /// that the file as-is shall not be loaded.
+ static void WarnIfInvalidUnsanitizedScriptExists(
+ Stream &os,
+ const ScriptInterpreter::SanitizedScriptingModuleName &sanitized_name,
+ const FileSpec &original_fspec, const FileSpec &fspec);
+
private:
typedef std::function<Status(const ModuleSpec &)> ModuleResolver;
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 5e8478c2670bb..b00f4db528ce3 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -214,6 +214,31 @@ ScriptInterpreter::AcquireInterpreterLock() {
return std::make_unique<ScriptInterpreterLocker>();
}
+ScriptInterpreter::SanitizedScriptingModuleName
+ScriptInterpreter::GetSanitizedScriptingModuleName(llvm::StringRef name) {
+ std::string sanitized_name(name);
+ std::string conflicting_keyword;
+
+ // FIXME: for Python, don't allow certain characters in imported module
+ // filenames. Theoretically, different scripting languages may have
+ // different sets of forbidden tokens in filenames, and that should
+ // be dealt with by each ScriptInterpreter. For now, just replace dots
+ // with underscores. In order to support anything other than Python
+ // this will need to be reworked.
+ llvm::replace(sanitized_name, '.', '_');
+ llvm::replace(sanitized_name, ' ', '_');
+ llvm::replace(sanitized_name, '-', '_');
+ llvm::replace(sanitized_name, '+', 'x');
+
+ if (IsReservedWord(sanitized_name.c_str())) {
+ conflicting_keyword = sanitized_name;
+ sanitized_name.insert(sanitized_name.begin(), '_');
+ }
+
+ return ScriptInterpreter::SanitizedScriptingModuleName(
+ name.str(), std::move(sanitized_name), std::move(conflicting_keyword));
+}
+
static void ReadThreadBytesReceived(void *baton, const void *src,
size_t src_len) {
if (src && src_len) {
diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
index 2f2eed3359a93..39abd9a8a1176 100644
--- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
+++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
@@ -80,97 +80,6 @@ static Status ExceptionMaskValidator(const char *string, void *unused) {
return {};
}
-namespace {
-/// Holds an lldb_private::Module name and a "sanitized" version
-/// of it for the purposes of loading a script of that name by
-/// the relevant ScriptInterpreter.
-///
-/// E.g., for Python the sanitized name can't include:
-/// * Special characters: '-', ' ', '.'
-/// * Python keywords
-class SanitizedScriptingModuleName {
-public:
- SanitizedScriptingModuleName(llvm::StringRef name,
- ScriptInterpreter &script_interpreter)
- : m_original_name(name), m_sanitized_name(name.str()) {
- // FIXME: for Python, don't allow certain characters in imported module
- // filenames. Theoretically, different scripting languages may have
- // different sets of forbidden tokens in filenames, and that should
- // be dealt with by each ScriptInterpreter. For now, just replace dots
- // with underscores. In order to support anything other than Python
- // this will need to be reworked.
- llvm::replace(m_sanitized_name, '.', '_');
- llvm::replace(m_sanitized_name, ' ', '_');
- llvm::replace(m_sanitized_name, '-', '_');
- llvm::replace(m_sanitized_name, '+', 'x');
-
- if (script_interpreter.IsReservedWord(m_sanitized_name.c_str())) {
- m_conflicting_keyword = m_sanitized_name;
- m_sanitized_name.insert(m_sanitized_name.begin(), '_');
- }
- }
-
- /// Returns \c true if this name is a keyword in the associated scripting
- /// language.
- bool IsKeyword() const { return !m_conflicting_keyword.empty(); }
-
- /// Returns \c true if the original name has been sanitized (i.e., required
- /// changes).
- bool RequiredSanitization() const {
- return m_sanitized_name != m_original_name;
- }
-
- llvm::StringRef GetSanitizedName() const { return m_sanitized_name; }
- llvm::StringRef GetOriginalName() const { return m_original_name; }
- llvm::StringRef GetConflictingKeyword() const {
- return m_conflicting_keyword;
- }
-
- /// If we did some replacements of reserved characters, and a
- /// file with the untampered name exists, then warn the user
- /// that the file as-is shall not be loaded.
- void WarnIfInvalidUnsanitizedScriptExists(Stream &os,
- const FileSpec &original_fspec,
- const FileSpec &fspec) const {
- if (!RequiredSanitization())
- return;
-
- // Path to unsanitized script name doesn't exist. Nothing to warn about.
- if (!FileSystem::Instance().Exists(original_fspec))
- return;
-
- std::string reason_for_complaint =
- IsKeyword() ? llvm::formatv("conflicts with the keyword '{0}'",
- GetConflictingKeyword())
- .str()
- : "contains reserved characters";
-
- if (FileSystem::Instance().Exists(fspec))
- os.Format(
- "debug script '{0}' cannot be loaded because '{1}' {2}. "
- "Ignoring '{1}' and loading '{3}' instead.\n",
- original_fspec.GetPath(), original_fspec.GetFilename(),
- std::move(reason_for_complaint), fspec.GetFilename());
- else
- os.Format(
- "debug script '{0}' cannot be loaded because '{1}' {2}. "
- "If you intend to have this script loaded, please rename it to "
- "'{3}' and retry.\n",
- original_fspec.GetPath(), original_fspec.GetFilename(),
- std::move(reason_for_complaint), fspec.GetFilename());
- }
-
-private:
- llvm::StringRef m_original_name;
- std::string m_sanitized_name;
-
- /// If the m_sanitized_name conflicts with a keyword for the ScriptInterpreter
- /// language associated with this SanitizedScriptingModuleName, is set to the
- /// conflicting keyword. Empty otherwise.
- std::string m_conflicting_keyword;
-};
-} // namespace
-
/// Destructor.
///
/// The destructor is virtual since this class is designed to be
@@ -297,9 +206,11 @@ FileSpecList PlatformDarwin::LocateExecutableScriptingResourcesFromDSYM(
FileSpecList file_list;
while (module_spec.GetFilename()) {
- SanitizedScriptingModuleName sanitized_name(
- module_spec.GetFilename().GetStringRef(),
- *target.GetDebugger().GetScriptInterpreter());
+ ScriptInterpreter::SanitizedScriptingModuleName sanitized_name =
+ target.GetDebugger()
+ .GetScriptInterpreter()
+ ->GetSanitizedScriptingModuleName(
+ module_spec.GetFilename().GetStringRef());
StreamString path_string;
StreamString original_path_string;
@@ -319,8 +230,8 @@ FileSpecList PlatformDarwin::LocateExecutableScriptingResourcesFromDSYM(
FileSpec orig_script_fspec(original_path_string.GetString());
FileSystem::Instance().Resolve(orig_script_fspec);
- sanitized_name.WarnIfInvalidUnsanitizedScriptExists(
- feedback_stream, orig_script_fspec, script_fspec);
+ WarnIfInvalidUnsanitizedScriptExists(feedback_stream, sanitized_name,
+ orig_script_fspec, script_fspec);
if (FileSystem::Instance().Exists(script_fspec)) {
file_list.Append(script_fspec);
diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp
index 647f8389dc41b..e159632a446fd 100644
--- a/lldb/source/Target/Platform.cpp
+++ b/lldb/source/Target/Platform.cpp
@@ -2111,6 +2111,37 @@ Platform::LocateModuleCallback Platform::GetLocateModuleCallback() const {
return m_locate_module_callback;
}
+void Platform::WarnIfInvalidUnsanitizedScriptExists(
+ Stream &os,
+ const ScriptInterpreter::SanitizedScriptingModuleName &sanitized_name,
+ const FileSpec &original_fspec, const FileSpec &fspec) {
+ if (!sanitized_name.RequiredSanitization())
+ return;
+
+ // Path to unsanitized script name doesn't exist. Nothing to warn about.
+ if (!FileSystem::Instance().Exists(original_fspec))
+ return;
+
+ std::string reason_for_complaint =
+ sanitized_name.IsKeyword()
+ ? llvm::formatv("conflicts with the keyword '{0}'",
+ sanitized_name.GetConflictingKeyword())
+ .str()
+ : "contains reserved characters";
+
+ if (FileSystem::Instance().Exists(fspec))
+ os.Format("debug script '{0}' cannot be loaded because '{1}' {2}. "
+ "Ignoring '{1}' and loading '{3}' instead.\n",
+ original_fspec.GetPath(), original_fspec.GetFilename(),
+ std::move(reason_for_complaint), fspec.GetFilename());
+ else
+ os.Format("debug script '{0}' cannot be loaded because '{1}' {2}. "
+ "If you intend to have this script loaded, please rename it to "
+ "'{3}' and retry.\n",
+ original_fspec.GetPath(), original_fspec.GetFilename(),
+ std::move(reason_for_complaint), fspec.GetFilename());
+}
+
PlatformSP PlatformList::GetOrCreate(llvm::StringRef name) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
for (const PlatformSP &platform_sp : m_platforms) {
>From 4065e8126ed68540d93320a20ad2e442638d5c79 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Tue, 10 Mar 2026 14:17:59 +0000
Subject: [PATCH 2/2] [lldb] Add mechanism for auto-loading Python scripts from
pre-configured paths
Depends on:
* https://github.com/llvm/llvm-project/pull/187229
(only last commit is relevant)
This patch implements the base infrastructure described in this [RFC re. Moving libc++ data-formatters out of LLDB](https://discourse.llvm.org/t/rfc-lldb-moving-libc-data-formatters-out-of-lldb/89591)
The intention is to provide vendors with a way to pre-configure a set of paths that LLDB can automatically ingest formatter scripts from.
Three main changes
---
lldb/include/lldb/Core/Debugger.h | 5 +
lldb/include/lldb/Target/Platform.h | 17 +-
lldb/source/Core/CMakeLists.txt | 18 +
lldb/source/Core/CoreProperties.td | 4 +
lldb/source/Core/Debugger.cpp | 51 ++
lldb/source/Core/Module.cpp | 3 +
lldb/source/Core/SafeAutoloadPaths.inc.in | 1 +
.../Platform/MacOSX/PlatformDarwin.cpp | 3 +-
.../Plugins/Platform/MacOSX/PlatformDarwin.h | 4 +-
lldb/source/Target/Platform.cpp | 73 ++-
lldb/source/Target/TargetProperties.td | 4 +
.../Darwin/dsym-takes-priority.test | 31 ++
.../safe-path-fallback-no-dsym-script.test | 24 +
.../SafeAutoLoad/UNIX/basic-load.test | 24 +
.../SafeAutoLoad/UNIX/last-path-wins.test | 31 ++
.../UNIX/no-match-nested-dir.test | 24 +
.../UNIX/no-match-wrong-dirname.test | 24 +
.../UNIX/no-match-wrong-scriptname.test | 24 +
.../UNIX/no-paths-configured.test | 22 +
.../UNIX/special-chars-sanitized.test | 24 +
.../SafeAutoLoad/UNIX/submodule-import.test | 30 ++
.../SafeAutoLoad/UNIX/subpackage-import.test | 30 ++
lldb/unittests/Platform/PlatformTest.cpp | 471 ++++++++++++++++++
23 files changed, 935 insertions(+), 7 deletions(-)
create mode 100644 lldb/source/Core/SafeAutoloadPaths.inc.in
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/Darwin/dsym-takes-priority.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/Darwin/safe-path-fallback-no-dsym-script.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/basic-load.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/last-path-wins.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-nested-dir.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-dirname.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-scriptname.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-paths-configured.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/special-chars-sanitized.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/submodule-import.test
create mode 100644 lldb/test/Shell/Platform/SafeAutoLoad/UNIX/subpackage-import.test
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 96f586a6f1989..e8f830337d26a 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -76,6 +76,9 @@ struct TestingProperties : public Properties {
TestingProperties();
bool GetInjectVarLocListError() const;
static TestingProperties &GetGlobalTestingProperties();
+ void SetSafeAutoLoadPaths(FileSpecList paths);
+ void AppendSafeAutoLoadPaths(FileSpec path);
+ FileSpecList GetSafeAutoLoadPaths() const;
};
#endif
@@ -131,6 +134,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
static void AssertCallback(llvm::StringRef message, llvm::StringRef backtrace,
llvm::StringRef prompt);
+ static FileSpecList GetSafeAutoLoadPaths();
+
void Clear();
void DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry);
diff --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h
index 1c30bc1003ec9..d150cc35a1bd1 100644
--- a/lldb/include/lldb/Target/Platform.h
+++ b/lldb/include/lldb/Target/Platform.h
@@ -275,9 +275,22 @@ class Platform : public PluginInterface {
//
// Locating the file should happen only on the local computer or using the
// current computers global settings.
+ FileSpecList LocateExecutableScriptingResources(Target *target,
+ Module &module,
+ Stream &feedback_stream);
+
virtual FileSpecList
- LocateExecutableScriptingResources(Target *target, Module &module,
- Stream &feedback_stream);
+ LocateExecutableScriptingResourcesImpl(Target *target, Module &module,
+ Stream &feedback_stream);
+
+ /// Helper function for \c LocateExecutableScriptingResources
+ /// which gathers FileSpecs for executable scripts from
+ /// pre-configured "safe" auto-load paths.
+ ///
+ /// Looks for a script at:
+ /// \c <safe-path>/<module-name>/<module-name>.py
+ static FileSpecList LocateExecutableScriptingResourcesFromSafePaths(
+ Stream &feedback_stream, FileSpec module_spec, const Target &target);
/// \param[in] module_spec
/// The ModuleSpec of a binary to find.
diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt
index df35bd5c025f3..8770039ae412e 100644
--- a/lldb/source/Core/CMakeLists.txt
+++ b/lldb/source/Core/CMakeLists.txt
@@ -85,6 +85,24 @@ add_lldb_library(lldbCore NO_PLUGIN_DEPENDENCIES
clangDriver
)
+set(LLDB_SAFE_AUTO_LOAD_PATHS "" CACHE STRING
+ "Semicolon-separated list of paths that LLDB will automatically load scripting resources from.")
+
+# Turn list of paths into a comma-separated list to be #include'd
+# into a C-array.
+set(_entries "")
+foreach(path IN LISTS LLDB_SAFE_AUTO_LOAD_PATHS)
+ string(REPLACE "\\" "\\\\" escaped_path "${path}")
+ list(APPEND _entries "\"${escaped_path}\",")
+endforeach()
+string(JOIN "\n" SAFE_PATH_ENTRIES "${_entries}")
+
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/SafeAutoloadPaths.inc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/SafeAutoloadPaths.inc
+ @ONLY
+)
+
add_dependencies(lldbCore
LLDBCorePropertiesGen
LLDBCorePropertiesEnumGen)
diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td
index 03326b130e067..8cea0931868aa 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -60,6 +60,10 @@ let Definition = "testing", Path = "testing" in {
Global,
DefaultFalse,
Desc<"Used for testing LLDB only. Hide locations of local variables.">;
+ def SafeAutoloadPaths : Property<"safe-auto-load-paths", "FileSpecList">,
+ DefaultStringValue<"">,
+ Desc<"List of paths that LLDB will automatically "
+ "load scripting resources from.">;
}
#endif
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index fc099891443df..3d4bd5b55b434 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -32,6 +32,7 @@
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/OptionValue.h"
+#include "lldb/Interpreter/OptionValueFileSpecList.h"
#include "lldb/Interpreter/OptionValueLanguage.h"
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Interpreter/OptionValueSInt64.h"
@@ -212,6 +213,27 @@ enum {
};
#endif
+static const FileSpecList &GetDefaultSafeAutoLoadPaths() {
+ static const FileSpecList sSafePaths = [] {
+ // FIXME: in c++20 this could be a std::array (with CTAD deduced size)
+ // and we could statically assert that all members are non-empty.
+ const llvm::SmallVector<llvm::StringRef> kVendorSafePaths = {
+#include "SafeAutoloadPaths.inc"
+ };
+ FileSpecList fspecs;
+ for (auto path : kVendorSafePaths) {
+ assert(!path.empty());
+ LLDB_LOG(GetLog(SystemLog::System), "Safe auto-load path configured: {0}",
+ path);
+ fspecs.EmplaceBack(path);
+ }
+
+ return fspecs;
+ }();
+
+ return sSafePaths;
+}
+
#ifndef NDEBUG
TestingProperties::TestingProperties() {
m_collection_sp = std::make_shared<OptionValueProperties>("testing");
@@ -228,6 +250,27 @@ TestingProperties &TestingProperties::GetGlobalTestingProperties() {
static TestingProperties g_testing_properties;
return g_testing_properties;
}
+
+void TestingProperties::SetSafeAutoLoadPaths(FileSpecList paths) {
+ const uint32_t idx = ePropertySafeAutoloadPaths;
+ OptionValueFileSpecList *option_value =
+ m_collection_sp->GetPropertyAtIndexAsOptionValueFileSpecList(idx);
+ assert(option_value);
+ option_value->SetCurrentValue(std::move(paths));
+}
+
+void TestingProperties::AppendSafeAutoLoadPaths(FileSpec path) {
+ const uint32_t idx = ePropertySafeAutoloadPaths;
+ OptionValueFileSpecList *option_value =
+ m_collection_sp->GetPropertyAtIndexAsOptionValueFileSpecList(idx);
+ assert(option_value);
+ option_value->AppendCurrentValue(path);
+}
+
+FileSpecList TestingProperties::GetSafeAutoLoadPaths() const {
+ const uint32_t idx = ePropertySafeAutoloadPaths;
+ return GetPropertyAtIndexAs<FileSpecList>(idx, GetDefaultSafeAutoLoadPaths());
+}
#endif
LoadPluginCallbackType Debugger::g_load_plugin_callback = nullptr;
@@ -2547,3 +2590,11 @@ StructuredData::DictionarySP Debugger::GetBuildConfiguration() {
AddLLVMTargets(*config_up);
return config_up;
}
+
+FileSpecList Debugger::GetSafeAutoLoadPaths() {
+#ifndef NDEBUG
+ return TestingProperties::GetGlobalTestingProperties().GetSafeAutoLoadPaths();
+#else
+ return GetDefaultSafeAutoLoadPaths();
+#endif
+}
diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index bbab89ff8db4f..eb0345158dd2a 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -1489,6 +1489,9 @@ To run all discovered debug scripts in this session:
return false;
}
+ LLDB_LOG(GetLog(LLDBLog::Modules), "Auto-loading {0}",
+ scripting_fspec.GetPath());
+
StreamString scripting_stream;
scripting_fspec.Dump(scripting_stream.AsRawOstream());
LoadScriptOptions options;
diff --git a/lldb/source/Core/SafeAutoloadPaths.inc.in b/lldb/source/Core/SafeAutoloadPaths.inc.in
new file mode 100644
index 0000000000000..4312239c10a63
--- /dev/null
+++ b/lldb/source/Core/SafeAutoloadPaths.inc.in
@@ -0,0 +1 @@
+ at SAFE_PATH_ENTRIES@
diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
index 39abd9a8a1176..960616b2fef1c 100644
--- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
+++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
@@ -251,7 +251,7 @@ FileSpecList PlatformDarwin::LocateExecutableScriptingResourcesFromDSYM(
return file_list;
}
-FileSpecList PlatformDarwin::LocateExecutableScriptingResources(
+FileSpecList PlatformDarwin::LocateExecutableScriptingResourcesImpl(
Target *target, Module &module, Stream &feedback_stream) {
if (!target)
return {};
@@ -268,7 +268,6 @@ FileSpecList PlatformDarwin::LocateExecutableScriptingResources(
// stripped while leaving "this.binary.file" as-is.
const FileSpec &module_spec = module.GetFileSpec();
-
if (!module_spec)
return {};
diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h
index e884bcba5c2cc..f17636e8b2e42 100644
--- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h
+++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h
@@ -68,8 +68,8 @@ class PlatformDarwin : public PlatformPOSIX {
FileSpec &sym_file) override;
FileSpecList
- LocateExecutableScriptingResources(Target *target, Module &module,
- Stream &feedback_stream) override;
+ LocateExecutableScriptingResourcesImpl(Target *target, Module &module_spec,
+ Stream &feedback_stream) override;
Status GetSharedModule(const ModuleSpec &module_spec, Process *process,
lldb::ModuleSP &module_sp,
diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp
index e159632a446fd..c0933b7e78f40 100644
--- a/lldb/source/Target/Platform.cpp
+++ b/lldb/source/Target/Platform.cpp
@@ -27,6 +27,7 @@
#include "lldb/Interpreter/OptionValueFileSpec.h"
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Interpreter/Property.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/ModuleCache.h"
#include "lldb/Target/Platform.h"
@@ -41,6 +42,7 @@
#include "lldb/Utility/StructuredData.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
// Define these constants from POSIX mman.h rather than include the file so
@@ -155,10 +157,79 @@ Status Platform::GetFileWithUUID(const FileSpec &platform_file,
return Status();
}
+FileSpecList Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ Stream &feedback_stream, FileSpec module_spec, const Target &target) {
+ assert(module_spec);
+ assert(target.GetDebugger().GetScriptInterpreter());
+
+ // For now only Python scripts supported for auto-loading.
+ if (target.GetDebugger().GetScriptLanguage() != eScriptLanguagePython)
+ return {};
+
+ ScriptInterpreter::SanitizedScriptingModuleName sanitized_name =
+ target.GetDebugger()
+ .GetScriptInterpreter()
+ ->GetSanitizedScriptingModuleName(
+ module_spec.GetFileNameStrippingExtension().GetStringRef());
+
+ FileSpecList file_list;
+ FileSpecList paths = Debugger::GetSafeAutoLoadPaths();
+
+ // Iterate in reverse so we consider the latest appended path first.
+ for (FileSpec path : llvm::reverse(paths)) {
+ path.AppendPathComponent(sanitized_name.GetOriginalName());
+
+ if (!FileSystem::Instance().Exists(path))
+ continue;
+
+ FileSpec script_fspec = path;
+ script_fspec.AppendPathComponent(
+ llvm::formatv("{0}.py", sanitized_name.GetSanitizedName()).str());
+ FileSystem::Instance().Resolve(script_fspec);
+
+ FileSpec orig_script_fspec = path;
+ orig_script_fspec.AppendPathComponent(
+ llvm::formatv("{0}.py", sanitized_name.GetOriginalName()).str());
+ FileSystem::Instance().Resolve(orig_script_fspec);
+
+ WarnIfInvalidUnsanitizedScriptExists(feedback_stream, sanitized_name,
+ orig_script_fspec, script_fspec);
+
+ if (FileSystem::Instance().Exists(script_fspec))
+ file_list.Append(script_fspec);
+
+ // If we successfully found a directory in a safe auto-load path
+ // stop looking at any other paths.
+ break;
+ }
+
+ return file_list;
+}
+
+FileSpecList
+Platform::LocateExecutableScriptingResourcesImpl(Target *target, Module &module,
+ Stream &feedback_stream) {
+ return {};
+}
+
FileSpecList
Platform::LocateExecutableScriptingResources(Target *target, Module &module,
Stream &feedback_stream) {
- return FileSpecList();
+ if (!target)
+ return {};
+
+ // Give derived platforms a chance to locate scripting resources.
+ if (FileSpecList fspecs = LocateExecutableScriptingResourcesImpl(
+ target, module, feedback_stream);
+ !fspecs.IsEmpty())
+ return fspecs;
+
+ const FileSpec &module_spec = module.GetFileSpec();
+ if (!module_spec)
+ return {};
+
+ return LocateExecutableScriptingResourcesFromSafePaths(feedback_stream,
+ module_spec, *target);
}
Status Platform::GetSharedModule(
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index 2361314d506ac..a4e18f5b59eea 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -180,6 +180,10 @@ let Definition = "target", Path = "target" in {
DefaultEnumValue<"eLoadScriptFromSymFileWarn">,
EnumValues<"OptionEnumValues(g_load_script_from_sym_file_values)">,
Desc<"Allow LLDB to load scripting resources embedded in symbol files when available.">;
+ def SafeLoadPaths
+ : Property<"safe-load-paths", "String">,
+ DefaultStringValue<"">,
+ Desc<"Paths that LLDB can auto-load scripting resources from.">;
def LoadCWDlldbinitFile: Property<"load-cwd-lldbinit", "Enum">,
DefaultEnumValue<"eLoadCWDlldbinitWarn">,
EnumValues<"OptionEnumValues(g_load_cwd_lldbinit_values)">,
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/Darwin/dsym-takes-priority.test b/lldb/test/Shell/Platform/SafeAutoLoad/Darwin/dsym-takes-priority.test
new file mode 100644
index 0000000000000..60b875a643c88
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/Darwin/dsym-takes-priority.test
@@ -0,0 +1,31 @@
+# REQUIRES: python, asserts, system-darwin
+#
+# Test that when both a dSYM script and a safe-path script exist,
+# the dSYM script takes priority.
+
+# RUN: split-file %s %t
+# RUN: %clang_host -g %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/TestModule.out.dSYM/Contents/Resources/Python
+# RUN: mkdir -p %t/safe-path/TestModule
+
+# RUN: cp %t/dsym_script.py %t/TestModule.out.dSYM/Contents/Resources/Python/TestModule.py
+# RUN: cp %t/safe_script.py %t/safe-path/TestModule/TestModule.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s --implicit-check-not=SAFE_PATH_SCRIPT
+
+# CHECK: DSYM_SCRIPT
+
+#--- main.c
+int main() { return 0; }
+
+#--- dsym_script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("DSYM_SCRIPT", file=sys.stderr)
+
+#--- safe_script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SAFE_PATH_SCRIPT", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/Darwin/safe-path-fallback-no-dsym-script.test b/lldb/test/Shell/Platform/SafeAutoLoad/Darwin/safe-path-fallback-no-dsym-script.test
new file mode 100644
index 0000000000000..b00484ecdbe6d
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/Darwin/safe-path-fallback-no-dsym-script.test
@@ -0,0 +1,24 @@
+# REQUIRES: python, asserts, system-darwin
+#
+# Test that when a dSYM exists but has no Python script inside,
+# the safe-path script is loaded as a fallback.
+
+# RUN: split-file %s %t
+# RUN: %clang_host -g %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/TestModule
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/TestModule.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK: SAFE_PATH_FALLBACK
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SAFE_PATH_FALLBACK", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/basic-load.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/basic-load.test
new file mode 100644
index 0000000000000..0ee57c14130ae
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/basic-load.test
@@ -0,0 +1,24 @@
+# REQUIRES: python, asserts, !system-windows
+
+# Test that LLDB auto-loads <safe-path>/<module-name>/<module-name>.py on
+# module load.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/TestModule
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/TestModule.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK: AUTOLOAD_SUCCESS
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("AUTOLOAD_SUCCESS", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/last-path-wins.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/last-path-wins.test
new file mode 100644
index 0000000000000..160b771fda024
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/last-path-wins.test
@@ -0,0 +1,31 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that the last appended safe-auto-load path takes priority.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/first-path/TestModule
+# RUN: mkdir -p %t/second-path/TestModule
+
+# RUN: cp %t/first.py %t/first-path/TestModule/TestModule.py
+# RUN: cp %t/second.py %t/second-path/TestModule/TestModule.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/first-path' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/second-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s --implicit-check-not=FIRST_PATH
+
+# CHECK: SECOND_PATH
+
+#--- main.c
+int main() { return 0; }
+
+#--- first.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("FIRST_PATH", file=sys.stderr)
+
+#--- second.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SECOND_PATH", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-nested-dir.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-nested-dir.test
new file mode 100644
index 0000000000000..aeee2e8cd473d
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-nested-dir.test
@@ -0,0 +1,24 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that LLDB does not load scripts from nested subdirectories inside the
+# module directory.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/TestModule/nested
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/nested/TestModule.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK-NOT: SHOULD_NOT_LOAD
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SHOULD_NOT_LOAD", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-dirname.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-dirname.test
new file mode 100644
index 0000000000000..345613a378a19
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-dirname.test
@@ -0,0 +1,24 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that LLDB does not load scripts when the directory name doesn't match
+# the module name.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/WrongName
+
+# RUN: cp %t/script.py %t/safe-path/WrongName/TestModule.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK-NOT: SHOULD_NOT_LOAD
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SHOULD_NOT_LOAD", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-scriptname.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-scriptname.test
new file mode 100644
index 0000000000000..f7fb13878d9a8
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-match-wrong-scriptname.test
@@ -0,0 +1,24 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that LLDB does not load scripts when the script filename inside the
+# module directory doesn't match <module-name>.py.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/TestModule
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/other.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK-NOT: SHOULD_NOT_LOAD
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SHOULD_NOT_LOAD", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-paths-configured.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-paths-configured.test
new file mode 100644
index 0000000000000..14e713aee5caa
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/no-paths-configured.test
@@ -0,0 +1,22 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that nothing loads when no safe-auto-load-paths are configured.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/TestModule
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/TestModule.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK-NOT: SHOULD_NOT_LOAD
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SHOULD_NOT_LOAD", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/special-chars-sanitized.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/special-chars-sanitized.test
new file mode 100644
index 0000000000000..c2ad4a96c1bd4
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/special-chars-sanitized.test
@@ -0,0 +1,24 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that a module name with special characters (dashes, dots, spaces) is
+# sanitized and the sanitized <module-name>.py is loaded.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o "%t/Test- Module.1.out"
+# RUN: mkdir -p "%t/safe-path/Test- Module.1"
+
+# RUN: cp %t/script.py "%t/safe-path/Test- Module.1/Test__Module_1.py"
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create "%t/Test- Module.1.out"' 2>&1 | FileCheck %s
+
+# CHECK: SANITIZED_LOAD
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+def __lldb_init_module(debugger, internal_dict):
+ print("SANITIZED_LOAD", file=sys.stderr)
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/submodule-import.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/submodule-import.test
new file mode 100644
index 0000000000000..a6fa1856aa588
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/submodule-import.test
@@ -0,0 +1,30 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that an auto-loaded script can import a sibling Python submodule
+# from the same directory.
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/TestModule
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/TestModule.py
+# RUN: cp %t/helper.py %t/safe-path/TestModule/helper.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK: HELPER_LOADED
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+import helper
+def __lldb_init_module(debugger, internal_dict):
+ print(helper.get_marker(), file=sys.stderr)
+
+#--- helper.py
+def get_marker():
+ return "HELPER_LOADED"
diff --git a/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/subpackage-import.test b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/subpackage-import.test
new file mode 100644
index 0000000000000..754d6482b4d87
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/subpackage-import.test
@@ -0,0 +1,30 @@
+# REQUIRES: python, asserts, !system-windows
+#
+# Test that an auto-loaded script can import a sibling Python package
+# (a subdirectory with an __init__.py).
+
+# RUN: split-file %s %t
+# RUN: %clang_host %t/main.c -o %t/TestModule.out
+# RUN: mkdir -p %t/safe-path/TestModule/helpers
+
+# RUN: cp %t/script.py %t/safe-path/TestModule/TestModule.py
+# RUN: cp %t/init.py %t/safe-path/TestModule/helpers/__init__.py
+# RUN: %lldb -b \
+# RUN: -o 'settings set target.load-script-from-symbol-file true' \
+# RUN: -o 'settings append testing.safe-auto-load-paths %t/safe-path' \
+# RUN: -o 'target create %t/TestModule.out' 2>&1 | FileCheck %s
+
+# CHECK: PACKAGE_LOADED
+
+#--- main.c
+int main() { return 0; }
+
+#--- script.py
+import sys
+import helpers
+def __lldb_init_module(debugger, internal_dict):
+ print(helpers.get_marker(), file=sys.stderr)
+
+#--- init.py
+def get_marker():
+ return "PACKAGE_LOADED"
diff --git a/lldb/unittests/Platform/PlatformTest.cpp b/lldb/unittests/Platform/PlatformTest.cpp
index 6299197791fc4..be20baf5d1f33 100644
--- a/lldb/unittests/Platform/PlatformTest.cpp
+++ b/lldb/unittests/Platform/PlatformTest.cpp
@@ -8,8 +8,12 @@
#include "gtest/gtest.h"
+#include "TestUtils.h"
+
#include "Plugins/Platform/POSIX/PlatformPOSIX.h"
#include "TestingSupport/SubsystemRAII.h"
+#include "TestingSupport/TestUtilities.h"
+#include "lldb/Core/Debugger.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
@@ -163,3 +167,470 @@ TEST_F(PlatformTest, CreateUnknown) {
ASSERT_EQ(list.Create("unknown-platform-name"), nullptr);
ASSERT_EQ(list.GetOrCreate("dummy"), nullptr);
}
+
+struct PlatformLocateSafePathTest : public PlatformTest {
+protected:
+ void SetUp() override {
+ std::call_once(TestUtilities::g_debugger_initialize_flag,
+ []() { Debugger::Initialize(nullptr); });
+
+ ArchSpec arch("x86_64-apple-macosx-");
+ m_platform_sp = std::make_shared<PlatformArm>();
+ Platform::SetHostPlatform(m_platform_sp);
+
+ m_debugger_sp = Debugger::CreateInstance();
+
+ m_debugger_sp->GetTargetList().CreateTarget(*m_debugger_sp, "", arch,
+ lldb_private::eLoadDependentsNo,
+ m_platform_sp, m_target_sp);
+
+ ASSERT_TRUE(m_target_sp);
+ ASSERT_TRUE(m_platform_sp);
+
+ ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory(
+ "locate-scripts-from-safe-paths-test", m_tmp_root_dir))
+ << "Failed to create test directory.";
+ };
+
+ void TearDown() override {
+ llvm::sys::fs::remove_directories(m_tmp_root_dir);
+ TestingProperties::GetGlobalTestingProperties().SetSafeAutoLoadPaths({});
+ }
+
+ DebuggerSP m_debugger_sp;
+ PlatformSP m_platform_sp;
+ TargetSP m_target_sp;
+
+ /// Root directory for m_tmp_dsym_dwarf_dir and m_tmp_dsym_python_dir
+ llvm::SmallString<128> m_tmp_root_dir;
+
+ SubsystemRAII<MockScriptInterpreterPython> subsystems;
+};
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_NoSetting) {
+ // Tests LocateScriptingResourcesFromSafePaths finds no script if we don't set
+ // the safe path setting.
+
+ // Create dummy module file at <test-root>/TestModule.o
+ FileSpec module_fspec(CreateFile("TestModule.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ CreateFile("TestModule.py", module_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ ASSERT_EQ(file_specs.GetSize(), 0u);
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_NoMatch) {
+ // Tests LocateScriptingResourcesFromSafePaths finds no directory to load
+ // from.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/TestModule.o
+ FileSpec module_fspec(CreateFile("TestModule.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ // Directory name doesn't match the module name.
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule1");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ CreateFile("TestModule1.py", module_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ ASSERT_EQ(file_specs.GetSize(), 0u);
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_Match) {
+ // Tests LocateScriptingResourcesFromSafePaths locates the
+ // <module-name>/<module-name>.py script correctly.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/TestModule.o
+ FileSpec module_fspec(CreateFile("TestModule.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ CreateFile("TestModule.py", module_dir);
+ // Other files should be ignored.
+ CreateFile("helper.py", module_dir);
+ CreateFile("not_a_script.txt", module_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 1u);
+ EXPECT_EQ(file_specs.GetFileSpecAtIndex(0).GetFilename(), "TestModule.py");
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_NestedDir) {
+ // Tests that a matching Python file nested inside a subdirectory is not
+ // picked up. Only <module-name>/<module-name>.py at the top level matters.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/TestModule.o
+ FileSpec module_fspec(CreateFile("TestModule.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ // Create a nested directory that contains the matching Python file.
+ llvm::SmallString<128> nested_dir(module_dir);
+ llvm::sys::path::append(nested_dir, "nested");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(nested_dir));
+
+ CreateFile("TestModule.py", nested_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 0u);
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_MultiplePaths) {
+ // Tests LocateScriptingResourcesFromSafePaths locates the script from the
+ // last appended auto-load path.
+
+ // Create dummy module file at <test-root>/TestModule.o
+ FileSpec module_fspec(CreateFile("TestModule.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> path1(m_tmp_root_dir);
+ llvm::sys::path::append(path1, "AnotherSafePath");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(path1));
+
+ llvm::SmallString<128> path2(m_tmp_root_dir);
+ llvm::sys::path::append(path2, "AnotherAnotherSafePath");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(path2));
+
+ llvm::SmallString<128> path3(m_tmp_root_dir);
+ llvm::sys::path::append(path3, "EmptySafePath");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(path3));
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ llvm::SmallString<128> path1_module_dir(path1);
+ llvm::sys::path::append(path1_module_dir, "TestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(path1_module_dir));
+
+ llvm::SmallString<128> path2_module_dir(path2);
+ llvm::sys::path::append(path2_module_dir, "NotTheTestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(path2_module_dir));
+
+ llvm::SmallString<128> path3_module_dir(path3);
+ llvm::sys::path::append(path3_module_dir, "TestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(path3_module_dir));
+
+ // Place the correctly named script in each module directory.
+ CreateFile("TestModule.py", module_dir);
+ CreateFile("TestModule.py", path1_module_dir);
+ CreateFile("TestModule.py", path2_module_dir);
+ // Keep path3 (EmptySafePath) empty.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(path1));
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(path2));
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ // path1 was the last appended path with a matching directory.
+ EXPECT_EQ(file_specs.GetSize(), 1u);
+ EXPECT_TRUE(llvm::StringRef(file_specs.GetFileSpecAtIndex(0).GetPath())
+ .contains("AnotherSafePath"));
+ EXPECT_EQ(file_specs.GetFileSpecAtIndex(0).GetFilename(), "TestModule.py");
+
+ // Now add another safe path with a valid module directory but no
+ // TestModule.py inside. LLDB shouldn't fall back to other matching safe
+ // paths.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(path3));
+
+ file_specs = Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 0u);
+
+ // Now place the correctly named script in path3.
+ CreateFile("TestModule.py", path3_module_dir);
+
+ file_specs = Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 1u);
+ EXPECT_TRUE(llvm::StringRef(file_specs.GetFileSpecAtIndex(0).GetPath())
+ .contains("EmptySafePath"));
+ EXPECT_EQ(file_specs.GetFileSpecAtIndex(0).GetFilename(), "TestModule.py");
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_SpecialChars_NoMatch) {
+ // Module name has special characters. The directory exists but only contains
+ // a script with the original (unsanitized) name. No match.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/TestModule-1.1 1.o
+ FileSpec module_fspec(CreateFile("TestModule-1.1 1.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule-1.1 1");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ // Only the unsanitized name exists.
+ FileSpec orig_fspec(CreateFile("TestModule-1.1 1.py", module_dir));
+ ASSERT_TRUE(orig_fspec);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 0u);
+
+ std::string expected = llvm::formatv(
+ "debug script '{0}' cannot be loaded because"
+ " 'TestModule-1.1 1.py' contains reserved characters. If you intend to"
+ " have this script loaded, please rename it to 'TestModule_1_1_1.py' and "
+ "retry.\n",
+ orig_fspec.GetPath());
+ EXPECT_EQ(ss.GetString(), expected);
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_SpecialChars_Match_Warning) {
+ // Module name has special characters. Both the original and sanitized scripts
+ // exist. LLDB loads the sanitized one and warns.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/TestModule-1.1 1.o
+ FileSpec module_fspec(CreateFile("TestModule-1.1 1.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule-1.1 1");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ FileSpec orig_fspec(CreateFile("TestModule-1.1 1.py", module_dir));
+ ASSERT_TRUE(orig_fspec);
+
+ CreateFile("TestModule_1_1_1.py", module_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 1u);
+ EXPECT_EQ(file_specs.GetFileSpecAtIndex(0).GetFilename(),
+ "TestModule_1_1_1.py");
+
+ std::string expected = llvm::formatv(
+ "debug script '{0}' cannot be loaded because"
+ " 'TestModule-1.1 1.py' contains reserved characters. Ignoring"
+ " 'TestModule-1.1 1.py' and loading 'TestModule_1_1_1.py' instead.\n",
+ orig_fspec.GetPath());
+ EXPECT_EQ(ss.GetString(), expected);
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_SpecialChars_Match_NoWarning) {
+ // Module name has special characters. Only the sanitized script exists.
+ // No warning.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/TestModule-1.1 1.o
+ FileSpec module_fspec(CreateFile("TestModule-1.1 1.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "TestModule-1.1 1");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ CreateFile("TestModule_1_1_1.py", module_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 1u);
+ EXPECT_EQ(file_specs.GetFileSpecAtIndex(0).GetFilename(),
+ "TestModule_1_1_1.py");
+ EXPECT_TRUE(ss.GetString().empty());
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_Keyword_NoMatch) {
+ // Module name is a reserved keyword. Only the original script exists.
+ // Warns and returns nothing.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/import.o
+ FileSpec module_fspec(CreateFile("import.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "import");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ FileSpec orig_fspec(CreateFile("import.py", module_dir));
+ ASSERT_TRUE(orig_fspec);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 0u);
+
+ std::string expected = llvm::formatv(
+ "debug script '{0}' cannot be loaded because 'import.py' "
+ "conflicts with the keyword 'import'. If you intend to have this script "
+ "loaded, please rename it to '_import.py' and retry.\n",
+ orig_fspec.GetPath());
+ EXPECT_EQ(ss.GetString(), expected);
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_Keyword_Match) {
+ // Module name is a reserved keyword. Both original and sanitized scripts
+ // exist. Loads the sanitized one and warns.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/import.o
+ FileSpec module_fspec(CreateFile("import.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "import");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ FileSpec orig_fspec(CreateFile("import.py", module_dir));
+ ASSERT_TRUE(orig_fspec);
+
+ CreateFile("_import.py", module_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 1u);
+ EXPECT_EQ(file_specs.GetFileSpecAtIndex(0).GetFilename(), "_import.py");
+
+ std::string expected =
+ llvm::formatv("debug script '{0}' cannot be loaded because 'import.py' "
+ "conflicts with the keyword 'import'. Ignoring 'import.py' "
+ "and loading '_import.py' instead.\n",
+ orig_fspec.GetPath());
+ EXPECT_EQ(ss.GetString(), expected);
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_Keyword_Match_NoWarning) {
+ // Module name is a reserved keyword. Only the sanitized script exists.
+ // No warning.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/import.o
+ FileSpec module_fspec(CreateFile("import.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> module_dir(m_tmp_root_dir);
+ llvm::sys::path::append(module_dir, "import");
+ ASSERT_FALSE(llvm::sys::fs::create_directory(module_dir));
+
+ CreateFile("_import.py", module_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 1u);
+ EXPECT_EQ(file_specs.GetFileSpecAtIndex(0).GetFilename(), "_import.py");
+ EXPECT_TRUE(ss.GetString().empty());
+}
+
+TEST_F(PlatformLocateSafePathTest,
+ LocateScriptingResourcesFromSafePaths_InnerDirectoryHasModuleName) {
+ // Test a directory structure like
+ // <safe-path>/TestModule/TestModule/TestModule.py. LLDB should not load that
+ // inner script.
+
+ TestingProperties::GetGlobalTestingProperties().AppendSafeAutoLoadPaths(
+ FileSpec(m_tmp_root_dir));
+
+ // Create dummy module file at <test-root>/TestModule.o
+ FileSpec module_fspec(CreateFile("TestModule.o", m_tmp_root_dir));
+ ASSERT_TRUE(module_fspec);
+
+ llvm::SmallString<128> inner_dir(m_tmp_root_dir);
+ llvm::sys::path::append(inner_dir, "TestModule", "TestModule");
+ ASSERT_FALSE(llvm::sys::fs::create_directories(inner_dir));
+
+ CreateFile("TestModule.py", inner_dir);
+
+ StreamString ss;
+ FileSpecList file_specs =
+ Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ ss, module_fspec, *m_target_sp);
+
+ EXPECT_EQ(file_specs.GetSize(), 0u);
+ EXPECT_TRUE(ss.GetString().empty());
+}
More information about the lldb-commits
mailing list