[lldb] [llvm] [WIP][lldb] Add mechanism for auto-loading Python scripts from pre-configured paths (PR #187031)
Michael Buch via llvm-commits
llvm-commits at lists.llvm.org
Tue Mar 17 07:46:54 PDT 2026
https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/187031
>From 5cd3f6b5d0b784987dd3903cfddc85d4fc748c2a Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 13 Mar 2026 15:40:12 +0000
Subject: [PATCH 1/2] TEMPORARY: initial libc++ test
---
libc++.1/libcxx_1.py | 471 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 471 insertions(+)
create mode 100644 libc++.1/libcxx_1.py
diff --git a/libc++.1/libcxx_1.py b/libc++.1/libcxx_1.py
new file mode 100644
index 0000000000000..b2cf2c47ebbe1
--- /dev/null
+++ b/libc++.1/libcxx_1.py
@@ -0,0 +1,471 @@
+"""
+Python LLDB synthetic child provider for libc++ std::map
+
+Converted from LibCxxMap.cpp
+
+Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+See https://llvm.org/LICENSE.txt for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""
+
+import lldb
+
+
+# The flattened layout of the std::__tree_iterator::__ptr_ looks
+# as follows:
+#
+# The following shows the contiguous block of memory:
+#
+# +-----------------------------+ class __tree_end_node
+# __ptr_ | pointer __left_; |
+# +-----------------------------+ class __tree_node_base
+# | pointer __right_; |
+# | __parent_pointer __parent_; |
+# | bool __is_black_; |
+# +-----------------------------+ class __tree_node
+# | __node_value_type __value_; | <<< our key/value pair
+# +-----------------------------+
+#
+# where __ptr_ has type __iter_pointer.
+
+
+class MapEntry:
+ """Wrapper around an LLDB ValueObject representing a tree node entry."""
+
+ def __init__(self, entry_sp=None):
+ self.m_entry_sp = entry_sp
+
+ def left(self):
+ """Get the left child pointer (offset 0)."""
+ if not self.m_entry_sp:
+ return None
+ return self.m_entry_sp.CreateChildAtOffset("", 0, self.m_entry_sp.GetType())
+
+ def right(self):
+ """Get the right child pointer (offset = address size)."""
+ if not self.m_entry_sp:
+ return None
+ addr_size = self.m_entry_sp.GetProcess().GetAddressByteSize()
+ return self.m_entry_sp.CreateChildAtOffset(
+ "", addr_size, self.m_entry_sp.GetType()
+ )
+
+ def parent(self):
+ """Get the parent pointer (offset = 2 * address size)."""
+ if not self.m_entry_sp:
+ return None
+ addr_size = self.m_entry_sp.GetProcess().GetAddressByteSize()
+ return self.m_entry_sp.CreateChildAtOffset(
+ "", 2 * addr_size, self.m_entry_sp.GetType()
+ )
+
+ def value(self):
+ """Get the unsigned integer value of the entry (pointer address)."""
+ if not self.m_entry_sp:
+ return 0
+ return self.m_entry_sp.GetValueAsUnsigned(0)
+
+ def error(self):
+ """Check if the entry has an error."""
+ if not self.m_entry_sp:
+ return True
+ return self.m_entry_sp.GetError().Fail()
+
+ def null(self):
+ """Check if the entry is null (value == 0)."""
+ return self.value() == 0
+
+ def get_entry(self):
+ """Get the underlying ValueObject."""
+ return self.m_entry_sp
+
+ def set_entry(self, entry):
+ """Set the underlying ValueObject."""
+ self.m_entry_sp = entry
+
+ def __eq__(self, other):
+ if not isinstance(other, MapEntry):
+ return False
+ if self.m_entry_sp is None and other.m_entry_sp is None:
+ return True
+ if self.m_entry_sp is None or other.m_entry_sp is None:
+ return False
+ return self.m_entry_sp.GetLoadAddress() == other.m_entry_sp.GetLoadAddress()
+
+
+class MapIterator:
+ """Iterator for traversing the red-black tree backing std::map."""
+
+ def __init__(self, entry=None, depth=0):
+ self.m_entry = MapEntry(entry) if entry else MapEntry()
+ self.m_max_depth = depth
+ self.m_error = False
+
+ def value(self):
+ """Get the current entry."""
+ return self.m_entry.get_entry()
+
+ def advance(self, count):
+ """Advance the iterator by count steps and return the entry."""
+ if self.m_error:
+ return None
+
+ steps = 0
+ while count > 0:
+ self._next()
+ count -= 1
+ steps += 1
+ if self.m_error or self.m_entry.null() or (steps > self.m_max_depth):
+ return None
+
+ return self.m_entry.get_entry()
+
+ def _next(self):
+ """
+ Mimics libc++'s __tree_next algorithm, which libc++ uses
+ in its __tree_iterator::operator++.
+ """
+ if self.m_entry.null():
+ return
+
+ right = MapEntry(self.m_entry.right())
+ if not right.null():
+ self.m_entry = self._tree_min(right)
+ return
+
+ steps = 0
+ while not self._is_left_child(self.m_entry):
+ if self.m_entry.error():
+ self.m_error = True
+ return
+ self.m_entry.set_entry(self.m_entry.parent())
+ steps += 1
+ if steps > self.m_max_depth:
+ self.m_entry = MapEntry()
+ return
+
+ self.m_entry = MapEntry(self.m_entry.parent())
+
+ def _tree_min(self, x):
+ """Mimics libc++'s __tree_min algorithm."""
+ if x.null():
+ return MapEntry()
+
+ left = MapEntry(x.left())
+ steps = 0
+ while not left.null():
+ if left.error():
+ self.m_error = True
+ return MapEntry()
+ x = MapEntry(left.get_entry()) # Create new MapEntry to avoid aliasing
+ left = MapEntry(x.left())
+ steps += 1
+ if steps > self.m_max_depth:
+ return MapEntry()
+
+ return x
+
+ def _is_left_child(self, x):
+ """Check if x is a left child of its parent."""
+ if x.null():
+ return False
+ rhs = MapEntry(x.parent())
+ rhs.set_entry(rhs.left())
+ return x.value() == rhs.value()
+
+
+def _get_first_value_of_libcxx_compressed_pair(pair):
+ """
+ Get the first value from a libc++ compressed pair.
+ Handles both old and new layouts.
+ """
+ # Try __value_ first (newer layout)
+ value_sp = pair.GetChildMemberWithName("__value_")
+ if value_sp and value_sp.IsValid():
+ return value_sp
+
+ # Try __first_ (older layout)
+ first_sp = pair.GetChildMemberWithName("__first_")
+ if first_sp and first_sp.IsValid():
+ return first_sp
+
+ return None
+
+
+def _get_value_or_old_compressed_pair(tree, value_name, pair_name):
+ """
+ Try to get a value member directly, or fall back to compressed pair layout.
+ Returns (value_sp, is_compressed_pair).
+ """
+ # Try new layout first (direct member)
+ value_sp = tree.GetChildMemberWithName(value_name)
+ if value_sp and value_sp.IsValid():
+ return (value_sp, False)
+
+ # Fall back to old compressed pair layout
+ pair_sp = tree.GetChildMemberWithName(pair_name)
+ if pair_sp and pair_sp.IsValid():
+ return (pair_sp, True)
+
+ return (None, False)
+
+
+class LibcxxStdMapSyntheticProvider:
+ """Synthetic children provider for libc++ std::map."""
+
+ def __init__(self, valobj, internal_dict):
+ self.valobj = valobj
+ self.m_tree = None
+ self.m_root_node = None
+ self.m_node_ptr_type = None
+ self.m_count = None
+ self.m_iterators = {}
+
+ def num_children(self):
+ """Calculate the number of children (map size)."""
+ if self.m_count is not None:
+ return self.m_count
+
+ if self.m_tree is None:
+ return 0
+
+ # Try new layout (__size_) or old compressed pair layout (__pair3_)
+ size_sp, is_compressed_pair = _get_value_or_old_compressed_pair(
+ self.m_tree, "__size_", "__pair3_"
+ )
+
+ if not size_sp:
+ return 0
+
+ if is_compressed_pair:
+ return self._calculate_num_children_for_old_compressed_pair_layout(size_sp)
+
+ self.m_count = size_sp.GetValueAsUnsigned(0)
+ return self.m_count
+
+ def _calculate_num_children_for_old_compressed_pair_layout(self, pair):
+ """Handle old libc++ compressed pair layout."""
+ node_sp = _get_first_value_of_libcxx_compressed_pair(pair)
+
+ if not node_sp:
+ return 0
+
+ self.m_count = node_sp.GetValueAsUnsigned(0)
+ return self.m_count
+
+ def get_child_index(self, name):
+ """Get the index of a child with the given name (e.g., "[0]" -> 0)."""
+ try:
+ if name.startswith("[") and name.endswith("]"):
+ return int(name[1:-1])
+ except ValueError:
+ pass
+ return None
+
+ def get_child_at_index(self, index):
+ """Get the child at the given index."""
+ num_children = self.num_children()
+ if index >= num_children:
+ return None
+
+ if self.m_tree is None or self.m_root_node is None:
+ return None
+
+ key_val_sp = self._get_key_value_pair(index, num_children)
+ if not key_val_sp:
+ # This will stop all future searches until an update() happens
+ self.m_tree = None
+ return None
+
+ # Create a synthetic child with the appropriate name
+ name = "[%d]" % index
+ potential_child_sp = key_val_sp.Clone(name)
+
+ if potential_child_sp and potential_child_sp.IsValid():
+ num_child_children = potential_child_sp.GetNumChildren()
+
+ # Handle __cc_ or __cc wrapper (1 child case)
+ if num_child_children == 1:
+ child0_sp = potential_child_sp.GetChildAtIndex(0)
+ if child0_sp:
+ child_name = child0_sp.GetName()
+ if child_name in ("__cc_", "__cc"):
+ potential_child_sp = child0_sp.Clone(name)
+
+ # Handle __cc_ and __nc wrapper (2 children case)
+ elif num_child_children == 2:
+ child0_sp = potential_child_sp.GetChildAtIndex(0)
+ child1_sp = potential_child_sp.GetChildAtIndex(1)
+ if child0_sp and child1_sp:
+ child0_name = child0_sp.GetName()
+ child1_name = child1_sp.GetName()
+ if child0_name in ("__cc_", "__cc") and child1_name == "__nc":
+ potential_child_sp = child0_sp.Clone(name)
+
+ return potential_child_sp
+
+ def update(self):
+ """Update the cached state. Called when the underlying value changes."""
+ self.m_count = None
+ self.m_tree = None
+ self.m_root_node = None
+ self.m_iterators.clear()
+
+ self.m_tree = self.valobj.GetChildMemberWithName("__tree_")
+ if not self.m_tree or not self.m_tree.IsValid():
+ return False
+
+ self.m_root_node = self.m_tree.GetChildMemberWithName("__begin_node_")
+
+ # Get the __node_pointer type from the tree's type
+ tree_type = self.m_tree.GetType()
+ self.m_node_ptr_type = tree_type.FindDirectNestedType("__node_pointer")
+
+ return False
+
+ def has_children(self):
+ """Check if this object has children."""
+ return True
+
+ def _get_key_value_pair(self, idx, max_depth):
+ """
+ Returns the ValueObject for the __tree_node type that
+ holds the key/value pair of the node at index idx.
+ """
+ iterator = MapIterator(self.m_root_node, max_depth)
+
+ advance_by = idx
+ if idx > 0:
+ # If we have already created the iterator for the previous
+ # index, we can start from there and advance by 1.
+ if idx - 1 in self.m_iterators:
+ iterator = self.m_iterators[idx - 1]
+ advance_by = 1
+
+ iterated_sp = iterator.advance(advance_by)
+ if not iterated_sp:
+ # This tree is garbage - stop
+ return None
+
+ if not self.m_node_ptr_type or not self.m_node_ptr_type.IsValid():
+ return None
+
+ # iterated_sp is a __iter_pointer at this point.
+ # We can cast it to a __node_pointer (which is what libc++ does).
+ value_type_sp = iterated_sp.Cast(self.m_node_ptr_type)
+ if not value_type_sp or not value_type_sp.IsValid():
+ return None
+
+ # Finally, get the key/value pair.
+ value_type_sp = value_type_sp.GetChildMemberWithName("__value_")
+ if not value_type_sp or not value_type_sp.IsValid():
+ return None
+
+ self.m_iterators[idx] = iterator
+
+ return value_type_sp
+
+
+class LibCxxMapIteratorSyntheticProvider:
+ """Synthetic children provider for libc++ std::map::iterator."""
+
+ def __init__(self, valobj, internal_dict):
+ self.valobj = valobj
+ self.m_pair_sp = None
+
+ def num_children(self):
+ """Map iterators always have 2 children (first and second)."""
+ return 2
+
+ def get_child_index(self, name):
+ """Get the index of a child with the given name."""
+ if not self.m_pair_sp:
+ return None
+ return self.m_pair_sp.GetIndexOfChildWithName(name)
+
+ def get_child_at_index(self, index):
+ """Get the child at the given index."""
+ if not self.m_pair_sp:
+ return None
+ return self.m_pair_sp.GetChildAtIndex(index)
+
+ def update(self):
+ """Update the cached state."""
+ self.m_pair_sp = None
+
+ if not self.valobj.IsValid():
+ return False
+
+ target = self.valobj.GetTarget()
+ if not target or not target.IsValid():
+ return False
+
+ # valobj is a std::map::iterator
+ # ...which is a __map_iterator<__tree_iterator<..., __node_pointer, ...>>
+ #
+ # Then, __map_iterator::__i_ is a __tree_iterator
+ tree_iter_sp = self.valobj.GetChildMemberWithName("__i_")
+ if not tree_iter_sp or not tree_iter_sp.IsValid():
+ return False
+
+ # Type is __tree_iterator::__node_pointer
+ # (We could alternatively also get this from the template argument)
+ tree_iter_type = tree_iter_sp.GetType()
+ node_pointer_type = tree_iter_type.FindDirectNestedType("__node_pointer")
+ if not node_pointer_type or not node_pointer_type.IsValid():
+ return False
+
+ # __ptr_ is a __tree_iterator::__iter_pointer
+ iter_pointer_sp = tree_iter_sp.GetChildMemberWithName("__ptr_")
+ if not iter_pointer_sp or not iter_pointer_sp.IsValid():
+ return False
+
+ # Cast the __iter_pointer to a __node_pointer (which stores our key/value pair)
+ node_pointer_sp = iter_pointer_sp.Cast(node_pointer_type)
+ if not node_pointer_sp or not node_pointer_sp.IsValid():
+ return False
+
+ key_value_sp = node_pointer_sp.GetChildMemberWithName("__value_")
+ if not key_value_sp or not key_value_sp.IsValid():
+ return False
+
+ # Create the synthetic child, which is a pair where the key and value can be
+ # retrieved by querying the synthetic provider for
+ # get_child_index("first") and get_child_index("second")
+ # respectively.
+ #
+ # std::map stores the actual key/value pair in value_type::__cc_ (or
+ # previously __cc).
+ key_value_sp = key_value_sp.Clone("pair")
+ if key_value_sp.GetNumChildren() == 1:
+ child0_sp = key_value_sp.GetChildAtIndex(0)
+ if child0_sp:
+ child_name = child0_sp.GetName()
+ if child_name in ("__cc_", "__cc"):
+ key_value_sp = child0_sp.Clone("pair")
+
+ self.m_pair_sp = key_value_sp
+ return False
+
+ def has_children(self):
+ """Check if this object has children."""
+ return True
+
+
+def __lldb_init_module(debugger, internal_dict):
+ debugger.HandleCommand(
+ f'type synthetic add -x "^std::__[[:alnum:]]+::map<.+> >$" -l libcxx_1.LibcxxStdMapSyntheticProvider -w "cplusplus-py"'
+ )
+ debugger.HandleCommand(
+ f'type synthetic add -x "^std::__[[:alnum:]]+::set<.+> >$" -l libcxx_1.LibcxxStdMapSyntheticProvider -w "cplusplus-py"'
+ )
+ debugger.HandleCommand(
+ f'type synthetic add -x "^std::__[[:alnum:]]+::multiset<.+> >$" -l libcxx_1.LibcxxStdMapSyntheticProvider -w "cplusplus-py"'
+ )
+ debugger.HandleCommand(
+ f'type synthetic add -x "^std::__[[:alnum:]]+::multimap<.+> >$" -l libcxx_1.LibcxxStdMapSyntheticProvider -w "cplusplus-py"'
+ )
+ debugger.HandleCommand(
+ f'type synthetic add -x "^std::__[[:alnum:]]+::__map_(const_)?iterator<.+>$" -l libcxx_1.LibCxxMapIteratorSyntheticProvider -w "cplusplus-py"'
+ )
+ debugger.HandleCommand(f"type category enable cplusplus-py")
>From dc2a69275a69dbbc244bd10308387f62e07a715f 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
https://discourse.llvm.org/t/rfc-lldb-moving-libc-data-formatters-out-of-lldb/89591
---
lldb/include/lldb/Core/Debugger.h | 5 +
lldb/include/lldb/Target/Platform.h | 62 ++-
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 | 107 +---
.../Plugins/Platform/MacOSX/PlatformDarwin.h | 4 +-
lldb/source/Target/Platform.cpp | 121 ++++-
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 | 32 ++
.../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/CMakeLists.txt | 1 +
.../unittests/Platform/PlatformDarwinTest.cpp | 156 +-----
lldb/unittests/Platform/PlatformTest.cpp | 471 ++++++++++++++++++
lldb/unittests/Platform/TestUtils.cpp | 42 ++
lldb/unittests/Platform/TestUtils.h | 59 +++
27 files changed, 1139 insertions(+), 259 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
create mode 100644 lldb/unittests/Platform/TestUtils.cpp
create mode 100644 lldb/unittests/Platform/TestUtils.h
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 1ba7516f0102c..423a8e65e91d9 100644
--- a/lldb/include/lldb/Target/Platform.h
+++ b/lldb/include/lldb/Target/Platform.h
@@ -44,8 +44,53 @@ class ProcessInstanceInfoMatch;
typedef std::vector<ProcessInstanceInfo> ProcessInstanceInfoList;
class ModuleCache;
+class ScriptInterpreter;
enum MmapFlags { eMmapFlagsPrivate = 1, eMmapFlagsAnon = 2 };
+/// 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);
+
+ /// 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 the script name required sanitization, and a file with the original
+ /// (unsanitized) name exists, warn the user that it won't be loaded.
+ void WarnIfInvalidUnsanitizedScriptExists(Stream &os,
+ const FileSpec &original_fspec,
+ const FileSpec &fspec);
+
+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;
+};
+
class PlatformProperties : public Properties {
public:
PlatformProperties();
@@ -274,9 +319,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 79618ab91e27a..3f7a55bd3fed7 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 2e5b458ffe297..607b494b8cb8c 100644
--- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
+++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp
@@ -29,6 +29,7 @@
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Interpreter/OptionValueString.h"
#include "lldb/Interpreter/Options.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Symbol/SymbolFile.h"
@@ -80,98 +81,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 &&
- 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
@@ -292,6 +201,7 @@ FileSpecList PlatformDarwin::LocateExecutableScriptingResourcesFromDSYM(
Stream &feedback_stream, FileSpec module_spec, const Target &target,
const FileSpec &symfile_spec) {
FileSpecList file_list;
+
while (module_spec.GetFilename()) {
SanitizedScriptingModuleName sanitized_name(
module_spec.GetFilename().GetStringRef(),
@@ -336,14 +246,10 @@ FileSpecList PlatformDarwin::LocateExecutableScriptingResourcesFromDSYM(
return file_list;
}
-FileSpecList PlatformDarwin::LocateExecutableScriptingResources(
+FileSpecList PlatformDarwin::LocateExecutableScriptingResourcesImpl(
Target *target, Module &module, Stream &feedback_stream) {
- if (!target)
- return {};
-
- // For now only Python scripts supported for auto-loading.
- if (target->GetDebugger().GetScriptLanguage() != eScriptLanguagePython)
- return {};
+ assert(target);
+ assert(target->GetDebugger().GetScriptLanguage() == eScriptLanguagePython);
// NB some extensions might be meaningful and should not be stripped -
// "this.binary.file"
@@ -354,8 +260,7 @@ FileSpecList PlatformDarwin::LocateExecutableScriptingResources(
const FileSpec &module_spec = module.GetFileSpec();
- if (!module_spec)
- return {};
+ assert(module_spec);
SymbolFile *symfile = module.GetSymbolFile();
if (!symfile)
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 647f8389dc41b..f22436decbea9 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
@@ -72,6 +74,55 @@ enum {
} // namespace
+SanitizedScriptingModuleName::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 &&
+ script_interpreter->IsReservedWord(m_sanitized_name.c_str())) {
+ m_conflicting_keyword = m_sanitized_name;
+ m_sanitized_name.insert(m_sanitized_name.begin(), '_');
+ }
+}
+
+void SanitizedScriptingModuleName::WarnIfInvalidUnsanitizedScriptExists(
+ Stream &os, const FileSpec &original_fspec, const FileSpec &fspec) {
+ 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());
+}
+
llvm::StringRef PlatformProperties::GetSettingName() {
static constexpr llvm::StringLiteral g_setting_name("platform");
return g_setting_name;
@@ -155,10 +206,78 @@ Status Platform::GetFileWithUUID(const FileSpec &platform_file,
return Status();
}
+FileSpecList Platform::LocateExecutableScriptingResourcesFromSafePaths(
+ Stream &feedback_stream, FileSpec module_spec, const Target &target) {
+ FileSpecList paths = Debugger::GetSafeAutoLoadPaths();
+ FileSpecList file_list;
+
+ ScriptInterpreter *script_interpreter =
+ target.GetDebugger().GetScriptInterpreter();
+ if (!script_interpreter)
+ return file_list;
+
+ SanitizedScriptingModuleName sanitized_name(
+ module_spec.GetFileNameStrippingExtension().GetStringRef(),
+ target.GetDebugger().GetScriptInterpreter());
+
+ // 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);
+
+ sanitized_name.WarnIfInvalidUnsanitizedScriptExists(
+ feedback_stream, 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 {};
+
+ // For now only Python scripts supported for auto-loading.
+ if (target->GetDebugger().GetScriptLanguage() != eScriptLanguagePython)
+ return {};
+
+ const FileSpec &module_spec = module.GetFileSpec();
+ if (!module_spec)
+ return {};
+
+ if (FileSpecList fspecs = LocateExecutableScriptingResourcesImpl(
+ target, module, feedback_stream);
+ !fspecs.IsEmpty())
+ return fspecs;
+
+ 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..9caf25061b802
--- /dev/null
+++ b/lldb/test/Shell/Platform/SafeAutoLoad/UNIX/last-path-wins.test
@@ -0,0 +1,32 @@
+# 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
+
+# CHECK: SECOND_PATH
+# CHECK-NOT: FIRST_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/CMakeLists.txt b/lldb/unittests/Platform/CMakeLists.txt
index a96636fc3fd59..f8755432bf6d7 100644
--- a/lldb/unittests/Platform/CMakeLists.txt
+++ b/lldb/unittests/Platform/CMakeLists.txt
@@ -1,4 +1,5 @@
add_lldb_unittest(LLDBPlatformTests
+ TestUtils.cpp
PlatformAppleSimulatorTest.cpp
PlatformDarwinTest.cpp
PlatformMacOSXTest.cpp
diff --git a/lldb/unittests/Platform/PlatformDarwinTest.cpp b/lldb/unittests/Platform/PlatformDarwinTest.cpp
index cd7cc80847302..cc24ac239db07 100644
--- a/lldb/unittests/Platform/PlatformDarwinTest.cpp
+++ b/lldb/unittests/Platform/PlatformDarwinTest.cpp
@@ -8,6 +8,8 @@
#include "gtest/gtest.h"
+#include "TestUtils.h"
+
#include "Plugins/Platform/MacOSX/PlatformDarwin.h"
#include "Plugins/Platform/MacOSX/PlatformMacOSX.h"
#include "Plugins/Platform/MacOSX/PlatformRemoteMacOSX.h"
@@ -16,7 +18,7 @@
#include "lldb/Core/Debugger.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/HostInfo.h"
-#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Target/Platform.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
@@ -28,54 +30,6 @@
using namespace lldb;
using namespace lldb_private;
-namespace {
-class MockScriptInterpreterPython : public ScriptInterpreter {
-public:
- MockScriptInterpreterPython(Debugger &debugger)
- : ScriptInterpreter(debugger,
- lldb::ScriptLanguage::eScriptLanguagePython) {}
-
- ~MockScriptInterpreterPython() override = default;
-
- bool ExecuteOneLine(llvm::StringRef command, CommandReturnObject *,
- const ExecuteScriptOptions &) override {
- return false;
- }
-
- void ExecuteInterpreterLoop() override {}
-
- static void Initialize() {
- PluginManager::RegisterPlugin(GetPluginNameStatic(),
- GetPluginDescriptionStatic(),
- lldb::eScriptLanguagePython, CreateInstance);
- }
-
- static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); }
-
- bool IsReservedWord(const char *word) override {
- return llvm::is_contained({"import", "mykeyword_1_1_1"},
- llvm::StringRef(word));
- }
-
- static lldb::ScriptInterpreterSP CreateInstance(Debugger &debugger) {
- return std::make_shared<MockScriptInterpreterPython>(debugger);
- }
-
- static llvm::StringRef GetPluginNameStatic() {
- return "MockScriptInterpreterPython";
- }
-
- static llvm::StringRef GetPluginDescriptionStatic() {
- return "MockScriptInterpreterPython";
- }
-
- // PluginInterface protocol
- llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
-};
-
-LLDB_PLUGIN_DEFINE(MockScriptInterpreterPython)
-} // namespace
-
struct PlatformDarwinLocateTest : public testing::Test {
protected:
void SetUp() override {
@@ -144,18 +98,6 @@ struct PlatformDarwinLocateTest : public testing::Test {
subsystems;
};
-static std::string CreateFile(llvm::StringRef filename,
- llvm::SmallString<128> parent_dir) {
- llvm::SmallString<128> path(parent_dir);
- llvm::sys::path::append(path, filename);
- int fd;
- std::error_code ret = llvm::sys::fs::openFileForWrite(path, fd);
- assert(!ret && "Failed to create test file.");
- ::close(fd);
-
- return path.c_str();
-}
-
TEST(PlatformDarwinTest, TestParseVersionBuildDir) {
llvm::VersionTuple V;
llvm::StringRef D;
@@ -526,98 +468,6 @@ TEST_F(
EXPECT_TRUE(ss.GetString().empty());
}
-TEST_F(
- PlatformDarwinLocateTest,
- LocateExecutableScriptingResourcesFromDSYM_ModuleNameIsKeywordAfterReplacement) {
- // Test case where the module name contains "special characters" but after
- // LLDB replaces those the filename is still a keyword. We ensure this by a
- // special case in MockScriptInterpreterPython::IsReservedWord.
-
- // Create dummy module file at <test-root>/mykeyword-1.1 1.o
- FileSpec module_fspec(CreateFile("mykeyword-1.1 1.o", m_tmp_root_dir));
- ASSERT_TRUE(module_fspec);
-
- // Create dummy module file at
- // <test-root>/.dSYM/Contents/Resources/DWARF/mykeyword-1.1 1.o
- FileSpec dsym_module_fpec(
- CreateFile("mykeyword-1.1 1.o", m_tmp_dsym_dwarf_dir));
- ASSERT_TRUE(dsym_module_fpec);
-
- CreateFile("mykeyword-1.1 1.py", m_tmp_dsym_python_dir);
-
- StreamString ss;
- FileSpecList fspecs =
- std::static_pointer_cast<PlatformDarwin>(m_platform_sp)
- ->LocateExecutableScriptingResourcesFromDSYM(
- ss, module_fspec, *m_target_sp, dsym_module_fpec);
- EXPECT_EQ(fspecs.GetSize(), 0u);
- EXPECT_TRUE(
- ss.GetString().contains("conflicts with the keyword 'mykeyword_1_1_1'"));
-}
-
-TEST_F(
- PlatformDarwinLocateTest,
- LocateExecutableScriptingResourcesFromDSYM_ModuleNameIsKeywordAfterReplacement_Match_Warning) {
- // Like
- // LocateExecutableScriptingResourcesFromDSYM_ModuleNameIsKeywordAfterReplacement
- // but we place a script with all the replacement characters into the module
- // directory so LLDB loads it. That will still produce a warning.
-
- // Create dummy module file at <test-root>/mykeyword-1.1 1.o
- FileSpec module_fspec(CreateFile("mykeyword-1.1 1.o", m_tmp_root_dir));
- ASSERT_TRUE(module_fspec);
-
- // Create dummy module file at
- // <test-root>/.dSYM/Contents/Resources/DWARF/mykeyword-1.1 1.o
- FileSpec dsym_module_fpec(
- CreateFile("mykeyword-1.1 1.o", m_tmp_dsym_dwarf_dir));
- ASSERT_TRUE(dsym_module_fpec);
-
- CreateFile("mykeyword-1.1 1.py", m_tmp_dsym_python_dir);
- CreateFile("_mykeyword_1_1_1.py", m_tmp_dsym_python_dir);
-
- StreamString ss;
- FileSpecList fspecs =
- std::static_pointer_cast<PlatformDarwin>(m_platform_sp)
- ->LocateExecutableScriptingResourcesFromDSYM(
- ss, module_fspec, *m_target_sp, dsym_module_fpec);
- EXPECT_EQ(fspecs.GetSize(), 1u);
- EXPECT_EQ(fspecs.GetFileSpecAtIndex(0).GetFilename(), "_mykeyword_1_1_1.py");
- EXPECT_TRUE(ss.GetString().contains("Ignoring 'mykeyword-1.1 1.py' and "
- "loading '_mykeyword_1_1_1.py' instead"));
-}
-
-TEST_F(
- PlatformDarwinLocateTest,
- LocateExecutableScriptingResourcesFromDSYM_ModuleNameIsKeywordAfterReplacement_Match_NoWarning) {
- // Like
- // LocateExecutableScriptingResourcesFromDSYM_ModuleNameIsKeywordAfterReplacement_Match_Warning
- // but we place a script with all the replacement characters into the module
- // directory so LLDB loads it (but no script that matches the original module
- // name, and hence generates no warning).
-
- // Create dummy module file at <test-root>/mykeyword-1.1 1.o
- FileSpec module_fspec(CreateFile("mykeyword-1.1 1.o", m_tmp_root_dir));
- ASSERT_TRUE(module_fspec);
-
- // Create dummy module file at
- // <test-root>/.dSYM/Contents/Resources/DWARF/mykeyword-1.1 1.o
- FileSpec dsym_module_fpec(
- CreateFile("mykeyword-1.1 1.o", m_tmp_dsym_dwarf_dir));
- ASSERT_TRUE(dsym_module_fpec);
-
- CreateFile("_mykeyword_1_1_1.py", m_tmp_dsym_python_dir);
-
- StreamString ss;
- FileSpecList fspecs =
- std::static_pointer_cast<PlatformDarwin>(m_platform_sp)
- ->LocateExecutableScriptingResourcesFromDSYM(
- ss, module_fspec, *m_target_sp, dsym_module_fpec);
- EXPECT_EQ(fspecs.GetSize(), 1u);
- EXPECT_EQ(fspecs.GetFileSpecAtIndex(0).GetFilename(), "_mykeyword_1_1_1.py");
- EXPECT_TRUE(ss.Empty());
-}
-
struct SpecialCharTestCase {
char special_char;
char replacement;
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());
+}
diff --git a/lldb/unittests/Platform/TestUtils.cpp b/lldb/unittests/Platform/TestUtils.cpp
new file mode 100644
index 0000000000000..7330395c803f8
--- /dev/null
+++ b/lldb/unittests/Platform/TestUtils.cpp
@@ -0,0 +1,42 @@
+//===-- TestUtils.cpp -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TestUtils.h"
+
+#include "lldb/Core/PluginManager.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+MockScriptInterpreterPython::MockScriptInterpreterPython(Debugger &debugger)
+ : ScriptInterpreter(debugger, lldb::ScriptLanguage::eScriptLanguagePython) {
+}
+
+void MockScriptInterpreterPython::Initialize() {
+ PluginManager::RegisterPlugin(GetPluginNameStatic(),
+ GetPluginDescriptionStatic(),
+ lldb::eScriptLanguagePython, CreateInstance);
+}
+
+void MockScriptInterpreterPython::Terminate() {
+ PluginManager::UnregisterPlugin(CreateInstance);
+}
+
+LLDB_PLUGIN_DEFINE(MockScriptInterpreterPython)
+
+std::string lldb_private::CreateFile(llvm::StringRef filename,
+ llvm::SmallString<128> parent_dir) {
+ llvm::SmallString<128> path(parent_dir);
+ llvm::sys::path::append(path, filename);
+ int fd;
+ std::error_code ret = llvm::sys::fs::openFileForWrite(path, fd);
+ assert(!ret && "Failed to create test file.");
+ ::close(fd);
+
+ return path.c_str();
+}
diff --git a/lldb/unittests/Platform/TestUtils.h b/lldb/unittests/Platform/TestUtils.h
new file mode 100644
index 0000000000000..1cb7b0b20d621
--- /dev/null
+++ b/lldb/unittests/Platform/TestUtils.h
@@ -0,0 +1,59 @@
+//===-- TestUtils.h -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UNITTESTS_PLATFORM_TESTUTILS
+#define LLDB_UNITTESTS_PLATFORM_TESTUTILS
+
+#include "lldb/Interpreter/ScriptInterpreter.h"
+
+namespace lldb_private {
+class Debugger;
+
+class MockScriptInterpreterPython : public ScriptInterpreter {
+public:
+ MockScriptInterpreterPython(Debugger &debugger);
+ ~MockScriptInterpreterPython() override = default;
+
+ bool ExecuteOneLine(llvm::StringRef command, CommandReturnObject *,
+ const ExecuteScriptOptions &) override {
+ return false;
+ }
+
+ void ExecuteInterpreterLoop() override {}
+
+ static void Initialize();
+
+ static void Terminate();
+
+ bool IsReservedWord(const char *word) override {
+ return llvm::is_contained({"import", "mykeyword_1_1_1"},
+ llvm::StringRef(word));
+ }
+
+ static lldb::ScriptInterpreterSP CreateInstance(Debugger &debugger) {
+ return std::make_shared<MockScriptInterpreterPython>(debugger);
+ }
+
+ static llvm::StringRef GetPluginNameStatic() {
+ return "MockScriptInterpreterPython";
+ }
+
+ static llvm::StringRef GetPluginDescriptionStatic() {
+ return "MockScriptInterpreterPython";
+ }
+
+ // PluginInterface protocol
+ llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+};
+
+std::string CreateFile(llvm::StringRef filename,
+ llvm::SmallString<128> parent_dir);
+
+} // namespace lldb_private
+
+#endif // LLDB_UNITTESTS_PLATFORM_TESTUTILS_H_IN
More information about the llvm-commits
mailing list