Cache the value for absolute path in FileSpec.

Checking if a path is absolute can be expensive and currently the result is not cached in the FileSpec object. This patch adds caching and also code to clear the cache if the file is modified.

Differential Revision: https://reviews.llvm.org/D130396

GitOrigin-RevId: dc0f452e85e572f1e824c1eb3cdaa060d326f0c3
diff --git a/include/lldb/Utility/FileSpec.h b/include/lldb/Utility/FileSpec.h
index a0b01fb..8492c93 100644
--- a/include/lldb/Utility/FileSpec.h
+++ b/include/lldb/Utility/FileSpec.h
@@ -416,10 +416,24 @@
   // Convenience method for setting the file without changing the style.
   void SetFile(llvm::StringRef path);
 
+  /// Called anytime m_directory or m_filename is changed to clear any cached
+  /// state in this object.
+  void PathWasModified() {
+    m_is_resolved = false;
+    m_absolute = Absolute::Calculate;
+  }
+
+  enum class Absolute : uint8_t {
+    Calculate,
+    Yes,
+    No
+  };
+
   // Member variables
   ConstString m_directory;            ///< The uniqued directory path
   ConstString m_filename;             ///< The uniqued filename path
   mutable bool m_is_resolved = false; ///< True if this path has been resolved.
+  mutable Absolute m_absolute = Absolute::Calculate; ///< Cache absoluteness.
   Style m_style; ///< The syntax that this path uses (e.g. Windows / Posix)
 };
 
diff --git a/source/Utility/FileSpec.cpp b/source/Utility/FileSpec.cpp
index 7646d33..48b9229 100644
--- a/source/Utility/FileSpec.cpp
+++ b/source/Utility/FileSpec.cpp
@@ -170,9 +170,7 @@
 // up into a directory and filename and stored as uniqued string values for
 // quick comparison and efficient memory usage.
 void FileSpec::SetFile(llvm::StringRef pathname, Style style) {
-  m_filename.Clear();
-  m_directory.Clear();
-  m_is_resolved = false;
+  Clear();
   m_style = (style == Style::native) ? GetNativeStyle() : style;
 
   if (pathname.empty())
@@ -259,6 +257,7 @@
 void FileSpec::Clear() {
   m_directory.Clear();
   m_filename.Clear();
+  PathWasModified();
 }
 
 // Compare two FileSpec objects. If "full" is true, then both the directory and
@@ -332,26 +331,32 @@
 
 void FileSpec::SetDirectory(ConstString directory) {
   m_directory = directory;
+  PathWasModified();
 }
 
 void FileSpec::SetDirectory(llvm::StringRef directory) {
   m_directory = ConstString(directory);
+  PathWasModified();
 }
 
 void FileSpec::SetFilename(ConstString filename) {
   m_filename = filename;
+  PathWasModified();
 }
 
 void FileSpec::SetFilename(llvm::StringRef filename) {
   m_filename = ConstString(filename);
+  PathWasModified();
 }
 
 void FileSpec::ClearFilename() {
   m_filename.Clear();
+  PathWasModified();
 }
 
 void FileSpec::ClearDirectory() {
   m_directory.Clear();
+  PathWasModified();
 }
 
 // Extract the directory and path into a fixed buffer. This is needed as the
@@ -488,18 +493,22 @@
 }
 
 bool FileSpec::IsAbsolute() const {
-llvm::SmallString<64> current_path;
-  GetPath(current_path, false);
+  // Check if we have cached if this path is absolute to avoid recalculating.
+  if (m_absolute != Absolute::Calculate)
+    return m_absolute == Absolute::Yes;
 
-  // Early return if the path is empty.
-  if (current_path.empty())
-    return false;
+  m_absolute = Absolute::No;
 
-  // We consider paths starting with ~ to be absolute.
-  if (current_path[0] == '~')
-    return true;
+  llvm::SmallString<64> path;
+  GetPath(path, false);
 
-  return llvm::sys::path::is_absolute(current_path, m_style);
+  if (!path.empty()) {
+    // We consider paths starting with ~ to be absolute.
+    if (path[0] == '~' || llvm::sys::path::is_absolute(path, m_style))
+      m_absolute = Absolute::Yes;
+  }
+
+  return m_absolute == Absolute::Yes;
 }
 
 void FileSpec::MakeAbsolute(const FileSpec &dir) {
diff --git a/unittests/Utility/FileSpecTest.cpp b/unittests/Utility/FileSpecTest.cpp
index 9260262..0249dd5 100644
--- a/unittests/Utility/FileSpecTest.cpp
+++ b/unittests/Utility/FileSpecTest.cpp
@@ -450,3 +450,29 @@
   EXPECT_FALSE(FileSpec(""));
   EXPECT_TRUE(FileSpec("/foo/bar"));
 }
+
+
+TEST(FileSpecTest, TestAbsoluteCaching) {
+  // Test that if we modify a path that we recalculate if a path is relative
+  // or absolute correctly. The test below calls set accessors and functions
+  // that change the path and verifies that the FileSpec::IsAbsolute() returns
+  // the correct value.
+  FileSpec file = PosixSpec("/tmp/a");
+  EXPECT_TRUE(file.IsAbsolute());
+  file.ClearDirectory();
+  EXPECT_FALSE(file.IsAbsolute());
+  file.SetDirectory("/tmp");
+  EXPECT_TRUE(file.IsAbsolute());
+  file.SetDirectory(".");
+  EXPECT_FALSE(file.IsAbsolute());
+  file.SetPath("/log.txt");
+  EXPECT_TRUE(file.IsAbsolute());
+  file.RemoveLastPathComponent();
+  EXPECT_TRUE(file.IsAbsolute());
+  file.ClearFilename();
+  EXPECT_FALSE(file.IsAbsolute());
+  file.AppendPathComponent("foo.txt");
+  EXPECT_FALSE(file.IsAbsolute());
+  file.PrependPathComponent("/tmp");
+  EXPECT_TRUE(file.IsAbsolute());
+}