[Lldb-commits] [lldb] [lldb] Realpath symlinks for breakpoints (PR #102223)

via lldb-commits lldb-commits at lists.llvm.org
Fri Aug 9 16:45:59 PDT 2024


https://github.com/royitaqi updated https://github.com/llvm/llvm-project/pull/102223

>From c629defe38a510ffdd23122b2e9cfac68933cf34 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 2 Aug 2024 06:35:45 -0700
Subject: [PATCH 1/9] [lldb] Allow realpathing support files when resolving
 file/line breakpoints

Differential Revision: https://phabricator.intern.facebook.com/D60317677
---
 lldb/include/lldb/Symbol/CompileUnit.h        | 10 +-
 lldb/include/lldb/Target/Target.h             |  4 +
 lldb/include/lldb/Utility/FileSpecList.h      |  9 +-
 lldb/include/lldb/Utility/RealpathPrefixes.h  | 55 +++++++++++
 .../Breakpoint/BreakpointResolverFileLine.cpp |  6 +-
 lldb/source/Symbol/CompileUnit.cpp            | 14 +--
 lldb/source/Target/Target.cpp                 |  8 ++
 lldb/source/Target/TargetProperties.td        |  3 +
 lldb/source/Utility/CMakeLists.txt            |  1 +
 lldb/source/Utility/FileSpecList.cpp          | 95 ++++++++++++-------
 lldb/source/Utility/RealpathPrefixes.cpp      | 57 +++++++++++
 11 files changed, 216 insertions(+), 46 deletions(-)
 create mode 100644 lldb/include/lldb/Utility/RealpathPrefixes.h
 create mode 100644 lldb/source/Utility/RealpathPrefixes.cpp

diff --git a/lldb/include/lldb/Symbol/CompileUnit.h b/lldb/include/lldb/Symbol/CompileUnit.h
index c20a37e3283075..6a9ef3e6c75bcb 100644
--- a/lldb/include/lldb/Symbol/CompileUnit.h
+++ b/lldb/include/lldb/Symbol/CompileUnit.h
@@ -24,6 +24,8 @@
 #include "llvm/ADT/DenseSet.h"
 
 namespace lldb_private {
+class RealpathPrefixes;
+
 /// \class CompileUnit CompileUnit.h "lldb/Symbol/CompileUnit.h"
 /// A class that describes a compilation unit.
 ///
@@ -390,9 +392,11 @@ class CompileUnit : public std::enable_shared_from_this<CompileUnit>,
   ///     entries appended to.
   ///
   /// \see enum SymbolContext::Scope
-  void ResolveSymbolContext(const SourceLocationSpec &src_location_spec,
-                            lldb::SymbolContextItem resolve_scope,
-                            SymbolContextList &sc_list);
+  void
+  ResolveSymbolContext(const SourceLocationSpec &src_location_spec,
+                       lldb::SymbolContextItem resolve_scope,
+                       SymbolContextList &sc_list,
+                       const RealpathPrefixes *realpath_prefixes = nullptr);
 
   /// Get whether compiler optimizations were enabled for this compile unit
   ///
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 119dff4d498199..d6d5f05e9258f2 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -39,6 +39,8 @@
 
 namespace lldb_private {
 
+class RealpathPrefixes;
+
 OptionEnumValues GetDynamicValueTypes();
 
 enum InlineStrategy {
@@ -117,6 +119,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..99a99a45a1dad2 100644
--- a/lldb/include/lldb/Utility/FileSpecList.h
+++ b/lldb/include/lldb/Utility/FileSpecList.h
@@ -17,6 +17,7 @@
 #include <vector>
 
 namespace lldb_private {
+class RealpathPrefixes;
 class Stream;
 
 /// A list of support files for a CompileUnit.
@@ -64,10 +65,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,
+      const 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..ba39259980c067
--- /dev/null
+++ b/lldb/include/lldb/Utility/RealpathPrefixes.h
@@ -0,0 +1,55 @@
+//===-- 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_CORE_REALPATHPREFIXES_H
+#define LLDB_CORE_REALPATHPREFIXES_H
+
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace lldb_private {
+class FileSpec;
+class FileSpecList;
+} // namespace lldb_private
+
+namespace lldb_private {
+
+class RealpathPrefixes {
+public:
+  // Prefixes are obtained from FileSpecList, through FileSpec::GetPath(), which
+  // ensures that the paths are normalized. For example:
+  // "./foo/.." -> ""
+  // "./foo/../bar" -> "bar"
+  explicit RealpathPrefixes(const FileSpecList &file_spec_list);
+
+  // Sets an optional filesystem to use for realpath'ing. If not set, the real
+  // filesystem will be used.
+  void SetFileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs);
+
+  std::optional<FileSpec> ResolveSymlinks(const FileSpec &file_spec) const;
+
+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;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_CORE_REALPATHPREFIXES_H
diff --git a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
index 16c4ee1b88d162..315e017350d80b 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,13 +291,16 @@ 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();
 
+  RealpathPrefixes realpath_prefixes =
+      GetBreakpoint()->GetTarget().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);
     }
   }
 
diff --git a/lldb/source/Symbol/CompileUnit.cpp b/lldb/source/Symbol/CompileUnit.cpp
index ddeacf18e855ee..f60745cb2e38e4 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,
+                const 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,
+    const 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/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..d037293c5a09f4 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 that don't match the original source file locations that the user will use to set breakpoints, then this setting can help resolve breakpoints correctly.">;
   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..c43659c95a2d09 100644
--- a/lldb/source/Utility/FileSpecList.cpp
+++ b/lldb/source/Utility/FileSpecList.cpp
@@ -8,6 +8,7 @@
 
 #include "lldb/Utility/FileSpecList.h"
 #include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/RealpathPrefixes.h"
 #include "lldb/Utility/Stream.h"
 
 #include <cstdint>
@@ -108,52 +109,76 @@ 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,
+  kCompatible = 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 the 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::kCompatible;
+  } else if (curr_file.IsRelative() || file_spec_relative) {
+    llvm::StringRef curr_file_dir = curr_file.GetDirectory().GetStringRef();
+    if (curr_file_dir.empty())
+      return IsCompatibleResult::kCompatible; // 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 IsCompatibleResult::kCompatible;
+  }
+  return IsCompatibleResult::kOnlyFileMatch;
+}
+
+size_t SupportFileList::FindCompatibleIndex(
+    size_t start_idx, const FileSpec &file_spec,
+    const 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::kCompatible)
       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))
+          return idx;
+      }
     }
   }
 
diff --git a/lldb/source/Utility/RealpathPrefixes.cpp b/lldb/source/Utility/RealpathPrefixes.cpp
new file mode 100644
index 00000000000000..a54145981061ed
--- /dev/null
+++ b/lldb/source/Utility/RealpathPrefixes.cpp
@@ -0,0 +1,57 @@
+//===-- 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/Host/FileSystem.h"
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/FileSpecList.h"
+
+using namespace lldb_private;
+
+RealpathPrefixes::RealpathPrefixes(const FileSpecList &file_spec_list)
+    : m_fs(llvm::vfs::getRealFileSystem()) {
+  m_prefixes.reserve(file_spec_list.GetSize());
+  for (const FileSpec &file_spec : file_spec_list) {
+    m_prefixes.emplace_back(file_spec.GetPath());
+  }
+}
+
+void RealpathPrefixes::SetFileSystem(
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
+  m_fs = fs;
+}
+
+std::optional<FileSpec>
+RealpathPrefixes::ResolveSymlinks(const FileSpec &file_spec) const {
+  if (m_prefixes.empty())
+    return std::nullopt;
+
+  auto is_prefix = [](llvm::StringRef a, llvm::StringRef b,
+                      bool case_sensitive) -> bool {
+    return case_sensitive ? a.consume_front(b) : a.consume_front_insensitive(b);
+  };
+  std::string file_spec_path = file_spec.GetPath();
+  for (const std::string &prefix : m_prefixes) {
+    if (is_prefix(file_spec_path, prefix, file_spec.IsCaseSensitive())) {
+      // 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);
+
+      // Only return realpath if it is different from the original file_spec.
+      if (realpath != file_spec)
+        return realpath;
+      return std::nullopt;
+    }
+  }
+  // No prefix matched
+  return std::nullopt;
+}

