[Lldb-commits] [lldb] 47721d4 - [lldb] Realpath symlinks for breakpoints (#102223)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Aug 15 11:26:29 PDT 2024
Author: royitaqi
Date: 2024-08-15T11:26:24-07:00
New Revision: 47721d46187f89c12a13d07b5857496301cf5d6e
URL: https://github.com/llvm/llvm-project/commit/47721d46187f89c12a13d07b5857496301cf5d6e
DIFF: https://github.com/llvm/llvm-project/commit/47721d46187f89c12a13d07b5857496301cf5d6e.diff
LOG: [lldb] Realpath symlinks for breakpoints (#102223)
Improve the chance of resolving file/line breakpoints by realpath'ing the support files before doing a second match attempt, with some conditions applied.
A working [hello-world example](https://github.com/royitaqi/lldb_demos/blob/main/realpath/README.md).
See [patch](https://github.com/llvm/llvm-project/pull/102223) for more info about problem/motivation, details of the feature, new settings, telemetries and tests.
Added:
lldb/include/lldb/Utility/RealpathPrefixes.h
lldb/source/Utility/RealpathPrefixes.cpp
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/Makefile
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/TestBreakpoint.py
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/main.c
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/bar.h
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/foo.h
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/qux.h
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/foo.h
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink2
lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/to-be-mapped/README.md
lldb/unittests/Utility/FileSpecListTest.cpp
lldb/unittests/Utility/MockSymlinkFileSystem.h
lldb/unittests/Utility/RealpathPrefixesTest.cpp
Modified:
lldb/include/lldb/Symbol/CompileUnit.h
lldb/include/lldb/Target/Statistics.h
lldb/include/lldb/Target/Target.h
lldb/include/lldb/Utility/FileSpecList.h
lldb/include/lldb/lldb-forward.h
lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
lldb/source/Symbol/CompileUnit.cpp
lldb/source/Target/Statistics.cpp
lldb/source/Target/Target.cpp
lldb/source/Target/TargetProperties.td
lldb/source/Utility/CMakeLists.txt
lldb/source/Utility/FileSpecList.cpp
lldb/unittests/Core/CMakeLists.txt
lldb/unittests/Utility/CMakeLists.txt
Removed:
lldb/unittests/Core/FileSpecListTest.cpp
################################################################################
diff --git a/lldb/include/lldb/Symbol/CompileUnit.h b/lldb/include/lldb/Symbol/CompileUnit.h
index c20a37e3283075..c5bb080d211849 100644
--- a/lldb/include/lldb/Symbol/CompileUnit.h
+++ b/lldb/include/lldb/Symbol/CompileUnit.h
@@ -19,11 +19,13 @@
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/UserID.h"
#include "lldb/lldb-enumerations.h"
+#include "lldb/lldb-forward.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
namespace lldb_private {
+
/// \class CompileUnit CompileUnit.h "lldb/Symbol/CompileUnit.h"
/// A class that describes a compilation unit.
///
@@ -389,10 +391,15 @@ class CompileUnit : public std::enable_shared_from_this<CompileUnit>,
/// A SymbolContext list class that will get any matching
/// entries appended to.
///
+ /// \param[in] realpath_prefixes
+ /// Paths that start with one of the prefixes in this list will be
+ /// realpath'ed to resolve any symlinks.
+ ///
/// \see enum SymbolContext::Scope
void ResolveSymbolContext(const SourceLocationSpec &src_location_spec,
lldb::SymbolContextItem resolve_scope,
- SymbolContextList &sc_list);
+ SymbolContextList &sc_list,
+ RealpathPrefixes *realpath_prefixes = nullptr);
/// Get whether compiler optimizations were enabled for this compile unit
///
diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h
index 35bd7f8a66e055..5193d099a5494d 100644
--- a/lldb/include/lldb/Target/Statistics.h
+++ b/lldb/include/lldb/Target/Statistics.h
@@ -10,6 +10,7 @@
#define LLDB_TARGET_STATISTICS_H
#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/RealpathPrefixes.h"
#include "lldb/Utility/Stream.h"
#include "lldb/lldb-forward.h"
#include "llvm/ADT/StringMap.h"
@@ -184,6 +185,8 @@ class TargetStats {
void SetFirstPrivateStopTime();
void SetFirstPublicStopTime();
void IncreaseSourceMapDeduceCount();
+ void IncreaseSourceRealpathAttemptCount(uint32_t count);
+ void IncreaseSourceRealpathCompatibleCount(uint32_t count);
StatsDuration &GetCreateTime() { return m_create_time; }
StatsSuccessFail &GetExpressionStats() { return m_expr_eval; }
@@ -198,6 +201,8 @@ class TargetStats {
StatsSuccessFail m_frame_var{"frameVariable"};
std::vector<intptr_t> m_module_identifiers;
uint32_t m_source_map_deduce_count = 0;
+ uint32_t m_source_realpath_attempt_count = 0;
+ uint32_t m_source_realpath_compatible_count = 0;
void CollectStats(Target &target);
};
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 119dff4d498199..7f4d607f5427df 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -34,6 +34,7 @@
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/LLDBAssert.h"
+#include "lldb/Utility/RealpathPrefixes.h"
#include "lldb/Utility/Timeout.h"
#include "lldb/lldb-public.h"
@@ -117,6 +118,8 @@ class TargetProperties : public Properties {
InlineStrategy GetInlineStrategy() const;
+ RealpathPrefixes GetSourceRealpathPrefixes() const;
+
llvm::StringRef GetArg0() const;
void SetArg0(llvm::StringRef arg);
diff --git a/lldb/include/lldb/Utility/FileSpecList.h b/lldb/include/lldb/Utility/FileSpecList.h
index 6eb3bb9971f13a..d091a9246e0827 100644
--- a/lldb/include/lldb/Utility/FileSpecList.h
+++ b/lldb/include/lldb/Utility/FileSpecList.h
@@ -64,10 +64,16 @@ class SupportFileList {
/// \param[in] file
/// The file specification to search for.
///
+ /// \param[in] realpath_prefixes
+ /// Paths that start with one of the prefixes in this list will be
+ /// realpath'ed to resolve any symlinks.
+ ///
/// \return
/// The index of the file that matches \a file if it is found,
/// else UINT32_MAX is returned.
- size_t FindCompatibleIndex(size_t idx, const FileSpec &file) const;
+ size_t
+ FindCompatibleIndex(size_t idx, const FileSpec &file,
+ RealpathPrefixes *realpath_prefixes = nullptr) const;
template <class... Args> void EmplaceBack(Args &&...args) {
m_files.push_back(
diff --git a/lldb/include/lldb/Utility/RealpathPrefixes.h b/lldb/include/lldb/Utility/RealpathPrefixes.h
new file mode 100644
index 00000000000000..daa2a479d6a246
--- /dev/null
+++ b/lldb/include/lldb/Utility/RealpathPrefixes.h
@@ -0,0 +1,77 @@
+//===-- RealpathPrefixes.h --------------------------------------*- C++ -*-===//
+//
+// 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_UTILITY_REALPATHPREFIXES_H
+#define LLDB_UTILITY_REALPATHPREFIXES_H
+
+#include "lldb/lldb-forward.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace lldb_private {
+
+class RealpathPrefixes {
+public:
+ /// \param[in] file_spec_list
+ /// Prefixes are obtained from FileSpecList, through FileSpec::GetPath(),
+ /// which ensures that the paths are normalized. For example:
+ /// "./foo/.." -> ""
+ /// "./foo/../bar" -> "bar"
+ ///
+ /// \param[in] fs
+ /// An optional filesystem to use for realpath'ing. If not set, the real
+ /// filesystem will be used.
+ explicit RealpathPrefixes(const FileSpecList &file_spec_list,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs =
+ llvm::vfs::getRealFileSystem());
+
+ std::optional<FileSpec> ResolveSymlinks(const FileSpec &file_spec);
+
+ // If/when Statistics.h/cpp is moved into Utility, we can remove these
+ // methods, hold a (weak) pointer to `TargetStats` and directly increment
+ // on that object.
+ void IncreaseSourceRealpathAttemptCount() {
+ ++m_source_realpath_attempt_count;
+ }
+ uint32_t GetSourceRealpathAttemptCount() const {
+ return m_source_realpath_attempt_count;
+ }
+ void IncreaseSourceRealpathCompatibleCount() {
+ ++m_source_realpath_compatible_count;
+ }
+ uint32_t GetSourceRealpathCompatibleCount() const {
+ return m_source_realpath_compatible_count;
+ }
+
+private:
+ // Paths that start with one of the prefixes in this list will be realpath'ed
+ // to resolve any symlinks.
+ //
+ // Wildcard prefixes:
+ // - "" (empty string) will match all paths.
+ // - "/" will match all absolute paths.
+ std::vector<std::string> m_prefixes;
+
+ // The filesystem to use for realpath'ing.
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> m_fs;
+
+ // The optional Target instance to gather statistics.
+ lldb::TargetWP m_target;
+
+ // Statistics that we temprarily hold here, to be gathered into TargetStats
+ uint32_t m_source_realpath_attempt_count = 0;
+ uint32_t m_source_realpath_compatible_count = 0;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_REALPATHPREFIXES_H
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 1024501e05bcac..337eff696fcf3f 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -175,6 +175,7 @@ class Queue;
class QueueImpl;
class QueueItem;
class REPL;
+class RealpathPrefixes;
class RecognizedStackFrame;
class RegisterCheckpoint;
class RegisterContext;
diff --git a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
index 16c4ee1b88d162..508754082cae8a 100644
--- a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
+++ b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
@@ -15,6 +15,7 @@
#include "lldb/Target/Target.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
+#include "lldb/Utility/RealpathPrefixes.h"
#include "lldb/Utility/StreamString.h"
#include <optional>
@@ -290,16 +291,25 @@ Searcher::CallbackReturn BreakpointResolverFileLine::SearchCallback(
const uint32_t line = m_location_spec.GetLine().value_or(0);
const std::optional<uint16_t> column = m_location_spec.GetColumn();
+ Target &target = GetBreakpoint()->GetTarget();
+ RealpathPrefixes realpath_prefixes = target.GetSourceRealpathPrefixes();
+
const size_t num_comp_units = context.module_sp->GetNumCompileUnits();
for (size_t i = 0; i < num_comp_units; i++) {
CompUnitSP cu_sp(context.module_sp->GetCompileUnitAtIndex(i));
if (cu_sp) {
if (filter.CompUnitPasses(*cu_sp))
cu_sp->ResolveSymbolContext(m_location_spec, eSymbolContextEverything,
- sc_list);
+ sc_list, &realpath_prefixes);
}
}
+ // Gather stats into the Target
+ target.GetStatistics().IncreaseSourceRealpathAttemptCount(
+ realpath_prefixes.GetSourceRealpathAttemptCount());
+ target.GetStatistics().IncreaseSourceRealpathCompatibleCount(
+ realpath_prefixes.GetSourceRealpathCompatibleCount());
+
FilterContexts(sc_list);
DeduceSourceMapping(sc_list);
diff --git a/lldb/source/Symbol/CompileUnit.cpp b/lldb/source/Symbol/CompileUnit.cpp
index ddeacf18e855ee..db8f8ce6bcbc92 100644
--- a/lldb/source/Symbol/CompileUnit.cpp
+++ b/lldb/source/Symbol/CompileUnit.cpp
@@ -213,11 +213,12 @@ VariableListSP CompileUnit::GetVariableList(bool can_create) {
return m_variables;
}
-std::vector<uint32_t> FindFileIndexes(const SupportFileList &files,
- const FileSpec &file) {
+std::vector<uint32_t>
+FindFileIndexes(const SupportFileList &files, const FileSpec &file,
+ RealpathPrefixes *realpath_prefixes = nullptr) {
std::vector<uint32_t> result;
uint32_t idx = -1;
- while ((idx = files.FindCompatibleIndex(idx + 1, file)) !=
+ while ((idx = files.FindCompatibleIndex(idx + 1, file, realpath_prefixes)) !=
UINT32_MAX)
result.push_back(idx);
return result;
@@ -247,7 +248,8 @@ uint32_t CompileUnit::FindLineEntry(uint32_t start_idx, uint32_t line,
void CompileUnit::ResolveSymbolContext(
const SourceLocationSpec &src_location_spec,
- SymbolContextItem resolve_scope, SymbolContextList &sc_list) {
+ SymbolContextItem resolve_scope, SymbolContextList &sc_list,
+ RealpathPrefixes *realpath_prefixes) {
const FileSpec file_spec = src_location_spec.GetFileSpec();
const uint32_t line = src_location_spec.GetLine().value_or(0);
const bool check_inlines = src_location_spec.GetCheckInlines();
@@ -275,8 +277,8 @@ void CompileUnit::ResolveSymbolContext(
return;
}
- std::vector<uint32_t> file_indexes = FindFileIndexes(GetSupportFiles(),
- file_spec);
+ std::vector<uint32_t> file_indexes =
+ FindFileIndexes(GetSupportFiles(), file_spec, realpath_prefixes);
const size_t num_file_indexes = file_indexes.size();
if (num_file_indexes == 0)
return;
diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp
index 583d1524881fc3..390e04cebf6be6 100644
--- a/lldb/source/Target/Statistics.cpp
+++ b/lldb/source/Target/Statistics.cpp
@@ -192,6 +192,10 @@ TargetStats::ToJSON(Target &target,
}
target_metrics_json.try_emplace("sourceMapDeduceCount",
m_source_map_deduce_count);
+ target_metrics_json.try_emplace("sourceRealpathAttemptCount",
+ m_source_realpath_attempt_count);
+ target_metrics_json.try_emplace("sourceRealpathCompatibleCount",
+ m_source_realpath_compatible_count);
return target_metrics_json;
}
@@ -220,6 +224,14 @@ void TargetStats::IncreaseSourceMapDeduceCount() {
++m_source_map_deduce_count;
}
+void TargetStats::IncreaseSourceRealpathAttemptCount(uint32_t count) {
+ m_source_realpath_attempt_count += count;
+}
+
+void TargetStats::IncreaseSourceRealpathCompatibleCount(uint32_t count) {
+ m_source_realpath_compatible_count += count;
+}
+
bool DebuggerStats::g_collecting_stats = false;
llvm::json::Value DebuggerStats::ReportStatistics(
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 129683c43f0c1a..5a5d689e03fbc0 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -60,6 +60,7 @@
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
+#include "lldb/Utility/RealpathPrefixes.h"
#include "lldb/Utility/State.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/Timer.h"
@@ -4354,6 +4355,13 @@ InlineStrategy TargetProperties::GetInlineStrategy() const {
static_cast<InlineStrategy>(g_target_properties[idx].default_uint_value));
}
+// Returning RealpathPrefixes, but the setting's type is FileSpecList. We do
+// this because we want the FileSpecList to normalize the file paths for us.
+RealpathPrefixes TargetProperties::GetSourceRealpathPrefixes() const {
+ const uint32_t idx = ePropertySourceRealpathPrefixes;
+ return RealpathPrefixes(GetPropertyAtIndexAs<FileSpecList>(idx, {}));
+}
+
llvm::StringRef TargetProperties::GetArg0() const {
const uint32_t idx = ePropertyArg0;
return GetPropertyAtIndexAs<llvm::StringRef>(
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index ef538678670fea..421252aa4aea26 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -150,6 +150,9 @@ let Definition = "target" in {
DefaultEnumValue<"eInlineBreakpointsAlways">,
EnumValues<"OptionEnumValues(g_inline_breakpoint_enums)">,
Desc<"The strategy to use when settings breakpoints by file and line. Breakpoint locations can end up being inlined by the compiler, so that a compile unit 'a.c' might contain an inlined function from another source file. Usually this is limited to breakpoint locations from inlined functions from header or other include files, or more accurately non-implementation source files. Sometimes code might #include implementation files and cause inlined breakpoint locations in inlined implementation files. Always checking for inlined breakpoint locations can be expensive (memory and time), so if you have a project with many headers and find that setting breakpoints is slow, then you can change this setting to headers. This setting allows you to control exactly which strategy is used when setting file and line breakpoints.">;
+ def SourceRealpathPrefixes: Property<"source-realpath-prefixes", "FileSpecList">,
+ DefaultStringValue<"">,
+ Desc<"Realpath any source paths that start with one of these prefixes. If the debug info contains symlinks which match the original source file's basename but don't match its location that the user will use to set breakpoints, then this setting can help resolve breakpoints correctly. This handles both symlinked files and directories. Wild card prefixes: An empty string matches all paths. A forward slash matches absolute paths.">;
def DisassemblyFlavor: Property<"x86-disassembly-flavor", "Enum">,
DefaultEnumValue<"eX86DisFlavorDefault">,
EnumValues<"OptionEnumValues(g_x86_dis_flavor_value_types)">,
diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt
index e9954d66cd1a52..397db0e8976023 100644
--- a/lldb/source/Utility/CMakeLists.txt
+++ b/lldb/source/Utility/CMakeLists.txt
@@ -51,6 +51,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
Log.cpp
NameMatches.cpp
ProcessInfo.cpp
+ RealpathPrefixes.cpp
RegisterValue.cpp
RegularExpression.cpp
Instrumentation.cpp
diff --git a/lldb/source/Utility/FileSpecList.cpp b/lldb/source/Utility/FileSpecList.cpp
index 7647e04a820451..5852367f77827f 100644
--- a/lldb/source/Utility/FileSpecList.cpp
+++ b/lldb/source/Utility/FileSpecList.cpp
@@ -7,7 +7,12 @@
//===----------------------------------------------------------------------===//
#include "lldb/Utility/FileSpecList.h"
+#include "lldb/Target/Statistics.h"
+#include "lldb/Target/Target.h"
#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/RealpathPrefixes.h"
#include "lldb/Utility/Stream.h"
#include <cstdint>
@@ -108,52 +113,85 @@ size_t SupportFileList::FindFileIndex(size_t start_idx,
});
}
-size_t SupportFileList::FindCompatibleIndex(size_t start_idx,
- const FileSpec &file_spec) const {
- const size_t num_files = m_files.size();
- if (start_idx >= num_files)
- return UINT32_MAX;
+enum IsCompatibleResult {
+ kNoMatch = 0,
+ kOnlyFileMatch = 1,
+ kBothDirectoryAndFileMatch = 2,
+};
+IsCompatibleResult IsCompatible(const FileSpec &curr_file,
+ const FileSpec &file_spec) {
const bool file_spec_relative = file_spec.IsRelative();
const bool file_spec_case_sensitive = file_spec.IsCaseSensitive();
// When looking for files, we will compare only the filename if the directory
// argument is empty in file_spec
const bool full = !file_spec.GetDirectory().IsEmpty();
+ // Always start by matching the filename first
+ if (!curr_file.FileEquals(file_spec))
+ return IsCompatibleResult::kNoMatch;
+
+ // Only compare the full name if the we were asked to and if the current
+ // file entry has a directory. If it doesn't have a directory then we only
+ // compare the filename.
+ if (FileSpec::Equal(curr_file, file_spec, full)) {
+ return IsCompatibleResult::kBothDirectoryAndFileMatch;
+ } else if (curr_file.IsRelative() || file_spec_relative) {
+ llvm::StringRef curr_file_dir = curr_file.GetDirectory().GetStringRef();
+ if (curr_file_dir.empty())
+ // Basename match only for this file in the list
+ return IsCompatibleResult::kBothDirectoryAndFileMatch;
+
+ // Check if we have a relative path in our file list, or if "file_spec" is
+ // relative, if so, check if either ends with the other.
+ llvm::StringRef file_spec_dir = file_spec.GetDirectory().GetStringRef();
+ // We have a relative path in our file list, it matches if the
+ // specified path ends with this path, but we must ensure the full
+ // component matches (we don't want "foo/bar.cpp" to match "oo/bar.cpp").
+ auto is_suffix = [](llvm::StringRef a, llvm::StringRef b,
+ bool case_sensitive) -> bool {
+ if (case_sensitive ? a.consume_back(b) : a.consume_back_insensitive(b))
+ return a.empty() || a.ends_with("/");
+ return false;
+ };
+ const bool case_sensitive =
+ file_spec_case_sensitive || curr_file.IsCaseSensitive();
+ if (is_suffix(curr_file_dir, file_spec_dir, case_sensitive) ||
+ is_suffix(file_spec_dir, curr_file_dir, case_sensitive))
+ return IsCompatibleResult::kBothDirectoryAndFileMatch;
+ }
+ return IsCompatibleResult::kOnlyFileMatch;
+}
+
+size_t SupportFileList::FindCompatibleIndex(
+ size_t start_idx, const FileSpec &file_spec,
+ RealpathPrefixes *realpath_prefixes) const {
+ const size_t num_files = m_files.size();
+ if (start_idx >= num_files)
+ return UINT32_MAX;
+
for (size_t idx = start_idx; idx < num_files; ++idx) {
const FileSpec &curr_file = m_files[idx]->GetSpecOnly();
- // Always start by matching the filename first
- if (!curr_file.FileEquals(file_spec))
- continue;
-
- // Only compare the full name if the we were asked to and if the current
- // file entry has the a directory. If it doesn't have a directory then we
- // only compare the filename.
- if (FileSpec::Equal(curr_file, file_spec, full)) {
+ IsCompatibleResult result = IsCompatible(curr_file, file_spec);
+ if (result == IsCompatibleResult::kBothDirectoryAndFileMatch)
return idx;
- } else if (curr_file.IsRelative() || file_spec_relative) {
- llvm::StringRef curr_file_dir = curr_file.GetDirectory().GetStringRef();
- if (curr_file_dir.empty())
- return idx; // Basename match only for this file in the list
-
- // Check if we have a relative path in our file list, or if "file_spec" is
- // relative, if so, check if either ends with the other.
- llvm::StringRef file_spec_dir = file_spec.GetDirectory().GetStringRef();
- // We have a relative path in our file list, it matches if the
- // specified path ends with this path, but we must ensure the full
- // component matches (we don't want "foo/bar.cpp" to match "oo/bar.cpp").
- auto is_suffix = [](llvm::StringRef a, llvm::StringRef b,
- bool case_sensitive) -> bool {
- if (case_sensitive ? a.consume_back(b) : a.consume_back_insensitive(b))
- return a.empty() || a.ends_with("/");
- return false;
- };
- const bool case_sensitive =
- file_spec_case_sensitive || curr_file.IsCaseSensitive();
- if (is_suffix(curr_file_dir, file_spec_dir, case_sensitive) ||
- is_suffix(file_spec_dir, curr_file_dir, case_sensitive))
- return idx;
+
+ if (realpath_prefixes && result == IsCompatibleResult::kOnlyFileMatch) {
+ if (std::optional<FileSpec> resolved_curr_file =
+ realpath_prefixes->ResolveSymlinks(curr_file)) {
+ if (IsCompatible(*resolved_curr_file, file_spec) ==
+ IsCompatibleResult::kBothDirectoryAndFileMatch) {
+ // Stats and logging.
+ realpath_prefixes->IncreaseSourceRealpathCompatibleCount();
+ Log *log = GetLog(LLDBLog::Source);
+ LLDB_LOGF(log,
+ "Realpath'ed support file %s is compatible to input file",
+ resolved_curr_file->GetPath().c_str());
+ // We found a match
+ return idx;
+ }
+ }
}
}
diff --git a/lldb/source/Utility/RealpathPrefixes.cpp b/lldb/source/Utility/RealpathPrefixes.cpp
new file mode 100644
index 00000000000000..14c81ee6a1f571
--- /dev/null
+++ b/lldb/source/Utility/RealpathPrefixes.cpp
@@ -0,0 +1,70 @@
+//===-- RealpathPrefixes.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 "lldb/Utility/RealpathPrefixes.h"
+
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/FileSpecList.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+
+using namespace lldb_private;
+
+RealpathPrefixes::RealpathPrefixes(
+ const FileSpecList &file_spec_list,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
+ : m_fs(fs) {
+ m_prefixes.reserve(file_spec_list.GetSize());
+ for (const FileSpec &file_spec : file_spec_list) {
+ m_prefixes.emplace_back(file_spec.GetPath());
+ }
+}
+
+std::optional<FileSpec>
+RealpathPrefixes::ResolveSymlinks(const FileSpec &file_spec) {
+ if (m_prefixes.empty())
+ return std::nullopt;
+
+ // Test if `b` is a *path* prefix of `a` (not just *string* prefix).
+ // E.g. "/foo/bar" is a path prefix of "/foo/bar/baz" but not "/foo/barbaz".
+ auto is_path_prefix = [](llvm::StringRef a, llvm::StringRef b,
+ bool case_sensitive,
+ llvm::sys::path::Style style) -> bool {
+ if (case_sensitive ? a.consume_front(b) : a.consume_front_insensitive(b))
+ // If `b` isn't "/", then it won't end with "/" because it comes from
+ // `FileSpec`. After `a` consumes `b`, `a` should either be empty (i.e.
+ // `a` == `b`) or end with "/" (the remainder of `a` is a subdirectory).
+ return b == "/" || a.empty() ||
+ llvm::sys::path::is_separator(a[0], style);
+ return false;
+ };
+ std::string file_spec_path = file_spec.GetPath();
+ for (const std::string &prefix : m_prefixes) {
+ if (is_path_prefix(file_spec_path, prefix, file_spec.IsCaseSensitive(),
+ file_spec.GetPathStyle())) {
+ // Stats and logging.
+ IncreaseSourceRealpathAttemptCount();
+ Log *log = GetLog(LLDBLog::Source);
+ LLDB_LOGF(log, "Realpath'ing support file %s", file_spec_path.c_str());
+
+ // One prefix matched. Try to realpath.
+ llvm::SmallString<PATH_MAX> buff;
+ std::error_code ec = m_fs->getRealPath(file_spec_path, buff);
+ if (ec)
+ return std::nullopt;
+ FileSpec realpath(buff, file_spec.GetPathStyle());
+
+ // Only return realpath if it is
diff erent from the original file_spec.
+ if (realpath != file_spec)
+ return realpath;
+ return std::nullopt;
+ }
+ }
+ // No prefix matched
+ return std::nullopt;
+}
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/Makefile b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/Makefile
new file mode 100644
index 00000000000000..10495940055b63
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/TestBreakpoint.py b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/TestBreakpoint.py
new file mode 100644
index 00000000000000..5dc2af73f3647d
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/TestBreakpoint.py
@@ -0,0 +1,158 @@
+"""
+Test lldb breakpoint with symlinks/realpath and source-map.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil, lldbplatformutil
+
+
+class BreakpointTestCase(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def setUp(self):
+ # Call super's setUp().
+ TestBase.setUp(self)
+ # Find the line number to break inside main().
+ self.line_in_main = line_number("main.c", "// Set break point at this line.")
+ self.line_in_foo = line_number("real/foo.h", "// Set break point at this line.")
+ self.line_in_bar = line_number("real/bar.h", "// Set break point at this line.")
+ self.line_in_qux = line_number("real/qux.h", "// Set break point at this line.")
+ # disable "There is a running process, kill it and restart?" prompt
+ self.runCmd("settings set auto-confirm true")
+ self.addTearDownHook(lambda: self.runCmd("settings clear auto-confirm"))
+
+ def buildAndCreateTarget(self):
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+
+ # Create a target by the debugger.
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+
+ @skipIf(oslist=["windows"])
+ def test_file_line_breakpoint_realpath_and_source_map(self):
+ """Test file/line breakpoint with realpathing and source-mapping."""
+ self.buildAndCreateTarget()
+ cwd = os.getcwd()
+
+ ######################################################################
+ # Baseline
+ # --------------------------------------------------------------------
+ # Breakpoints should be resolved with paths which are in the line-table.
+ lldbutil.run_break_set_by_file_and_line(
+ self, "main.c", self.line_in_main, num_expected_locations=1, loc_exact=True
+ )
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "symlink1/foo.h",
+ self.line_in_foo,
+ num_expected_locations=1,
+ loc_exact=True,
+ )
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "symlink2/bar.h",
+ self.line_in_bar,
+ num_expected_locations=1,
+ loc_exact=True,
+ )
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "symlink2/qux.h",
+ self.line_in_qux,
+ num_expected_locations=1,
+ loc_exact=True,
+ )
+
+ ######################################################################
+ # Symlinked file
+ # --------------------------------------------------------------------
+ # - `symlink1/foo.h` is a symlink file, pointing at `real/foo.h`
+ # - main.c includes `symlink1/foo.h`.
+ # - As a result, the line-table contains a support file `(test_base_dir)/symlink1/foo.h`
+ # - Setting a breakpoint for `real/foo.h` won't be resolved, because it doesn't match the above path.
+ # - Setting a realpath prefix to the current working directory will cause the above support file to be realpath'ed to `(test_base_dir)/real/foo.h`
+ # - Now setting a breakpoint for `real/foo.h` will be resolved.
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "real/foo.h",
+ self.line_in_foo,
+ num_expected_locations=0,
+ loc_exact=True,
+ )
+ self.runCmd(f'settings set target.source-realpath-prefixes "{cwd}"')
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "real/foo.h",
+ self.line_in_foo,
+ num_expected_locations=1,
+ loc_exact=True,
+ )
+ # Clear settings so that the test below won't be affected.
+ self.runCmd("settings clear target.source-realpath-prefixes")
+
+ ######################################################################
+ # Symlinked directory
+ # --------------------------------------------------------------------
+ # - `symlink2` is a symlink directory, pointing at `real`.
+ # - So, `symlink2/bar.h` will be realpath'ed to `real/bar.h`.
+ # - main.c includes `symlink2/bar.h`.
+ # - As a result, the line-table contains a support file `(test_base_dir)/symlink2/bar.h`
+ # - Setting a breakpoint for `real/bar.h` won't be resolved, because it doesn't match the above path.
+ # - Setting a realpath prefix to the current working directory will cause the above support file to be realpath'ed to `(test_base_dir)/real/bar.h`
+ # - Now setting a breakpoint for `real/bar.h` will be resolved.
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "real/bar.h",
+ self.line_in_foo,
+ num_expected_locations=0,
+ loc_exact=True,
+ )
+ self.runCmd(f'settings set target.source-realpath-prefixes "{cwd}"')
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "real/bar.h",
+ self.line_in_foo,
+ num_expected_locations=1,
+ loc_exact=True,
+ )
+ # Clear settings so that the test below won't be affected.
+ self.runCmd("settings clear target.source-realpath-prefixes")
+
+ ######################################################################
+ # Symlink + source-map
+ # --------------------------------------------------------------------
+ # - `symlink2` is a symlink directory, pointing at `real`.
+ # - So, `symlink2/qux.h` will be realpath'ed to `real/qux.h`.
+ # - main.c includes `symlink2/qux.h`.
+ # - As a result, the line-table contains a support file `(test_base_dir)/symlink2/qux.h`
+ # - Setting a realpath prefix to the current working directory will cause the above support file to be realpath'ed to `(test_base_dir)/real/qux.h`
+ # - Setting a breakpoint for `to-be-mapped/qux.h` won't be resolved, because it doesn't match the above path.
+ # - After setting a source-map, setting the same breakpoint will be resolved, because the input path `to-be-mapped/qux.h` is reverse-mapped to `real/qux.h`, which matches the realpath'ed support file.
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "real/qux.h",
+ self.line_in_foo,
+ num_expected_locations=0,
+ loc_exact=True,
+ )
+ self.runCmd(f'settings set target.source-realpath-prefixes "{cwd}"')
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "to-be-mapped/qux.h",
+ self.line_in_foo,
+ num_expected_locations=0,
+ loc_exact=True,
+ )
+ self.runCmd('settings set target.source-map "real" "to-be-mapped"')
+ lldbutil.run_break_set_by_file_and_line(
+ self,
+ "to-be-mapped/qux.h",
+ self.line_in_foo,
+ num_expected_locations=1,
+ loc_exact=True,
+ )
+ # Clear settings so that the test below won't be affected.
+ self.runCmd("settings clear target.source-realpath-prefixes")
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/main.c b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/main.c
new file mode 100644
index 00000000000000..716e7a91715f9b
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/main.c
@@ -0,0 +1,10 @@
+#include "symlink1/foo.h"
+#include "symlink2/bar.h"
+#include "symlink2/qux.h"
+
+int main(int argc, char const *argv[]) {
+ int a = foo(); // 1
+ int b = bar(); // 2
+ int c = qux(); // 3
+ return a + b - c; // Set break point at this line.
+}
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/bar.h b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/bar.h
new file mode 100644
index 00000000000000..40155eb2075ab6
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/bar.h
@@ -0,0 +1,3 @@
+int bar() {
+ return 2; // Set break point at this line.
+}
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/foo.h b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/foo.h
new file mode 100644
index 00000000000000..a4ceefecf01238
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/foo.h
@@ -0,0 +1,3 @@
+int foo() {
+ return 1; // Set break point at this line.
+}
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/qux.h b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/qux.h
new file mode 100644
index 00000000000000..a90e0feba30aa5
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/qux.h
@@ -0,0 +1,3 @@
+int qux() {
+ return 3; // Set break point at this line.
+}
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/foo.h b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/foo.h
new file mode 120000
index 00000000000000..c9001b4753bba6
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/foo.h
@@ -0,0 +1 @@
+../real/foo.h
\ No newline at end of file
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink2 b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink2
new file mode 120000
index 00000000000000..ac558a3e1bf444
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink2
@@ -0,0 +1 @@
+real
\ No newline at end of file
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/to-be-mapped/README.md b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/to-be-mapped/README.md
new file mode 100644
index 00000000000000..5067af8eed047a
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/to-be-mapped/README.md
@@ -0,0 +1 @@
+This is an empty folder just so that `settings set target.source-map "real" "to-be-mapped"` can be run successfully - it requires tha the latter path is valid.
diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt
index d40c357e3f463b..949963fd403463 100644
--- a/lldb/unittests/Core/CMakeLists.txt
+++ b/lldb/unittests/Core/CMakeLists.txt
@@ -3,7 +3,6 @@ add_lldb_unittest(LLDBCoreTests
DiagnosticEventTest.cpp
DumpDataExtractorTest.cpp
DumpRegisterInfoTest.cpp
- FileSpecListTest.cpp
FormatEntityTest.cpp
MangledTest.cpp
ModuleSpecTest.cpp
diff --git a/lldb/unittests/Core/FileSpecListTest.cpp b/lldb/unittests/Core/FileSpecListTest.cpp
deleted file mode 100644
index e63f4a00bc3a94..00000000000000
--- a/lldb/unittests/Core/FileSpecListTest.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-//===-- FileSpecListTest.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 "gtest/gtest.h"
-
-#include "lldb/Utility/FileSpecList.h"
-
-using namespace lldb_private;
-
-static FileSpec PosixSpec(llvm::StringRef path) {
- return FileSpec(path, FileSpec::Style::posix);
-}
-
-static FileSpec WindowsSpec(llvm::StringRef path) {
- return FileSpec(path, FileSpec::Style::windows);
-}
-
-TEST(SupportFileListTest, RelativePathMatchesPosix) {
-
- const FileSpec fullpath = PosixSpec("/build/src/main.cpp");
- const FileSpec relative = PosixSpec("./src/main.cpp");
- const FileSpec basename = PosixSpec("./main.cpp");
- const FileSpec full_wrong = PosixSpec("/other/wrong/main.cpp");
- const FileSpec rel_wrong = PosixSpec("./wrong/main.cpp");
- // Make sure these don't match "src/main.cpp" as we want to match full
- // directories only
- const FileSpec rel2_wrong = PosixSpec("asrc/main.cpp");
- const FileSpec rel3_wrong = PosixSpec("rc/main.cpp");
-
- SupportFileList files;
- files.Append(fullpath);
- files.Append(relative);
- files.Append(basename);
- files.Append(full_wrong);
- files.Append(rel_wrong);
- files.Append(rel2_wrong);
- files.Append(rel3_wrong);
-
- // Make sure the full path only matches the first entry
- EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, fullpath));
- EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, fullpath));
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, fullpath));
- EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, fullpath));
- // Make sure the relative path matches the all of the entries that contain
- // the relative path
- EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, relative));
- EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, relative));
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, relative));
- EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, relative));
-
- // Make sure looking file a file using the basename matches all entries
- EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, basename));
- EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, basename));
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, basename));
- EXPECT_EQ((size_t)3, files.FindCompatibleIndex(3, basename));
- EXPECT_EQ((size_t)4, files.FindCompatibleIndex(4, basename));
- EXPECT_EQ((size_t)5, files.FindCompatibleIndex(5, basename));
- EXPECT_EQ((size_t)6, files.FindCompatibleIndex(6, basename));
-
- // Make sure that paths that have a common suffix don't return values that
- // don't match on directory delimiters.
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel2_wrong));
- EXPECT_EQ((size_t)5, files.FindCompatibleIndex(3, rel2_wrong));
- EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(6, rel2_wrong));
-
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel3_wrong));
- EXPECT_EQ((size_t)6, files.FindCompatibleIndex(3, rel3_wrong));
-}
-
-TEST(SupportFileListTest, RelativePathMatchesWindows) {
-
- const FileSpec fullpath = WindowsSpec(R"(C:\build\src\main.cpp)");
- const FileSpec relative = WindowsSpec(R"(.\src\main.cpp)");
- const FileSpec basename = WindowsSpec(R"(.\main.cpp)");
- const FileSpec full_wrong = WindowsSpec(R"(\other\wrong\main.cpp)");
- const FileSpec rel_wrong = WindowsSpec(R"(.\wrong\main.cpp)");
- // Make sure these don't match "src\main.cpp" as we want to match full
- // directories only
- const FileSpec rel2_wrong = WindowsSpec(R"(asrc\main.cpp)");
- const FileSpec rel3_wrong = WindowsSpec(R"("rc\main.cpp)");
-
- SupportFileList files;
- files.Append(fullpath);
- files.Append(relative);
- files.Append(basename);
- files.Append(full_wrong);
- files.Append(rel_wrong);
- files.Append(rel2_wrong);
- files.Append(rel3_wrong);
-
- // Make sure the full path only matches the first entry
- EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, fullpath));
- EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, fullpath));
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, fullpath));
- EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, fullpath));
- // Make sure the relative path matches the all of the entries that contain
- // the relative path
- EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, relative));
- EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, relative));
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, relative));
- EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, relative));
-
- // Make sure looking file a file using the basename matches all entries
- EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, basename));
- EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, basename));
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, basename));
- EXPECT_EQ((size_t)3, files.FindCompatibleIndex(3, basename));
- EXPECT_EQ((size_t)4, files.FindCompatibleIndex(4, basename));
- EXPECT_EQ((size_t)5, files.FindCompatibleIndex(5, basename));
- EXPECT_EQ((size_t)6, files.FindCompatibleIndex(6, basename));
-
- // Make sure that paths that have a common suffix don't return values that
- // don't match on directory delimiters.
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel2_wrong));
- EXPECT_EQ((size_t)5, files.FindCompatibleIndex(3, rel2_wrong));
- EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(6, rel2_wrong));
-
- EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel3_wrong));
- EXPECT_EQ((size_t)6, files.FindCompatibleIndex(3, rel3_wrong));
-}
diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt
index 8e12815d51541c..40e0959fc01d14 100644
--- a/lldb/unittests/Utility/CMakeLists.txt
+++ b/lldb/unittests/Utility/CMakeLists.txt
@@ -12,6 +12,7 @@ add_lldb_unittest(UtilityTests
DataExtractorTest.cpp
EnvironmentTest.cpp
EventTest.cpp
+ FileSpecListTest.cpp
FileSpecTest.cpp
FlagsTest.cpp
ListenerTest.cpp
@@ -22,6 +23,7 @@ add_lldb_unittest(UtilityTests
ProcessInstanceInfoTest.cpp
RangeMapTest.cpp
RangeTest.cpp
+ RealpathPrefixesTest.cpp
RegisterValueTest.cpp
RegularExpressionTest.cpp
ScalarTest.cpp
@@ -49,6 +51,7 @@ add_lldb_unittest(UtilityTests
XcodeSDKTest.cpp
LINK_LIBS
+ lldbTarget
lldbUtility
lldbUtilityHelpers
LLVMTestingSupport
diff --git a/lldb/unittests/Utility/FileSpecListTest.cpp b/lldb/unittests/Utility/FileSpecListTest.cpp
new file mode 100644
index 00000000000000..4c04f434261301
--- /dev/null
+++ b/lldb/unittests/Utility/FileSpecListTest.cpp
@@ -0,0 +1,323 @@
+//===-- FileSpecListTest.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 "gtest/gtest.h"
+
+#include "MockSymlinkFileSystem.h"
+#include "lldb/Utility/FileSpecList.h"
+#include "lldb/Utility/RealpathPrefixes.h"
+
+using namespace lldb_private;
+
+static FileSpec PosixSpec(llvm::StringRef path) {
+ return FileSpec(path, FileSpec::Style::posix);
+}
+
+static FileSpec WindowsSpec(llvm::StringRef path) {
+ return FileSpec(path, FileSpec::Style::windows);
+}
+
+TEST(SupportFileListTest, RelativePathMatchesPosix) {
+
+ const FileSpec fullpath = PosixSpec("/build/src/main.cpp");
+ const FileSpec relative = PosixSpec("./src/main.cpp");
+ const FileSpec basename = PosixSpec("./main.cpp");
+ const FileSpec full_wrong = PosixSpec("/other/wrong/main.cpp");
+ const FileSpec rel_wrong = PosixSpec("./wrong/main.cpp");
+ // Make sure these don't match "src/main.cpp" as we want to match full
+ // directories only
+ const FileSpec rel2_wrong = PosixSpec("asrc/main.cpp");
+ const FileSpec rel3_wrong = PosixSpec("rc/main.cpp");
+
+ SupportFileList files;
+ files.Append(fullpath);
+ files.Append(relative);
+ files.Append(basename);
+ files.Append(full_wrong);
+ files.Append(rel_wrong);
+ files.Append(rel2_wrong);
+ files.Append(rel3_wrong);
+
+ // Make sure the full path only matches the first entry
+ EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, fullpath));
+ EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, fullpath));
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, fullpath));
+ EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, fullpath));
+ // Make sure the relative path matches the all of the entries that contain
+ // the relative path
+ EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, relative));
+ EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, relative));
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, relative));
+ EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, relative));
+
+ // Make sure looking file a file using the basename matches all entries
+ EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, basename));
+ EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, basename));
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, basename));
+ EXPECT_EQ((size_t)3, files.FindCompatibleIndex(3, basename));
+ EXPECT_EQ((size_t)4, files.FindCompatibleIndex(4, basename));
+ EXPECT_EQ((size_t)5, files.FindCompatibleIndex(5, basename));
+ EXPECT_EQ((size_t)6, files.FindCompatibleIndex(6, basename));
+
+ // Make sure that paths that have a common suffix don't return values that
+ // don't match on directory delimiters.
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel2_wrong));
+ EXPECT_EQ((size_t)5, files.FindCompatibleIndex(3, rel2_wrong));
+ EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(6, rel2_wrong));
+
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel3_wrong));
+ EXPECT_EQ((size_t)6, files.FindCompatibleIndex(3, rel3_wrong));
+}
+
+TEST(SupportFileListTest, RelativePathMatchesWindows) {
+
+ const FileSpec fullpath = WindowsSpec(R"(C:\build\src\main.cpp)");
+ const FileSpec relative = WindowsSpec(R"(.\src\main.cpp)");
+ const FileSpec basename = WindowsSpec(R"(.\main.cpp)");
+ const FileSpec full_wrong = WindowsSpec(R"(\other\wrong\main.cpp)");
+ const FileSpec rel_wrong = WindowsSpec(R"(.\wrong\main.cpp)");
+ // Make sure these don't match "src\main.cpp" as we want to match full
+ // directories only
+ const FileSpec rel2_wrong = WindowsSpec(R"(asrc\main.cpp)");
+ const FileSpec rel3_wrong = WindowsSpec(R"("rc\main.cpp)");
+
+ SupportFileList files;
+ files.Append(fullpath);
+ files.Append(relative);
+ files.Append(basename);
+ files.Append(full_wrong);
+ files.Append(rel_wrong);
+ files.Append(rel2_wrong);
+ files.Append(rel3_wrong);
+
+ // Make sure the full path only matches the first entry
+ EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, fullpath));
+ EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, fullpath));
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, fullpath));
+ EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, fullpath));
+ // Make sure the relative path matches the all of the entries that contain
+ // the relative path
+ EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, relative));
+ EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, relative));
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, relative));
+ EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(3, relative));
+
+ // Make sure looking file a file using the basename matches all entries
+ EXPECT_EQ((size_t)0, files.FindCompatibleIndex(0, basename));
+ EXPECT_EQ((size_t)1, files.FindCompatibleIndex(1, basename));
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(2, basename));
+ EXPECT_EQ((size_t)3, files.FindCompatibleIndex(3, basename));
+ EXPECT_EQ((size_t)4, files.FindCompatibleIndex(4, basename));
+ EXPECT_EQ((size_t)5, files.FindCompatibleIndex(5, basename));
+ EXPECT_EQ((size_t)6, files.FindCompatibleIndex(6, basename));
+
+ // Make sure that paths that have a common suffix don't return values that
+ // don't match on directory delimiters.
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel2_wrong));
+ EXPECT_EQ((size_t)5, files.FindCompatibleIndex(3, rel2_wrong));
+ EXPECT_EQ((size_t)UINT32_MAX, files.FindCompatibleIndex(6, rel2_wrong));
+
+ EXPECT_EQ((size_t)2, files.FindCompatibleIndex(0, rel3_wrong));
+ EXPECT_EQ((size_t)6, files.FindCompatibleIndex(3, rel3_wrong));
+}
+
+// Support file is a symlink to the breakpoint file.
+// Absolute paths are used.
+// A matching prefix is set.
+// Should find it compatible.
+TEST(SupportFileListTest, SymlinkedAbsolutePaths) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("/symlink_dir/foo.h"), FileSpec("/real_dir/foo.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("/symlink_dir");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("/symlink_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("/real_dir/foo.h"), &prefixes);
+ EXPECT_EQ(ret, (size_t)0);
+}
+
+// Support file is a symlink to the breakpoint file.
+// Absolute paths are used.
+// A matching prefix is set, which is the root directory.
+// Should find it compatible.
+TEST(SupportFileListTest, RootDirectory) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("/symlink_dir/foo.h"), FileSpec("/real_dir/foo.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("/");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("/symlink_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("/real_dir/foo.h"), &prefixes);
+ EXPECT_EQ(ret, (size_t)0);
+}
+
+// Support file is a symlink to the breakpoint file.
+// Relative paths are used.
+// A matching prefix is set.
+// Should find it compatible.
+TEST(SupportFileListTest, SymlinkedRelativePaths) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("symlink_dir/foo.h"), FileSpec("real_dir/foo.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("symlink_dir");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("symlink_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("real_dir/foo.h"), &prefixes);
+ EXPECT_EQ(ret, (size_t)0);
+}
+
+// Support file is a symlink to the breakpoint file.
+// A matching prefix is set.
+// Input file only match basename and not directory.
+// Should find it incompatible.
+TEST(SupportFileListTest, RealpathOnlyMatchFileName) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("symlink_dir/foo.h"), FileSpec("real_dir/foo.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("symlink_dir");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("symlink_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("some_other_dir/foo.h"), &prefixes);
+ EXPECT_EQ(ret, UINT32_MAX);
+}
+
+// Support file is a symlink to the breakpoint file.
+// A prefix is set, which is a matching string prefix, but not a path prefix.
+// Should find it incompatible.
+TEST(SupportFileListTest, DirectoryMatchStringPrefixButNotWholeDirectoryName) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("symlink_dir/foo.h"), FileSpec("real_dir/foo.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("symlink"); // This is a string prefix of the
+ // symlink but not a path prefix.
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("symlink_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("real_dir/foo.h"), &prefixes);
+ EXPECT_EQ(ret, UINT32_MAX);
+}
+
+// Support file is a symlink to the breakpoint file.
+// A matching prefix is set.
+// However, the breakpoint is set with a partial path.
+// Should find it compatible.
+TEST(SupportFileListTest, PartialBreakpointPath) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("symlink_dir/foo.h"), FileSpec("/real_dir/foo.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("symlink_dir");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("symlink_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("real_dir/foo.h"), &prefixes);
+ EXPECT_EQ(ret, (size_t)0);
+}
+
+// Support file is a symlink to the breakpoint file.
+// A matching prefix is set.
+// However, the basename is
diff erent between the symlink and its target.
+// Should find it incompatible.
+TEST(SupportFileListTest, DifferentBasename) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("/symlink_dir/foo.h"), FileSpec("/real_dir/bar.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("/symlink_dir");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("/symlink_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("real_dir/bar.h"), &prefixes);
+ EXPECT_EQ(ret, UINT32_MAX);
+}
+
+// No prefixes are configured.
+// The support file and the breakpoint file are
diff erent.
+// Should find it incompatible.
+TEST(SupportFileListTest, NoPrefixes) {
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("/real_dir/bar.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("/real_dir/foo.h"), nullptr);
+ EXPECT_EQ(ret, UINT32_MAX);
+}
+
+// No prefixes are configured.
+// The support file and the breakpoint file are the same.
+// Should find it compatible.
+TEST(SupportFileListTest, SameFile) {
+ // Prepare support file list
+ SupportFileList support_file_list;
+ support_file_list.EmplaceBack(FileSpec("/real_dir/foo.h"));
+
+ // Test
+ size_t ret = support_file_list.FindCompatibleIndex(
+ 0, FileSpec("/real_dir/foo.h"), nullptr);
+ EXPECT_EQ(ret, (size_t)0);
+}
diff --git a/lldb/unittests/Utility/MockSymlinkFileSystem.h b/lldb/unittests/Utility/MockSymlinkFileSystem.h
new file mode 100644
index 00000000000000..7fa1f93bfa38a9
--- /dev/null
+++ b/lldb/unittests/Utility/MockSymlinkFileSystem.h
@@ -0,0 +1,66 @@
+//===-- MockSymlinkFileSystem.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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Utility/FileSpec.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+namespace lldb_private {
+
+// A mock file system that realpath's a given symlink to a given realpath.
+class MockSymlinkFileSystem : public llvm::vfs::FileSystem {
+public:
+ // Treat all files as non-symlinks.
+ MockSymlinkFileSystem() = default;
+
+ /// Treat \a symlink as a symlink to \a realpath. Treat all other files as
+ /// non-symlinks.
+ MockSymlinkFileSystem(FileSpec &&symlink, FileSpec &&realpath,
+ FileSpec::Style style = FileSpec::Style::native)
+ : m_symlink(std::move(symlink)), m_realpath(std::move(realpath)),
+ m_style(style) {}
+
+ /// If \a Path matches the symlink given in the ctor, put the realpath given
+ /// in the ctor into \a Output.
+ std::error_code getRealPath(const llvm::Twine &Path,
+ llvm::SmallVectorImpl<char> &Output) override {
+ if (FileSpec(Path.str(), m_style) == m_symlink) {
+ std::string path = m_realpath.GetPath();
+ Output.assign(path.begin(), path.end());
+ } else {
+ Path.toVector(Output);
+ }
+ return {};
+ }
+
+ // Implement the rest of the interface
+ llvm::ErrorOr<llvm::vfs::Status> status(const llvm::Twine &Path) override {
+ return llvm::errc::operation_not_permitted;
+ }
+ llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
+ openFileForRead(const llvm::Twine &Path) override {
+ return llvm::errc::operation_not_permitted;
+ }
+ llvm::vfs::directory_iterator dir_begin(const llvm::Twine &Dir,
+ std::error_code &EC) override {
+ return llvm::vfs::directory_iterator();
+ }
+ std::error_code setCurrentWorkingDirectory(const llvm::Twine &Path) override {
+ return llvm::errc::operation_not_permitted;
+ }
+ llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
+ return llvm::errc::operation_not_permitted;
+ }
+
+private:
+ FileSpec m_symlink;
+ FileSpec m_realpath;
+ FileSpec::Style m_style;
+};
+
+} // namespace lldb_private
diff --git a/lldb/unittests/Utility/RealpathPrefixesTest.cpp b/lldb/unittests/Utility/RealpathPrefixesTest.cpp
new file mode 100644
index 00000000000000..872ddf1fd223aa
--- /dev/null
+++ b/lldb/unittests/Utility/RealpathPrefixesTest.cpp
@@ -0,0 +1,140 @@
+//===-- RealpathPrefixesTest.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 "gtest/gtest.h"
+
+#include "MockSymlinkFileSystem.h"
+#include "lldb/Utility/FileSpecList.h"
+#include "lldb/Utility/RealpathPrefixes.h"
+
+using namespace lldb_private;
+
+// Should resolve a symlink which match an absolute prefix
+TEST(RealpathPrefixesTest, MatchingAbsolutePrefix) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("/dir1/link.h"), FileSpec("/dir2/real.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("/dir1");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Test
+ std::optional<FileSpec> ret =
+ prefixes.ResolveSymlinks(FileSpec("/dir1/link.h"));
+ EXPECT_EQ(ret, FileSpec("/dir2/real.h"));
+}
+
+// Should resolve a symlink which match a relative prefix
+TEST(RealpathPrefixesTest, MatchingRelativePrefix) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("dir1/link.h"), FileSpec("dir2/real.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("dir1");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Test
+ std::optional<FileSpec> ret =
+ prefixes.ResolveSymlinks(FileSpec("dir1/link.h"));
+ EXPECT_EQ(ret, FileSpec("dir2/real.h"));
+}
+
+// Should resolve in Windows and/or with a case-insensitive support file
+TEST(RealpathPrefixesTest, WindowsAndCaseInsensitive) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("f:\\dir1\\link.h", FileSpec::Style::windows),
+ FileSpec("f:\\dir2\\real.h", FileSpec::Style::windows),
+ FileSpec::Style::windows));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack(FileSpec("f:\\dir1", FileSpec::Style::windows));
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Test
+ std::optional<FileSpec> ret = prefixes.ResolveSymlinks(
+ FileSpec("F:\\DIR1\\LINK.H", FileSpec::Style::windows));
+ EXPECT_EQ(ret, FileSpec("f:\\dir2\\real.h", FileSpec::Style::windows));
+}
+
+// Should resolve a symlink when there is mixture of matching and mismatching
+// prefixex
+TEST(RealpathPrefixesTest, MatchingAndMismatchingPrefix) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("/dir1/link.h"), FileSpec("/dir2/real.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("/fake/path1");
+ file_spec_list.EmplaceBack("/dir1"); // Matching prefix
+ file_spec_list.EmplaceBack("/fake/path2");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Test
+ std::optional<FileSpec> ret =
+ prefixes.ResolveSymlinks(FileSpec("/dir1/link.h"));
+ EXPECT_EQ(ret, FileSpec("/dir2/real.h"));
+}
+
+// Should resolve a symlink when the prefixes matches after normalization
+TEST(RealpathPrefixesTest, ComplexPrefixes) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("dir1/link.h"), FileSpec("dir2/real.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("./dir1/foo/../bar/.."); // Equivalent to "/dir1"
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Test
+ std::optional<FileSpec> ret =
+ prefixes.ResolveSymlinks(FileSpec("dir1/link.h"));
+ EXPECT_EQ(ret, FileSpec("dir2/real.h"));
+}
+
+// Should not resolve a symlink which doesn't match any prefixes
+TEST(RealpathPrefixesTest, MismatchingPrefixes) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+ FileSpec("/dir1/link.h"), FileSpec("/dir2/real.h")));
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("/dir3");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Test
+ std::optional<FileSpec> ret =
+ prefixes.ResolveSymlinks(FileSpec("/dir1/link.h"));
+ EXPECT_EQ(ret, std::nullopt);
+}
+
+// Should not resolve a realpath
+TEST(RealpathPrefixesTest, Realpath) {
+ // Prepare FS
+ llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(
+ new MockSymlinkFileSystem());
+
+ // Prepare RealpathPrefixes
+ FileSpecList file_spec_list;
+ file_spec_list.EmplaceBack("/symlink_dir");
+ RealpathPrefixes prefixes(file_spec_list, fs);
+
+ // Test
+ std::optional<FileSpec> ret =
+ prefixes.ResolveSymlinks(FileSpec("/dir/real.h"));
+ EXPECT_EQ(ret, std::nullopt);
+}
More information about the lldb-commits
mailing list