>From 8de590f656fc2b8ed3cb4348fde2a9b243acbad4 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 2 Aug 2024 06:37:28 -0700
Subject: [PATCH 2/9] Add unit tests

Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:

Differential Revision: https://phabricator.intern.facebook.com/D60668386
---
 lldb/unittests/Core/CMakeLists.txt            |   1 -
 lldb/unittests/Utility/CMakeLists.txt         |   2 +
 .../{Core => Utility}/FileSpecListTest.cpp    | 130 ++++++++++++++++
 .../unittests/Utility/MockSymlinkFileSystem.h |  63 ++++++++
 .../Utility/RealpathPrefixesTest.cpp          | 146 ++++++++++++++++++
 5 files changed, 341 insertions(+), 1 deletion(-)
 rename lldb/unittests/{Core => Utility}/FileSpecListTest.cpp (57%)
 create mode 100644 lldb/unittests/Utility/MockSymlinkFileSystem.h
 create mode 100644 lldb/unittests/Utility/RealpathPrefixesTest.cpp

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/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt
index 8e12815d51541c..1aaf87af699ceb 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
diff --git a/lldb/unittests/Core/FileSpecListTest.cpp b/lldb/unittests/Utility/FileSpecListTest.cpp
similarity index 57%
rename from lldb/unittests/Core/FileSpecListTest.cpp
rename to lldb/unittests/Utility/FileSpecListTest.cpp
index e63f4a00bc3a94..06efdd4a70458c 100644
--- a/lldb/unittests/Core/FileSpecListTest.cpp
+++ b/lldb/unittests/Utility/FileSpecListTest.cpp
@@ -8,7 +8,9 @@
 
 #include "gtest/gtest.h"
 
+#include "MockSymlinkFileSystem.h"
 #include "lldb/Utility/FileSpecList.h"
+#include "lldb/Utility/RealpathPrefixes.h"
 
 using namespace lldb_private;
 
@@ -123,3 +125,131 @@ TEST(SupportFileListTest, RelativePathMatchesWindows) {
   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.
+// A matching prefix is set.
+// Should find the two compatible.
+// Absolute paths are used.
+TEST(SupportFileListTest, SymlinkedAbsolutePaths) {
+  // Prepare FS
+  llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+      FileSpec("/symlink_dir/foo.h"), FileSpec("/real_dir/foo.h")));
+
+  // Prepare RealpathPrefixes
+  FileSpecList temp;
+  temp.EmplaceBack("/symlink_dir");
+  RealpathPrefixes prefixes(std::move(temp));
+  prefixes.SetFileSystem(fs);
+
+  // Prepare FileSpecList
+  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.
+// Should find the two compatible.
+// Relative paths are used.
+TEST(SupportFileListTest, SymlinkedRelativePaths) {
+  // Prepare FS
+  llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
+      FileSpec("symlink_dir/foo.h"), FileSpec("real_dir/foo.h")));
+
+  // Prepare RealpathPrefixes
+  FileSpecList temp;
+  temp.EmplaceBack("symlink_dir");
+  RealpathPrefixes prefixes(std::move(temp));
+  prefixes.SetFileSystem(fs);
+
+  // Prepare FileSpecList
+  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 breakpoint is set with a partial path.
+// Should find the two 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 temp;
+  temp.EmplaceBack("symlink_dir");
+  RealpathPrefixes prefixes(std::move(temp));
+  prefixes.SetFileSystem(fs);
+
+  // Prepare FileSpecList
+  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 different between the symlink and its target.
+// Should find the two 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 temp;
+  temp.EmplaceBack("/symlink_dir");
+  RealpathPrefixes prefixes(std::move(temp));
+  prefixes.SetFileSystem(fs);
+
+  // Prepare FileSpecList
+  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 different.
+// Should find the two incompatible.
+TEST(SupportFileListTest, NoPrefixes) {
+  // Prepare FileSpecList
+  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 the two compatible.
+TEST(SupportFileListTest, SameFile) {
+  // Prepare FileSpecList
+  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..3326fc8046819d
--- /dev/null
+++ b/lldb/unittests/Utility/MockSymlinkFileSystem.h
@@ -0,0 +1,63 @@
+//===-- 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)
+      : m_symlink(std::move(symlink)), m_realpath(std::move(realpath)) {}
+
+  /// 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_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;
+};
+
+} // namespace lldb_private
diff --git a/lldb/unittests/Utility/RealpathPrefixesTest.cpp b/lldb/unittests/Utility/RealpathPrefixesTest.cpp
new file mode 100644
index 00000000000000..15b06f9854b8fa
--- /dev/null
+++ b/lldb/unittests/Utility/RealpathPrefixesTest.cpp
@@ -0,0 +1,146 @@
+//===-- 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(std::move(file_spec_list));
+  prefixes.SetFileSystem(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(std::move(file_spec_list));
+  prefixes.SetFileSystem(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 prefix with a case-insensitive support
+// file
+TEST(RealpathPrefixesTest, CaseInsensitive) {
+  // 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(std::move(file_spec_list));
+  prefixes.SetFileSystem(fs);
+
+  // Test
+  std::optional<FileSpec> ret = prefixes.ResolveSymlinks(
+      FileSpec("/DIR1/LINK.H", FileSpec::Style::posix));
+  EXPECT_EQ(ret, FileSpec("/dir2/real.h"));
+}
+
+// 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(std::move(file_spec_list));
+  prefixes.SetFileSystem(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(std::move(file_spec_list));
+  prefixes.SetFileSystem(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(std::move(file_spec_list));
+  prefixes.SetFileSystem(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(std::move(file_spec_list));
+  prefixes.SetFileSystem(fs);
+
+  // Test
+  std::optional<FileSpec> ret =
+      prefixes.ResolveSymlinks(FileSpec("/dir/real.h"));
+  EXPECT_EQ(ret, std::nullopt);
+}

>From 02c015d3e16fa5dd9495fc4f663cd65afbb4aba4 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 2 Aug 2024 08:30:53 -0700
Subject: [PATCH 3/9] Add logging and telemetry

Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:

Differential Revision: https://phabricator.intern.facebook.com/D60673160
---
 lldb/include/lldb/Target/Statistics.h           |  4 ++++
 lldb/include/lldb/Utility/RealpathPrefixes.h    |  8 ++++++++
 lldb/source/Target/Statistics.cpp               | 12 ++++++++++++
 lldb/source/Target/Target.cpp                   |  4 +++-
 lldb/source/Utility/FileSpecList.cpp            | 15 ++++++++++++++-
 lldb/source/Utility/RealpathPrefixes.cpp        | 15 ++++++++++++---
 lldb/unittests/Utility/CMakeLists.txt           |  1 +
 lldb/unittests/Utility/MockSymlinkFileSystem.h  |  9 ++++++---
 lldb/unittests/Utility/RealpathPrefixesTest.cpp | 15 ++++++++-------
 9 files changed, 68 insertions(+), 15 deletions(-)

diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h
index 35bd7f8a66e055..2ce56309aefc39 100644
--- a/lldb/include/lldb/Target/Statistics.h
+++ b/lldb/include/lldb/Target/Statistics.h
@@ -184,6 +184,8 @@ class TargetStats {
   void SetFirstPrivateStopTime();
   void SetFirstPublicStopTime();
   void IncreaseSourceMapDeduceCount();
+  void IncreaseSourceRealpathAttemptCount();
+  void IncreaseSourceRealpathCompatibleCount();
 
   StatsDuration &GetCreateTime() { return m_create_time; }
   StatsSuccessFail &GetExpressionStats() { return m_expr_eval; }
@@ -198,6 +200,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/Utility/RealpathPrefixes.h b/lldb/include/lldb/Utility/RealpathPrefixes.h
index ba39259980c067..b9387ec0dee731 100644
--- a/lldb/include/lldb/Utility/RealpathPrefixes.h
+++ b/lldb/include/lldb/Utility/RealpathPrefixes.h
@@ -19,6 +19,7 @@
 namespace lldb_private {
 class FileSpec;
 class FileSpecList;
+class Target;
 } // namespace lldb_private
 
 namespace lldb_private {
@@ -35,6 +36,10 @@ class RealpathPrefixes {
   // filesystem will be used.
   void SetFileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs);
 
+  // Sets an optional Target instance to gather statistics.
+  void SetTarget(Target *target) { m_target = target; }
+  Target *GetTarget() const { return m_target; }
+
   std::optional<FileSpec> ResolveSymlinks(const FileSpec &file_spec) const;
 
 private:
@@ -48,6 +53,9 @@ class RealpathPrefixes {
 
   // The filesystem to use for realpath'ing.
   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> m_fs;
+
+  // The optional Target instance to gather statistics.
+  Target *m_target;
 };
 
 } // namespace lldb_private
diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp
index 583d1524881fc3..894c247571ff90 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() {
+  ++m_source_realpath_attempt_count;
+}
+
+void TargetStats::IncreaseSourceRealpathCompatibleCount() {
+  ++m_source_realpath_compatible_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 5a5d689e03fbc0..48feb97db6d083 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4359,7 +4359,9 @@ InlineStrategy TargetProperties::GetInlineStrategy() const {
 // 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, {}));
+  auto prefixes = RealpathPrefixes(GetPropertyAtIndexAs<FileSpecList>(idx, {}));
+  prefixes.SetTarget(this->m_target);
+  return prefixes;
 }
 
 llvm::StringRef TargetProperties::GetArg0() const {
diff --git a/lldb/source/Utility/FileSpecList.cpp b/lldb/source/Utility/FileSpecList.cpp
index c43659c95a2d09..d16789d65e8017 100644
--- a/lldb/source/Utility/FileSpecList.cpp
+++ b/lldb/source/Utility/FileSpecList.cpp
@@ -7,7 +7,11 @@
 //===----------------------------------------------------------------------===//
 
 #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"
 
@@ -176,8 +180,17 @@ size_t SupportFileList::FindCompatibleIndex(
     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))
+        if (IsCompatible(*resolved_curr_file, file_spec)) {
+          // Stats and logging.
+          if (Target *target = realpath_prefixes->GetTarget())
+            target->GetStatistics().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
index a54145981061ed..646f08ef409c44 100644
--- a/lldb/source/Utility/RealpathPrefixes.cpp
+++ b/lldb/source/Utility/RealpathPrefixes.cpp
@@ -8,14 +8,17 @@
 
 #include "lldb/Utility/RealpathPrefixes.h"
 
-#include "lldb/Host/FileSystem.h"
+#include "lldb/Target/Statistics.h"
+#include "lldb/Target/Target.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)
-    : m_fs(llvm::vfs::getRealFileSystem()) {
+    : m_fs(llvm::vfs::getRealFileSystem()), m_target(nullptr) {
   m_prefixes.reserve(file_spec_list.GetSize());
   for (const FileSpec &file_spec : file_spec_list) {
     m_prefixes.emplace_back(file_spec.GetPath());
@@ -39,12 +42,18 @@ RealpathPrefixes::ResolveSymlinks(const FileSpec &file_spec) const {
   std::string file_spec_path = file_spec.GetPath();
   for (const std::string &prefix : m_prefixes) {
     if (is_prefix(file_spec_path, prefix, file_spec.IsCaseSensitive())) {
+      // Stats and logging.
+      if (m_target)
+        m_target->GetStatistics().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);
+      FileSpec realpath(buff, file_spec.GetPathStyle());
 
       // Only return realpath if it is different from the original file_spec.
       if (realpath != file_spec)
diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt
index 1aaf87af699ceb..40e0959fc01d14 100644
--- a/lldb/unittests/Utility/CMakeLists.txt
+++ b/lldb/unittests/Utility/CMakeLists.txt
@@ -51,6 +51,7 @@ add_lldb_unittest(UtilityTests
   XcodeSDKTest.cpp
 
   LINK_LIBS
+      lldbTarget
       lldbUtility
       lldbUtilityHelpers
       LLVMTestingSupport
diff --git a/lldb/unittests/Utility/MockSymlinkFileSystem.h b/lldb/unittests/Utility/MockSymlinkFileSystem.h
index 3326fc8046819d..7fa1f93bfa38a9 100644
--- a/lldb/unittests/Utility/MockSymlinkFileSystem.h
+++ b/lldb/unittests/Utility/MockSymlinkFileSystem.h
@@ -20,14 +20,16 @@ class MockSymlinkFileSystem : public llvm::vfs::FileSystem {
 
   /// Treat \a symlink as a symlink to \a realpath. Treat all other files as
   /// non-symlinks.
-  MockSymlinkFileSystem(FileSpec &&symlink, FileSpec &&realpath)
-      : m_symlink(std::move(symlink)), m_realpath(std::move(realpath)) {}
+  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_symlink) {
+    if (FileSpec(Path.str(), m_style) == m_symlink) {
       std::string path = m_realpath.GetPath();
       Output.assign(path.begin(), path.end());
     } else {
@@ -58,6 +60,7 @@ class MockSymlinkFileSystem : public llvm::vfs::FileSystem {
 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
index 15b06f9854b8fa..c6896c23b437da 100644
--- a/lldb/unittests/Utility/RealpathPrefixesTest.cpp
+++ b/lldb/unittests/Utility/RealpathPrefixesTest.cpp
@@ -51,23 +51,24 @@ TEST(RealpathPrefixesTest, MatchingRelativePrefix) {
   EXPECT_EQ(ret, FileSpec("dir2/real.h"));
 }
 
-// Should resolve a symlink which match a prefix with a case-insensitive support
-// file
-TEST(RealpathPrefixesTest, CaseInsensitive) {
+// Should resolve in Windows and/or with a case-insensitive support file
+TEST(RealpathPrefixesTest, WindowsAndCaseInsensitive) {
   // Prepare FS
   llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
-      FileSpec("/dir1/link.h"), FileSpec("/dir2/real.h")));
+      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("/dir1");
+  file_spec_list.EmplaceBack(FileSpec("f:\\dir1", FileSpec::Style::windows));
   RealpathPrefixes prefixes(std::move(file_spec_list));
   prefixes.SetFileSystem(fs);
 
   // Test
   std::optional<FileSpec> ret = prefixes.ResolveSymlinks(
-      FileSpec("/DIR1/LINK.H", FileSpec::Style::posix));
-  EXPECT_EQ(ret, FileSpec("/dir2/real.h"));
+      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

>From b99ced042c907b968dc97a1e3181f84657c4b00d Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Tue, 6 Aug 2024 13:28:23 -0700
Subject: [PATCH 4/9] Address additional comments in diffs

---
 lldb/include/lldb/Symbol/CompileUnit.h | 4 ++++
 lldb/include/lldb/Target/Target.h      | 3 +--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/lldb/include/lldb/Symbol/CompileUnit.h b/lldb/include/lldb/Symbol/CompileUnit.h
index 6a9ef3e6c75bcb..c8ce99b01b62f3 100644
--- a/lldb/include/lldb/Symbol/CompileUnit.h
+++ b/lldb/include/lldb/Symbol/CompileUnit.h
@@ -391,6 +391,10 @@ 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,
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index d6d5f05e9258f2..7f4d607f5427df 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -34,13 +34,12 @@
 #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"
 
 namespace lldb_private {
 
-class RealpathPrefixes;
-
 OptionEnumValues GetDynamicValueTypes();
 
 enum InlineStrategy {

>From 6011b7ebfaa3717086251dc61cd0ba32a6b0c3fd Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Wed, 7 Aug 2024 15:11:16 -0700
Subject: [PATCH 5/9] Address most of Jim's comments

---
 lldb/include/lldb/Symbol/CompileUnit.h       |  2 +-
 lldb/include/lldb/Target/Statistics.h        |  7 ++++---
 lldb/include/lldb/Utility/FileSpecList.h     |  1 -
 lldb/include/lldb/Utility/RealpathPrefixes.h | 21 +++++++++++---------
 lldb/include/lldb/lldb-forward.h             |  1 +
 lldb/source/Target/Target.cpp                |  2 +-
 lldb/source/Target/TargetProperties.td       |  2 +-
 lldb/source/Utility/FileSpecList.cpp         | 18 ++++++++---------
 lldb/source/Utility/RealpathPrefixes.cpp     |  6 +++---
 9 files changed, 32 insertions(+), 28 deletions(-)

diff --git a/lldb/include/lldb/Symbol/CompileUnit.h b/lldb/include/lldb/Symbol/CompileUnit.h
index c8ce99b01b62f3..d4cef886d8aa3d 100644
--- a/lldb/include/lldb/Symbol/CompileUnit.h
+++ b/lldb/include/lldb/Symbol/CompileUnit.h
@@ -19,12 +19,12 @@
 #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 RealpathPrefixes;
 
 /// \class CompileUnit CompileUnit.h "lldb/Symbol/CompileUnit.h"
 /// A class that describes a compilation unit.
diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h
index 2ce56309aefc39..c0c06e72c4f39e 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"
@@ -175,7 +176,7 @@ struct StatisticsOptions {
 };
 
 /// A class that represents statistics for a since lldb_private::Target.
-class TargetStats {
+class TargetStats : public RealpathPrefixesStats {
 public:
   llvm::json::Value ToJSON(Target &target,
                            const lldb_private::StatisticsOptions &options);
@@ -184,8 +185,8 @@ class TargetStats {
   void SetFirstPrivateStopTime();
   void SetFirstPublicStopTime();
   void IncreaseSourceMapDeduceCount();
-  void IncreaseSourceRealpathAttemptCount();
-  void IncreaseSourceRealpathCompatibleCount();
+  virtual void IncreaseSourceRealpathAttemptCount() override;
+  virtual void IncreaseSourceRealpathCompatibleCount() override;
 
   StatsDuration &GetCreateTime() { return m_create_time; }
   StatsSuccessFail &GetExpressionStats() { return m_expr_eval; }
diff --git a/lldb/include/lldb/Utility/FileSpecList.h b/lldb/include/lldb/Utility/FileSpecList.h
index 99a99a45a1dad2..b26e2ed05dc18d 100644
--- a/lldb/include/lldb/Utility/FileSpecList.h
+++ b/lldb/include/lldb/Utility/FileSpecList.h
@@ -17,7 +17,6 @@
 #include <vector>
 
 namespace lldb_private {
-class RealpathPrefixes;
 class Stream;
 
 /// A list of support files for a CompileUnit.
diff --git a/lldb/include/lldb/Utility/RealpathPrefixes.h b/lldb/include/lldb/Utility/RealpathPrefixes.h
index b9387ec0dee731..d50fbb663eacad 100644
--- a/lldb/include/lldb/Utility/RealpathPrefixes.h
+++ b/lldb/include/lldb/Utility/RealpathPrefixes.h
@@ -9,6 +9,7 @@
 #ifndef LLDB_CORE_REALPATHPREFIXES_H
 #define LLDB_CORE_REALPATHPREFIXES_H
 
+#include "lldb/lldb-forward.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/Support/VirtualFileSystem.h"
 
@@ -16,12 +17,6 @@
 #include <string>
 #include <vector>
 
-namespace lldb_private {
-class FileSpec;
-class FileSpecList;
-class Target;
-} // namespace lldb_private
-
 namespace lldb_private {
 
 class RealpathPrefixes {
@@ -37,8 +32,8 @@ class RealpathPrefixes {
   void SetFileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs);
 
   // Sets an optional Target instance to gather statistics.
-  void SetTarget(Target *target) { m_target = target; }
-  Target *GetTarget() const { return m_target; }
+  void SetTarget(const lldb::TargetSP& target) { m_target = target; }
+  lldb::TargetSP GetTarget() const { return m_target.lock(); }
 
   std::optional<FileSpec> ResolveSymlinks(const FileSpec &file_spec) const;
 
@@ -55,7 +50,15 @@ class RealpathPrefixes {
   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> m_fs;
 
   // The optional Target instance to gather statistics.
-  Target *m_target;
+  lldb::TargetWP m_target;
+};
+
+class RealpathPrefixesStats {
+public:
+  virtual ~RealpathPrefixesStats() = default;
+
+  virtual void IncreaseSourceRealpathAttemptCount() = 0;
+  virtual void IncreaseSourceRealpathCompatibleCount() = 0;
 };
 
 } // namespace lldb_private
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/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 48feb97db6d083..52cc682ae6b88e 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4360,7 +4360,7 @@ InlineStrategy TargetProperties::GetInlineStrategy() const {
 RealpathPrefixes TargetProperties::GetSourceRealpathPrefixes() const {
   const uint32_t idx = ePropertySourceRealpathPrefixes;
   auto prefixes = RealpathPrefixes(GetPropertyAtIndexAs<FileSpecList>(idx, {}));
-  prefixes.SetTarget(this->m_target);
+  prefixes.SetTarget(this->m_target->shared_from_this());
   return prefixes;
 }
 
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index d037293c5a09f4..421252aa4aea26 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -152,7 +152,7 @@ let Definition = "target" in {
     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 that don't match the original source file locations that the user will use to set breakpoints, then this setting can help resolve breakpoints correctly.">;
+    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/FileSpecList.cpp b/lldb/source/Utility/FileSpecList.cpp
index d16789d65e8017..f71a0b75b5b44e 100644
--- a/lldb/source/Utility/FileSpecList.cpp
+++ b/lldb/source/Utility/FileSpecList.cpp
@@ -116,7 +116,7 @@ size_t SupportFileList::FindFileIndex(size_t start_idx,
 enum IsCompatibleResult {
   kNoMatch = 0,
   kOnlyFileMatch = 1,
-  kCompatible = 2,
+  kBothDirectoryAndFileMatch = 2,
 };
 
 IsCompatibleResult IsCompatible(const FileSpec &curr_file,
@@ -132,15 +132,15 @@ IsCompatibleResult IsCompatible(const FileSpec &curr_file,
     return IsCompatibleResult::kNoMatch;
 
   // 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.
+  // 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::kCompatible;
+    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())
-      return IsCompatibleResult::kCompatible; // Basename match only for this
-                                              // file in the list
+      // 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.
@@ -158,7 +158,7 @@ IsCompatibleResult IsCompatible(const FileSpec &curr_file,
         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::kCompatible;
+      return IsCompatibleResult::kBothDirectoryAndFileMatch;
   }
   return IsCompatibleResult::kOnlyFileMatch;
 }
@@ -174,7 +174,7 @@ size_t SupportFileList::FindCompatibleIndex(
     const FileSpec &curr_file = m_files[idx]->GetSpecOnly();
 
     IsCompatibleResult result = IsCompatible(curr_file, file_spec);
-    if (result == IsCompatibleResult::kCompatible)
+    if (result == IsCompatibleResult::kBothDirectoryAndFileMatch)
       return idx;
 
     if (realpath_prefixes && result == IsCompatibleResult::kOnlyFileMatch) {
@@ -182,7 +182,7 @@ size_t SupportFileList::FindCompatibleIndex(
               realpath_prefixes->ResolveSymlinks(curr_file)) {
         if (IsCompatible(*resolved_curr_file, file_spec)) {
           // Stats and logging.
-          if (Target *target = realpath_prefixes->GetTarget())
+          if (lldb::TargetSP target = realpath_prefixes->GetTarget())
             target->GetStatistics().IncreaseSourceRealpathCompatibleCount();
           Log *log = GetLog(LLDBLog::Source);
           LLDB_LOGF(log,
diff --git a/lldb/source/Utility/RealpathPrefixes.cpp b/lldb/source/Utility/RealpathPrefixes.cpp
index 646f08ef409c44..c970e67cf88587 100644
--- a/lldb/source/Utility/RealpathPrefixes.cpp
+++ b/lldb/source/Utility/RealpathPrefixes.cpp
@@ -18,7 +18,7 @@
 using namespace lldb_private;
 
 RealpathPrefixes::RealpathPrefixes(const FileSpecList &file_spec_list)
-    : m_fs(llvm::vfs::getRealFileSystem()), m_target(nullptr) {
+    : m_fs(llvm::vfs::getRealFileSystem()) {
   m_prefixes.reserve(file_spec_list.GetSize());
   for (const FileSpec &file_spec : file_spec_list) {
     m_prefixes.emplace_back(file_spec.GetPath());
@@ -43,8 +43,8 @@ RealpathPrefixes::ResolveSymlinks(const FileSpec &file_spec) const {
   for (const std::string &prefix : m_prefixes) {
     if (is_prefix(file_spec_path, prefix, file_spec.IsCaseSensitive())) {
       // Stats and logging.
-      if (m_target)
-        m_target->GetStatistics().IncreaseSourceRealpathAttemptCount();
+      if (lldb::TargetSP target = m_target.lock())
+        target->GetStatistics().IncreaseSourceRealpathAttemptCount();
       Log *log = GetLog(LLDBLog::Source);
       LLDB_LOGF(log, "Realpath'ing support file %s", file_spec_path.c_str());
 

>From 7f6f55f9a26afa364058a41ecbff70c6ed0deef0 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 9 Aug 2024 10:40:04 -0700
Subject: [PATCH 6/9] Addressed all comments, EXCEPT the lack of a unit test
 containing both realpath and source-map

---
 lldb/include/lldb/Symbol/CompileUnit.h        |  9 ++-
 lldb/include/lldb/Target/Statistics.h         |  6 +-
 lldb/include/lldb/Utility/FileSpecList.h      |  6 +-
 lldb/include/lldb/Utility/RealpathPrefixes.h  | 51 +++++++++------
 .../Breakpoint/BreakpointResolverFileLine.cpp | 10 ++-
 lldb/source/Symbol/CompileUnit.cpp            |  4 +-
 lldb/source/Target/Statistics.cpp             |  8 +--
 lldb/source/Target/Target.cpp                 |  4 +-
 lldb/source/Utility/FileSpecList.cpp          |  8 +--
 lldb/source/Utility/RealpathPrefixes.cpp      | 36 ++++++-----
 lldb/unittests/Utility/FileSpecListTest.cpp   | 64 ++++++++++++-------
 .../Utility/RealpathPrefixesTest.cpp          | 21 ++----
 12 files changed, 129 insertions(+), 98 deletions(-)

diff --git a/lldb/include/lldb/Symbol/CompileUnit.h b/lldb/include/lldb/Symbol/CompileUnit.h
index d4cef886d8aa3d..c5bb080d211849 100644
--- a/lldb/include/lldb/Symbol/CompileUnit.h
+++ b/lldb/include/lldb/Symbol/CompileUnit.h
@@ -396,11 +396,10 @@ class CompileUnit : public std::enable_shared_from_this<CompileUnit>,
   ///     realpath'ed to resolve any symlinks.
   ///
   /// \see enum SymbolContext::Scope
-  void
-  ResolveSymbolContext(const SourceLocationSpec &src_location_spec,
-                       lldb::SymbolContextItem resolve_scope,
-                       SymbolContextList &sc_list,
-                       const RealpathPrefixes *realpath_prefixes = nullptr);
+  void ResolveSymbolContext(const SourceLocationSpec &src_location_spec,
+                            lldb::SymbolContextItem resolve_scope,
+                            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 c0c06e72c4f39e..5193d099a5494d 100644
--- a/lldb/include/lldb/Target/Statistics.h
+++ b/lldb/include/lldb/Target/Statistics.h
@@ -176,7 +176,7 @@ struct StatisticsOptions {
 };
 
 /// A class that represents statistics for a since lldb_private::Target.
-class TargetStats : public RealpathPrefixesStats {
+class TargetStats {
 public:
   llvm::json::Value ToJSON(Target &target,
                            const lldb_private::StatisticsOptions &options);
@@ -185,8 +185,8 @@ class TargetStats : public RealpathPrefixesStats {
   void SetFirstPrivateStopTime();
   void SetFirstPublicStopTime();
   void IncreaseSourceMapDeduceCount();
-  virtual void IncreaseSourceRealpathAttemptCount() override;
-  virtual void IncreaseSourceRealpathCompatibleCount() override;
+  void IncreaseSourceRealpathAttemptCount(uint32_t count);
+  void IncreaseSourceRealpathCompatibleCount(uint32_t count);
 
   StatsDuration &GetCreateTime() { return m_create_time; }
   StatsSuccessFail &GetExpressionStats() { return m_expr_eval; }
diff --git a/lldb/include/lldb/Utility/FileSpecList.h b/lldb/include/lldb/Utility/FileSpecList.h
index b26e2ed05dc18d..d091a9246e0827 100644
--- a/lldb/include/lldb/Utility/FileSpecList.h
+++ b/lldb/include/lldb/Utility/FileSpecList.h
@@ -71,9 +71,9 @@ class SupportFileList {
   /// \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 RealpathPrefixes *realpath_prefixes = nullptr) 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
index d50fbb663eacad..b45dee33dd3be3 100644
--- a/lldb/include/lldb/Utility/RealpathPrefixes.h
+++ b/lldb/include/lldb/Utility/RealpathPrefixes.h
@@ -21,21 +21,36 @@ namespace lldb_private {
 
 class RealpathPrefixes {
 public:
-  // Prefixes are obtained from FileSpecList, through FileSpec::GetPath(), which
-  // ensures that the paths are normalized. For example:
-  // "./foo/.." -> ""
-  // "./foo/../bar" -> "bar"
-  explicit RealpathPrefixes(const FileSpecList &file_spec_list);
+  /// \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());
 
-  // Sets an optional filesystem to use for realpath'ing. If not set, the real
-  // filesystem will be used.
-  void SetFileSystem(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs);
+  std::optional<FileSpec> ResolveSymlinks(const FileSpec &file_spec);
 
-  // Sets an optional Target instance to gather statistics.
-  void SetTarget(const lldb::TargetSP& target) { m_target = target; }
-  lldb::TargetSP GetTarget() const { return m_target.lock(); }
-
-  std::optional<FileSpec> ResolveSymlinks(const FileSpec &file_spec) const;
+  // 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
@@ -51,14 +66,10 @@ class RealpathPrefixes {
 
   // The optional Target instance to gather statistics.
   lldb::TargetWP m_target;
-};
-
-class RealpathPrefixesStats {
-public:
-  virtual ~RealpathPrefixesStats() = default;
 
-  virtual void IncreaseSourceRealpathAttemptCount() = 0;
-  virtual void IncreaseSourceRealpathCompatibleCount() = 0;
+  // 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
diff --git a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
index 315e017350d80b..508754082cae8a 100644
--- a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
+++ b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp
@@ -291,8 +291,8 @@ 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();
 
-  RealpathPrefixes realpath_prefixes =
-      GetBreakpoint()->GetTarget().GetSourceRealpathPrefixes();
+  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++) {
@@ -304,6 +304,12 @@ Searcher::CallbackReturn BreakpointResolverFileLine::SearchCallback(
     }
   }
 
+  // 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 f60745cb2e38e4..db8f8ce6bcbc92 100644
--- a/lldb/source/Symbol/CompileUnit.cpp
+++ b/lldb/source/Symbol/CompileUnit.cpp
@@ -215,7 +215,7 @@ VariableListSP CompileUnit::GetVariableList(bool can_create) {
 
 std::vector<uint32_t>
 FindFileIndexes(const SupportFileList &files, const FileSpec &file,
-                const RealpathPrefixes *realpath_prefixes = nullptr) {
+                RealpathPrefixes *realpath_prefixes = nullptr) {
   std::vector<uint32_t> result;
   uint32_t idx = -1;
   while ((idx = files.FindCompatibleIndex(idx + 1, file, realpath_prefixes)) !=
@@ -249,7 +249,7 @@ 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,
-    const RealpathPrefixes *realpath_prefixes) {
+    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();
diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp
index 894c247571ff90..390e04cebf6be6 100644
--- a/lldb/source/Target/Statistics.cpp
+++ b/lldb/source/Target/Statistics.cpp
@@ -224,12 +224,12 @@ void TargetStats::IncreaseSourceMapDeduceCount() {
   ++m_source_map_deduce_count;
 }
 
-void TargetStats::IncreaseSourceRealpathAttemptCount() {
-  ++m_source_realpath_attempt_count;
+void TargetStats::IncreaseSourceRealpathAttemptCount(uint32_t count) {
+  m_source_realpath_attempt_count += count;
 }
 
-void TargetStats::IncreaseSourceRealpathCompatibleCount() {
-  ++m_source_realpath_compatible_count;
+void TargetStats::IncreaseSourceRealpathCompatibleCount(uint32_t count) {
+  m_source_realpath_compatible_count += count;
 }
 
 bool DebuggerStats::g_collecting_stats = false;
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 52cc682ae6b88e..5a5d689e03fbc0 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -4359,9 +4359,7 @@ InlineStrategy TargetProperties::GetInlineStrategy() const {
 // this because we want the FileSpecList to normalize the file paths for us.
 RealpathPrefixes TargetProperties::GetSourceRealpathPrefixes() const {
   const uint32_t idx = ePropertySourceRealpathPrefixes;
-  auto prefixes = RealpathPrefixes(GetPropertyAtIndexAs<FileSpecList>(idx, {}));
-  prefixes.SetTarget(this->m_target->shared_from_this());
-  return prefixes;
+  return RealpathPrefixes(GetPropertyAtIndexAs<FileSpecList>(idx, {}));
 }
 
 llvm::StringRef TargetProperties::GetArg0() const {
diff --git a/lldb/source/Utility/FileSpecList.cpp b/lldb/source/Utility/FileSpecList.cpp
index f71a0b75b5b44e..5852367f77827f 100644
--- a/lldb/source/Utility/FileSpecList.cpp
+++ b/lldb/source/Utility/FileSpecList.cpp
@@ -165,7 +165,7 @@ IsCompatibleResult IsCompatible(const FileSpec &curr_file,
 
 size_t SupportFileList::FindCompatibleIndex(
     size_t start_idx, const FileSpec &file_spec,
-    const RealpathPrefixes *realpath_prefixes) const {
+    RealpathPrefixes *realpath_prefixes) const {
   const size_t num_files = m_files.size();
   if (start_idx >= num_files)
     return UINT32_MAX;
@@ -180,10 +180,10 @@ size_t SupportFileList::FindCompatibleIndex(
     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)) {
+        if (IsCompatible(*resolved_curr_file, file_spec) ==
+            IsCompatibleResult::kBothDirectoryAndFileMatch) {
           // Stats and logging.
-          if (lldb::TargetSP target = realpath_prefixes->GetTarget())
-            target->GetStatistics().IncreaseSourceRealpathCompatibleCount();
+          realpath_prefixes->IncreaseSourceRealpathCompatibleCount();
           Log *log = GetLog(LLDBLog::Source);
           LLDB_LOGF(log,
                     "Realpath'ed support file %s is compatible to input file",
diff --git a/lldb/source/Utility/RealpathPrefixes.cpp b/lldb/source/Utility/RealpathPrefixes.cpp
index c970e67cf88587..14c81ee6a1f571 100644
--- a/lldb/source/Utility/RealpathPrefixes.cpp
+++ b/lldb/source/Utility/RealpathPrefixes.cpp
@@ -8,8 +8,6 @@
 
 #include "lldb/Utility/RealpathPrefixes.h"
 
-#include "lldb/Target/Statistics.h"
-#include "lldb/Target/Target.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/FileSpecList.h"
 #include "lldb/Utility/LLDBLog.h"
@@ -17,34 +15,40 @@
 
 using namespace lldb_private;
 
-RealpathPrefixes::RealpathPrefixes(const FileSpecList &file_spec_list)
-    : m_fs(llvm::vfs::getRealFileSystem()) {
+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());
   }
 }
 
-void RealpathPrefixes::SetFileSystem(
-    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
-  m_fs = fs;
-}
-
 std::optional<FileSpec>
-RealpathPrefixes::ResolveSymlinks(const FileSpec &file_spec) const {
+RealpathPrefixes::ResolveSymlinks(const FileSpec &file_spec) {
   if (m_prefixes.empty())
     return std::nullopt;
 
-  auto is_prefix = [](llvm::StringRef a, llvm::StringRef b,
-                      bool case_sensitive) -> bool {
-    return case_sensitive ? a.consume_front(b) : a.consume_front_insensitive(b);
+  // 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_prefix(file_spec_path, prefix, file_spec.IsCaseSensitive())) {
+    if (is_path_prefix(file_spec_path, prefix, file_spec.IsCaseSensitive(),
+                       file_spec.GetPathStyle())) {
       // Stats and logging.
-      if (lldb::TargetSP target = m_target.lock())
-        target->GetStatistics().IncreaseSourceRealpathAttemptCount();
+      IncreaseSourceRealpathAttemptCount();
       Log *log = GetLog(LLDBLog::Source);
       LLDB_LOGF(log, "Realpath'ing support file %s", file_spec_path.c_str());
 
diff --git a/lldb/unittests/Utility/FileSpecListTest.cpp b/lldb/unittests/Utility/FileSpecListTest.cpp
index 06efdd4a70458c..d5ad8d276a66e9 100644
--- a/lldb/unittests/Utility/FileSpecListTest.cpp
+++ b/lldb/unittests/Utility/FileSpecListTest.cpp
@@ -136,12 +136,11 @@ TEST(SupportFileListTest, SymlinkedAbsolutePaths) {
       FileSpec("/symlink_dir/foo.h"), FileSpec("/real_dir/foo.h")));
 
   // Prepare RealpathPrefixes
-  FileSpecList temp;
-  temp.EmplaceBack("/symlink_dir");
-  RealpathPrefixes prefixes(std::move(temp));
-  prefixes.SetFileSystem(fs);
+  FileSpecList file_spec_list;
+  file_spec_list.EmplaceBack("/symlink_dir");
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
-  // Prepare FileSpecList
+  // Prepare support file list
   SupportFileList support_file_list;
   support_file_list.EmplaceBack(FileSpec("/symlink_dir/foo.h"));
 
@@ -161,12 +160,11 @@ TEST(SupportFileListTest, SymlinkedRelativePaths) {
       FileSpec("symlink_dir/foo.h"), FileSpec("real_dir/foo.h")));
 
   // Prepare RealpathPrefixes
-  FileSpecList temp;
-  temp.EmplaceBack("symlink_dir");
-  RealpathPrefixes prefixes(std::move(temp));
-  prefixes.SetFileSystem(fs);
+  FileSpecList file_spec_list;
+  file_spec_list.EmplaceBack("symlink_dir");
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
-  // Prepare FileSpecList
+  // Prepare support file list
   SupportFileList support_file_list;
   support_file_list.EmplaceBack(FileSpec("symlink_dir/foo.h"));
 
@@ -176,6 +174,30 @@ TEST(SupportFileListTest, SymlinkedRelativePaths) {
   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 the two 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 matching prefix is set.
 // However, the breakpoint is set with a partial path.
@@ -186,12 +208,11 @@ TEST(SupportFileListTest, PartialBreakpointPath) {
       FileSpec("symlink_dir/foo.h"), FileSpec("/real_dir/foo.h")));
 
   // Prepare RealpathPrefixes
-  FileSpecList temp;
-  temp.EmplaceBack("symlink_dir");
-  RealpathPrefixes prefixes(std::move(temp));
-  prefixes.SetFileSystem(fs);
+  FileSpecList file_spec_list;
+  file_spec_list.EmplaceBack("symlink_dir");
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
-  // Prepare FileSpecList
+  // Prepare support file list
   SupportFileList support_file_list;
   support_file_list.EmplaceBack(FileSpec("symlink_dir/foo.h"));
 
@@ -211,12 +232,11 @@ TEST(SupportFileListTest, DifferentBasename) {
       FileSpec("/symlink_dir/foo.h"), FileSpec("/real_dir/bar.h")));
 
   // Prepare RealpathPrefixes
-  FileSpecList temp;
-  temp.EmplaceBack("/symlink_dir");
-  RealpathPrefixes prefixes(std::move(temp));
-  prefixes.SetFileSystem(fs);
+  FileSpecList file_spec_list;
+  file_spec_list.EmplaceBack("/symlink_dir");
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
-  // Prepare FileSpecList
+  // Prepare support file list
   SupportFileList support_file_list;
   support_file_list.EmplaceBack(FileSpec("/symlink_dir/foo.h"));
 
@@ -230,7 +250,7 @@ TEST(SupportFileListTest, DifferentBasename) {
 // The support file and the breakpoint file are different.
 // Should find the two incompatible.
 TEST(SupportFileListTest, NoPrefixes) {
-  // Prepare FileSpecList
+  // Prepare support file list
   SupportFileList support_file_list;
   support_file_list.EmplaceBack(FileSpec("/real_dir/bar.h"));
 
@@ -244,7 +264,7 @@ TEST(SupportFileListTest, NoPrefixes) {
 // The support file and the breakpoint file are the same.
 // Should find the two compatible.
 TEST(SupportFileListTest, SameFile) {
-  // Prepare FileSpecList
+  // Prepare support file list
   SupportFileList support_file_list;
   support_file_list.EmplaceBack(FileSpec("/real_dir/foo.h"));
 
diff --git a/lldb/unittests/Utility/RealpathPrefixesTest.cpp b/lldb/unittests/Utility/RealpathPrefixesTest.cpp
index c6896c23b437da..872ddf1fd223aa 100644
--- a/lldb/unittests/Utility/RealpathPrefixesTest.cpp
+++ b/lldb/unittests/Utility/RealpathPrefixesTest.cpp
@@ -24,8 +24,7 @@ TEST(RealpathPrefixesTest, MatchingAbsolutePrefix) {
   // Prepare RealpathPrefixes
   FileSpecList file_spec_list;
   file_spec_list.EmplaceBack("/dir1");
-  RealpathPrefixes prefixes(std::move(file_spec_list));
-  prefixes.SetFileSystem(fs);
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
   // Test
   std::optional<FileSpec> ret =
@@ -42,8 +41,7 @@ TEST(RealpathPrefixesTest, MatchingRelativePrefix) {
   // Prepare RealpathPrefixes
   FileSpecList file_spec_list;
   file_spec_list.EmplaceBack("dir1");
-  RealpathPrefixes prefixes(std::move(file_spec_list));
-  prefixes.SetFileSystem(fs);
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
   // Test
   std::optional<FileSpec> ret =
@@ -62,8 +60,7 @@ TEST(RealpathPrefixesTest, WindowsAndCaseInsensitive) {
   // Prepare RealpathPrefixes
   FileSpecList file_spec_list;
   file_spec_list.EmplaceBack(FileSpec("f:\\dir1", FileSpec::Style::windows));
-  RealpathPrefixes prefixes(std::move(file_spec_list));
-  prefixes.SetFileSystem(fs);
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
   // Test
   std::optional<FileSpec> ret = prefixes.ResolveSymlinks(
@@ -83,8 +80,7 @@ TEST(RealpathPrefixesTest, MatchingAndMismatchingPrefix) {
   file_spec_list.EmplaceBack("/fake/path1");
   file_spec_list.EmplaceBack("/dir1"); // Matching prefix
   file_spec_list.EmplaceBack("/fake/path2");
-  RealpathPrefixes prefixes(std::move(file_spec_list));
-  prefixes.SetFileSystem(fs);
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
   // Test
   std::optional<FileSpec> ret =
@@ -101,8 +97,7 @@ TEST(RealpathPrefixesTest, ComplexPrefixes) {
   // Prepare RealpathPrefixes
   FileSpecList file_spec_list;
   file_spec_list.EmplaceBack("./dir1/foo/../bar/.."); // Equivalent to "/dir1"
-  RealpathPrefixes prefixes(std::move(file_spec_list));
-  prefixes.SetFileSystem(fs);
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
   // Test
   std::optional<FileSpec> ret =
@@ -119,8 +114,7 @@ TEST(RealpathPrefixesTest, MismatchingPrefixes) {
   // Prepare RealpathPrefixes
   FileSpecList file_spec_list;
   file_spec_list.EmplaceBack("/dir3");
-  RealpathPrefixes prefixes(std::move(file_spec_list));
-  prefixes.SetFileSystem(fs);
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
   // Test
   std::optional<FileSpec> ret =
@@ -137,8 +131,7 @@ TEST(RealpathPrefixesTest, Realpath) {
   // Prepare RealpathPrefixes
   FileSpecList file_spec_list;
   file_spec_list.EmplaceBack("/symlink_dir");
-  RealpathPrefixes prefixes(std::move(file_spec_list));
-  prefixes.SetFileSystem(fs);
+  RealpathPrefixes prefixes(file_spec_list, fs);
 
   // Test
   std::optional<FileSpec> ret =

>From 03c73f45d70387052dba8a5c523c9ee7544f4187 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 9 Aug 2024 15:52:41 -0700
Subject: [PATCH 7/9] Add unit test for realpath + source-map

---
 .../Makefile                                  |   3 +
 .../TestBreakpoint.py                         | 134 ++++++++++++++++++
 .../main.c                                    |  10 ++
 .../real/bar.h                                |   3 +
 .../real/foo.h                                |   3 +
 .../real/qux.h                                |   3 +
 .../symlink1/bar.c                            |   1 +
 .../symlink1/bar.h                            |   1 +
 .../symlink1/foo.h                            |   1 +
 .../symlink2                                  |   1 +
 .../to-be-mapped/README.md                    |   1 +
 11 files changed, 161 insertions(+)
 create mode 100644 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/Makefile
 create mode 100644 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/TestBreakpoint.py
 create mode 100644 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/main.c
 create mode 100644 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/bar.h
 create mode 100644 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/foo.h
 create mode 100644 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/real/qux.h
 create mode 120000 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/bar.c
 create mode 120000 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/bar.h
 create mode 120000 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/foo.h
 create mode 120000 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink2
 create mode 100644 lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/to-be-mapped/README.md

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..aaa90196c93245
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/TestBreakpoint.py
@@ -0,0 +1,134 @@
+"""
+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)
+
+        # Retrieve the associated command interpreter from our debugger.
+        ci = self.dbg.GetCommandInterpreter()
+        self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
+        return ci
+
+    @skipIf(oslist=["windows"])
+    def test_file_line_breakpoint_realpath(self):
+        """Test file/line breakpoint where support files have symlinks."""
+        ci = self.buildAndCreateTarget()
+
+        file_path = self.getBuildArtifact("a.out")
+        print("DEBUG", file_path)
+
+        res = lldb.SBCommandReturnObject()
+        # ci.HandleCommand(f"b main.c:{self.line_in_main}", res)
+        # print("DEBUG OUT", res.GetOutput())
+        # print("DEBUG ERR", res.GetError())
+
+        cwd = os.getcwd()
+        print("DEBUG CWD", cwd)
+
+        ######################################################################
+        # 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..6ed2a201ecbea4
--- /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/bar.c b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/bar.c
new file mode 120000
index 00000000000000..5c1cf5d6beaa10
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/bar.c
@@ -0,0 +1 @@
+../real/bar.c
\ No newline at end of file
diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/bar.h b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/bar.h
new file mode 120000
index 00000000000000..d6d045ca5d8416
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/breakpoint_with_realpath_and_source_map/symlink1/bar.h
@@ -0,0 +1 @@
+../real/bar.h
\ No newline at end of file
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.

>From 594122218ff713c208b6151c02a2c044e2ad9389 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 9 Aug 2024 16:04:19 -0700
Subject: [PATCH 8/9] Add test case for whole directory name matching and root
 directory as a special case

---
 lldb/unittests/Utility/FileSpecListTest.cpp | 66 ++++++++++++++++++---
 1 file changed, 57 insertions(+), 9 deletions(-)

diff --git a/lldb/unittests/Utility/FileSpecListTest.cpp b/lldb/unittests/Utility/FileSpecListTest.cpp
index d5ad8d276a66e9..4c04f434261301 100644
--- a/lldb/unittests/Utility/FileSpecListTest.cpp
+++ b/lldb/unittests/Utility/FileSpecListTest.cpp
@@ -127,9 +127,9 @@ TEST(SupportFileListTest, RelativePathMatchesWindows) {
 }
 
 // Support file is a symlink to the breakpoint file.
-// A matching prefix is set.
-// Should find the two compatible.
 // Absolute paths are used.
+// A matching prefix is set.
+// Should find it compatible.
 TEST(SupportFileListTest, SymlinkedAbsolutePaths) {
   // Prepare FS
   llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
@@ -151,9 +151,33 @@ TEST(SupportFileListTest, SymlinkedAbsolutePaths) {
 }
 
 // Support file is a symlink to the breakpoint file.
-// A matching prefix is set.
-// Should find the two compatible.
+// 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(
@@ -177,7 +201,7 @@ TEST(SupportFileListTest, SymlinkedRelativePaths) {
 // Support file is a symlink to the breakpoint file.
 // A matching prefix is set.
 // Input file only match basename and not directory.
-// Should find the two incompatible.
+// Should find it incompatible.
 TEST(SupportFileListTest, RealpathOnlyMatchFileName) {
   // Prepare FS
   llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
@@ -198,10 +222,34 @@ TEST(SupportFileListTest, RealpathOnlyMatchFileName) {
   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 the two compatible.
+// Should find it compatible.
 TEST(SupportFileListTest, PartialBreakpointPath) {
   // Prepare FS
   llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
@@ -225,7 +273,7 @@ TEST(SupportFileListTest, PartialBreakpointPath) {
 // Support file is a symlink to the breakpoint file.
 // A matching prefix is set.
 // However, the basename is different between the symlink and its target.
-// Should find the two incompatible.
+// Should find it incompatible.
 TEST(SupportFileListTest, DifferentBasename) {
   // Prepare FS
   llvm::IntrusiveRefCntPtr<MockSymlinkFileSystem> fs(new MockSymlinkFileSystem(
@@ -248,7 +296,7 @@ TEST(SupportFileListTest, DifferentBasename) {
 
 // No prefixes are configured.
 // The support file and the breakpoint file are different.
-// Should find the two incompatible.
+// Should find it incompatible.
 TEST(SupportFileListTest, NoPrefixes) {
   // Prepare support file list
   SupportFileList support_file_list;
@@ -262,7 +310,7 @@ TEST(SupportFileListTest, NoPrefixes) {
 
 // No prefixes are configured.
 // The support file and the breakpoint file are the same.
-// Should find the two compatible.
+// Should find it compatible.
 TEST(SupportFileListTest, SameFile) {
   // Prepare support file list
   SupportFileList support_file_list;

>From cf256d3fbacc25391b1a5f3fb21daed738cc5f20 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 9 Aug 2024 16:09:37 -0700
Subject: [PATCH 9/9] Formatting

---
 .../TestBreakpoint.py                         | 69 +++++++++++++++----
 .../main.c                                    |  8 +--
 2 files changed, 58 insertions(+), 19 deletions(-)

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
index aaa90196c93245..a2dc01a9a4f665 100644
--- 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
@@ -2,7 +2,6 @@
 Test lldb breakpoint with symlinks/realpath and source-map.
 """
 
-
 import lldb
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
@@ -55,24 +54,36 @@ def test_file_line_breakpoint_realpath(self):
 
         ######################################################################
         # 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
+            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
+            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
+            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`
@@ -80,18 +91,26 @@ def test_file_line_breakpoint_realpath(self):
         # - 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,
+            "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
+            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`.
@@ -100,18 +119,26 @@ def test_file_line_breakpoint_realpath(self):
         # - 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,
+            "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
+            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`.
@@ -120,15 +147,27 @@ def test_file_line_breakpoint_realpath(self):
         # - 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,
+            "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,
+            "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
+            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
index 6ed2a201ecbea4..716e7a91715f9b 100644
--- 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
@@ -2,9 +2,9 @@
 #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
+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.
 }



More information about the lldb-commits mailing list