1 //===-- FileSpec.cpp --------------------------------------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "lldb/Utility/FileSpec.h"
11 #include "lldb/Utility/RegularExpression.h"
12 #include "lldb/Utility/Stream.h"
13 #include "lldb/Utility/TildeExpressionResolver.h"
14 
15 #include "llvm/ADT/SmallString.h"
16 #include "llvm/ADT/SmallVector.h"
17 #include "llvm/ADT/StringRef.h"
18 #include "llvm/ADT/Triple.h"
19 #include "llvm/ADT/Twine.h"
20 #include "llvm/Support/ErrorOr.h"
21 #include "llvm/Support/FileSystem.h"
22 #include "llvm/Support/Program.h"
23 #include "llvm/Support/raw_ostream.h"
24 
25 #include <algorithm>    // for replace, min, unique
26 #include <system_error> // for error_code
27 #include <vector>       // for vector
28 
29 #include <assert.h> // for assert
30 #include <stdio.h>  // for size_t, NULL, snpr...
31 #include <string.h> // for strcmp
32 
33 using namespace lldb;
34 using namespace lldb_private;
35 
36 namespace {
37 
38 static constexpr FileSpec::Style GetNativeStyle() {
39 #if defined(_WIN32)
40   return FileSpec::Style::windows;
41 #else
42   return FileSpec::Style::posix;
43 #endif
44 }
45 
46 bool PathStyleIsPosix(FileSpec::Style style) {
47   return (style == FileSpec::Style::posix ||
48           (style == FileSpec::Style::native &&
49            GetNativeStyle() == FileSpec::Style::posix));
50 }
51 
52 const char *GetPathSeparators(FileSpec::Style style) {
53   return llvm::sys::path::get_separator(style).data();
54 }
55 
56 char GetPreferredPathSeparator(FileSpec::Style style) {
57   return GetPathSeparators(style)[0];
58 }
59 
60 bool IsPathSeparator(char value, FileSpec::Style style) {
61   return value == '/' || (!PathStyleIsPosix(style) && value == '\\');
62 }
63 
64 void Denormalize(llvm::SmallVectorImpl<char> &path, FileSpec::Style style) {
65   if (PathStyleIsPosix(style))
66     return;
67 
68   std::replace(path.begin(), path.end(), '/', '\\');
69 }
70 
71 } // end anonymous namespace
72 
73 void FileSpec::Resolve(llvm::SmallVectorImpl<char> &path) {
74   if (path.empty())
75     return;
76 
77   llvm::SmallString<32> Source(path.begin(), path.end());
78   StandardTildeExpressionResolver Resolver;
79   Resolver.ResolveFullPath(Source, path);
80 
81   // Save a copy of the original path that's passed in
82   llvm::SmallString<128> original_path(path.begin(), path.end());
83 
84   llvm::sys::fs::make_absolute(path);
85   if (!llvm::sys::fs::exists(path)) {
86     path.clear();
87     path.append(original_path.begin(), original_path.end());
88   }
89 }
90 
91 FileSpec::FileSpec() : m_style(GetNativeStyle()) {}
92 
93 //------------------------------------------------------------------
94 // Default constructor that can take an optional full path to a file on disk.
95 //------------------------------------------------------------------
96 FileSpec::FileSpec(llvm::StringRef path, bool resolve_path, Style style)
97     : m_style(style) {
98   SetFile(path, resolve_path, style);
99 }
100 
101 FileSpec::FileSpec(llvm::StringRef path, bool resolve_path,
102                    const llvm::Triple &Triple)
103     : FileSpec{path, resolve_path,
104                Triple.isOSWindows() ? Style::windows : Style::posix} {}
105 
106 //------------------------------------------------------------------
107 // Copy constructor
108 //------------------------------------------------------------------
109 FileSpec::FileSpec(const FileSpec &rhs)
110     : m_directory(rhs.m_directory), m_filename(rhs.m_filename),
111       m_is_resolved(rhs.m_is_resolved), m_style(rhs.m_style) {}
112 
113 //------------------------------------------------------------------
114 // Copy constructor
115 //------------------------------------------------------------------
116 FileSpec::FileSpec(const FileSpec *rhs) : m_directory(), m_filename() {
117   if (rhs)
118     *this = *rhs;
119 }
120 
121 //------------------------------------------------------------------
122 // Virtual destructor in case anyone inherits from this class.
123 //------------------------------------------------------------------
124 FileSpec::~FileSpec() {}
125 
126 namespace {
127 //------------------------------------------------------------------
128 /// Safely get a character at the specified index.
129 ///
130 /// @param[in] path
131 ///     A full, partial, or relative path to a file.
132 ///
133 /// @param[in] i
134 ///     An index into path which may or may not be valid.
135 ///
136 /// @return
137 ///   The character at index \a i if the index is valid, or 0 if
138 ///   the index is not valid.
139 //------------------------------------------------------------------
140 inline char safeCharAtIndex(const llvm::StringRef &path, size_t i) {
141   if (i < path.size())
142     return path[i];
143   return 0;
144 }
145 
146 //------------------------------------------------------------------
147 /// Check if a path needs to be normalized.
148 ///
149 /// Check if a path needs to be normalized. We currently consider a
150 /// path to need normalization if any of the following are true
151 ///  - path contains "/./"
152 ///  - path contains "/../"
153 ///  - path contains "//"
154 ///  - path ends with "/"
155 /// Paths that start with "./" or with "../" are not considered to
156 /// need normalization since we aren't trying to resolve the path,
157 /// we are just trying to remove redundant things from the path.
158 ///
159 /// @param[in] path
160 ///     A full, partial, or relative path to a file.
161 ///
162 /// @return
163 ///   Returns \b true if the path needs to be normalized.
164 //------------------------------------------------------------------
165 bool needsNormalization(const llvm::StringRef &path) {
166   if (path.empty())
167     return false;
168   // We strip off leading "." values so these paths need to be normalized
169   if (path[0] == '.')
170     return true;
171   for (auto i = path.find_first_of("\\/"); i != llvm::StringRef::npos;
172        i = path.find_first_of("\\/", i + 1)) {
173     const auto next = safeCharAtIndex(path, i+1);
174     switch (next) {
175       case 0:
176         // path separator char at the end of the string which should be
177         // stripped unless it is the one and only character
178         return i > 0;
179       case '/':
180       case '\\':
181         // two path separator chars in the middle of a path needs to be
182         // normalized
183         if (i > 0)
184           return true;
185         ++i;
186         break;
187 
188       case '.': {
189           const auto next_next = safeCharAtIndex(path, i+2);
190           switch (next_next) {
191             default: break;
192             case 0: return true; // ends with "/."
193             case '/':
194             case '\\':
195               return true; // contains "/./"
196             case '.': {
197               const auto next_next_next = safeCharAtIndex(path, i+3);
198               switch (next_next_next) {
199                 default: break;
200                 case 0: return true; // ends with "/.."
201                 case '/':
202                 case '\\':
203                   return true; // contains "/../"
204               }
205               break;
206             }
207           }
208         }
209         break;
210 
211       default:
212         break;
213     }
214   }
215   return false;
216 }
217 
218 
219 }
220 //------------------------------------------------------------------
221 // Assignment operator.
222 //------------------------------------------------------------------
223 const FileSpec &FileSpec::operator=(const FileSpec &rhs) {
224   if (this != &rhs) {
225     m_directory = rhs.m_directory;
226     m_filename = rhs.m_filename;
227     m_is_resolved = rhs.m_is_resolved;
228     m_style = rhs.m_style;
229   }
230   return *this;
231 }
232 
233 void FileSpec::SetFile(llvm::StringRef pathname, bool resolve) {
234   SetFile(pathname, resolve, m_style);
235 }
236 
237 //------------------------------------------------------------------
238 // Update the contents of this object with a new path. The path will be split
239 // up into a directory and filename and stored as uniqued string values for
240 // quick comparison and efficient memory usage.
241 //------------------------------------------------------------------
242 void FileSpec::SetFile(llvm::StringRef pathname, bool resolve, Style style) {
243   m_filename.Clear();
244   m_directory.Clear();
245   m_is_resolved = false;
246   m_style = (style == Style::native) ? GetNativeStyle() : style;
247 
248   if (pathname.empty())
249     return;
250 
251   llvm::SmallString<64> resolved(pathname);
252 
253   if (resolve) {
254     FileSpec::Resolve(resolved);
255     m_is_resolved = true;
256   }
257 
258   // Normalize the path by removing ".", ".." and other redundant components.
259   if (needsNormalization(resolved))
260     llvm::sys::path::remove_dots(resolved, true, m_style);
261 
262   // Normalize back slashes to forward slashes
263   if (m_style == Style::windows)
264     std::replace(resolved.begin(), resolved.end(), '\\', '/');
265 
266   if (resolved.empty()) {
267     // If we have no path after normalization set the path to the current
268     // directory. This matches what python does and also a few other path
269     // utilities.
270     m_filename.SetString(".");
271     return;
272   }
273 
274   // Split path into filename and directory. We rely on the underlying char
275   // pointer to be nullptr when the components are empty.
276   llvm::StringRef filename = llvm::sys::path::filename(resolved, m_style);
277   if(!filename.empty())
278     m_filename.SetString(filename);
279   llvm::StringRef directory = llvm::sys::path::parent_path(resolved, m_style);
280   if(!directory.empty())
281     m_directory.SetString(directory);
282 }
283 
284 void FileSpec::SetFile(llvm::StringRef path, bool resolve,
285                        const llvm::Triple &Triple) {
286   return SetFile(path, resolve,
287                  Triple.isOSWindows() ? Style::windows : Style::posix);
288 }
289 
290 //----------------------------------------------------------------------
291 // Convert to pointer operator. This allows code to check any FileSpec objects
292 // to see if they contain anything valid using code such as:
293 //
294 //  if (file_spec)
295 //  {}
296 //----------------------------------------------------------------------
297 FileSpec::operator bool() const { return m_filename || m_directory; }
298 
299 //----------------------------------------------------------------------
300 // Logical NOT operator. This allows code to check any FileSpec objects to see
301 // if they are invalid using code such as:
302 //
303 //  if (!file_spec)
304 //  {}
305 //----------------------------------------------------------------------
306 bool FileSpec::operator!() const { return !m_directory && !m_filename; }
307 
308 bool FileSpec::DirectoryEquals(const FileSpec &rhs) const {
309   const bool case_sensitive = IsCaseSensitive() || rhs.IsCaseSensitive();
310   return ConstString::Equals(m_directory, rhs.m_directory, case_sensitive);
311 }
312 
313 bool FileSpec::FileEquals(const FileSpec &rhs) const {
314   const bool case_sensitive = IsCaseSensitive() || rhs.IsCaseSensitive();
315   return ConstString::Equals(m_filename, rhs.m_filename, case_sensitive);
316 }
317 
318 //------------------------------------------------------------------
319 // Equal to operator
320 //------------------------------------------------------------------
321 bool FileSpec::operator==(const FileSpec &rhs) const {
322   if (!FileEquals(rhs))
323     return false;
324   if (DirectoryEquals(rhs))
325     return true;
326 
327   // TODO: determine if we want to keep this code in here.
328   // The code below was added to handle a case where we were trying to set a
329   // file and line breakpoint and one path was resolved, and the other not and
330   // the directory was in a mount point that resolved to a more complete path:
331   // "/tmp/a.c" == "/private/tmp/a.c". I might end up pulling this out...
332   if (IsResolved() && rhs.IsResolved()) {
333     // Both paths are resolved, no need to look further...
334     return false;
335   }
336 
337   FileSpec resolved_lhs(*this);
338 
339   // If "this" isn't resolved, resolve it
340   if (!IsResolved()) {
341     if (resolved_lhs.ResolvePath()) {
342       // This path wasn't resolved but now it is. Check if the resolved
343       // directory is the same as our unresolved directory, and if so, we can
344       // mark this object as resolved to avoid more future resolves
345       m_is_resolved = (m_directory == resolved_lhs.m_directory);
346     } else
347       return false;
348   }
349 
350   FileSpec resolved_rhs(rhs);
351   if (!rhs.IsResolved()) {
352     if (resolved_rhs.ResolvePath()) {
353       // rhs's path wasn't resolved but now it is. Check if the resolved
354       // directory is the same as rhs's unresolved directory, and if so, we can
355       // mark this object as resolved to avoid more future resolves
356       rhs.m_is_resolved = (rhs.m_directory == resolved_rhs.m_directory);
357     } else
358       return false;
359   }
360 
361   // If we reach this point in the code we were able to resolve both paths and
362   // since we only resolve the paths if the basenames are equal, then we can
363   // just check if both directories are equal...
364   return DirectoryEquals(rhs);
365 }
366 
367 //------------------------------------------------------------------
368 // Not equal to operator
369 //------------------------------------------------------------------
370 bool FileSpec::operator!=(const FileSpec &rhs) const { return !(*this == rhs); }
371 
372 //------------------------------------------------------------------
373 // Less than operator
374 //------------------------------------------------------------------
375 bool FileSpec::operator<(const FileSpec &rhs) const {
376   return FileSpec::Compare(*this, rhs, true) < 0;
377 }
378 
379 //------------------------------------------------------------------
380 // Dump a FileSpec object to a stream
381 //------------------------------------------------------------------
382 Stream &lldb_private::operator<<(Stream &s, const FileSpec &f) {
383   f.Dump(&s);
384   return s;
385 }
386 
387 //------------------------------------------------------------------
388 // Clear this object by releasing both the directory and filename string values
389 // and making them both the empty string.
390 //------------------------------------------------------------------
391 void FileSpec::Clear() {
392   m_directory.Clear();
393   m_filename.Clear();
394 }
395 
396 //------------------------------------------------------------------
397 // Compare two FileSpec objects. If "full" is true, then both the directory and
398 // the filename must match. If "full" is false, then the directory names for
399 // "a" and "b" are only compared if they are both non-empty. This allows a
400 // FileSpec object to only contain a filename and it can match FileSpec objects
401 // that have matching filenames with different paths.
402 //
403 // Return -1 if the "a" is less than "b", 0 if "a" is equal to "b" and "1" if
404 // "a" is greater than "b".
405 //------------------------------------------------------------------
406 int FileSpec::Compare(const FileSpec &a, const FileSpec &b, bool full) {
407   int result = 0;
408 
409   // case sensitivity of compare
410   const bool case_sensitive = a.IsCaseSensitive() || b.IsCaseSensitive();
411 
412   // If full is true, then we must compare both the directory and filename.
413 
414   // If full is false, then if either directory is empty, then we match on the
415   // basename only, and if both directories have valid values, we still do a
416   // full compare. This allows for matching when we just have a filename in one
417   // of the FileSpec objects.
418 
419   if (full || (a.m_directory && b.m_directory)) {
420     result = ConstString::Compare(a.m_directory, b.m_directory, case_sensitive);
421     if (result)
422       return result;
423   }
424   return ConstString::Compare(a.m_filename, b.m_filename, case_sensitive);
425 }
426 
427 bool FileSpec::Equal(const FileSpec &a, const FileSpec &b, bool full) {
428   // case sensitivity of equality test
429   const bool case_sensitive = a.IsCaseSensitive() || b.IsCaseSensitive();
430 
431   const bool filenames_equal = ConstString::Equals(a.m_filename,
432                                                    b.m_filename,
433                                                    case_sensitive);
434 
435   if (!filenames_equal)
436     return false;
437 
438   if (!full && (a.GetDirectory().IsEmpty() || b.GetDirectory().IsEmpty()))
439     return filenames_equal;
440 
441   return a == b;
442 }
443 
444 //------------------------------------------------------------------
445 // Dump the object to the supplied stream. If the object contains a valid
446 // directory name, it will be displayed followed by a directory delimiter, and
447 // the filename.
448 //------------------------------------------------------------------
449 void FileSpec::Dump(Stream *s) const {
450   if (s) {
451     std::string path{GetPath(true)};
452     s->PutCString(path);
453     char path_separator = GetPreferredPathSeparator(m_style);
454     if (!m_filename && !path.empty() && path.back() != path_separator)
455       s->PutChar(path_separator);
456   }
457 }
458 
459 //------------------------------------------------------------------
460 // Returns true if the file exists.
461 //------------------------------------------------------------------
462 bool FileSpec::Exists() const { return llvm::sys::fs::exists(GetPath()); }
463 
464 bool FileSpec::Readable() const {
465   return GetPermissions() & llvm::sys::fs::perms::all_read;
466 }
467 
468 bool FileSpec::ResolveExecutableLocation() {
469   // CLEANUP: Use StringRef for string handling.
470   if (!m_directory) {
471     const char *file_cstr = m_filename.GetCString();
472     if (file_cstr) {
473       const std::string file_str(file_cstr);
474       llvm::ErrorOr<std::string> error_or_path =
475           llvm::sys::findProgramByName(file_str);
476       if (!error_or_path)
477         return false;
478       std::string path = error_or_path.get();
479       llvm::StringRef dir_ref = llvm::sys::path::parent_path(path);
480       if (!dir_ref.empty()) {
481         // FindProgramByName returns "." if it can't find the file.
482         if (strcmp(".", dir_ref.data()) == 0)
483           return false;
484 
485         m_directory.SetCString(dir_ref.data());
486         if (Exists())
487           return true;
488         else {
489           // If FindProgramByName found the file, it returns the directory +
490           // filename in its return results. We need to separate them.
491           FileSpec tmp_file(dir_ref.data(), false);
492           if (tmp_file.Exists()) {
493             m_directory = tmp_file.m_directory;
494             return true;
495           }
496         }
497       }
498     }
499   }
500 
501   return false;
502 }
503 
504 bool FileSpec::ResolvePath() {
505   if (m_is_resolved)
506     return true; // We have already resolved this path
507 
508   // SetFile(...) will set m_is_resolved correctly if it can resolve the path
509   SetFile(GetPath(false), true);
510   return m_is_resolved;
511 }
512 
513 uint64_t FileSpec::GetByteSize() const {
514   uint64_t Size = 0;
515   if (llvm::sys::fs::file_size(GetPath(), Size))
516     return 0;
517   return Size;
518 }
519 
520 FileSpec::Style FileSpec::GetPathStyle() const { return m_style; }
521 
522 uint32_t FileSpec::GetPermissions() const {
523   namespace fs = llvm::sys::fs;
524   fs::file_status st;
525   if (fs::status(GetPath(), st, false))
526     return fs::perms::perms_not_known;
527 
528   return st.permissions();
529 }
530 
531 //------------------------------------------------------------------
532 // Directory string get accessor.
533 //------------------------------------------------------------------
534 ConstString &FileSpec::GetDirectory() { return m_directory; }
535 
536 //------------------------------------------------------------------
537 // Directory string const get accessor.
538 //------------------------------------------------------------------
539 const ConstString &FileSpec::GetDirectory() const { return m_directory; }
540 
541 //------------------------------------------------------------------
542 // Filename string get accessor.
543 //------------------------------------------------------------------
544 ConstString &FileSpec::GetFilename() { return m_filename; }
545 
546 //------------------------------------------------------------------
547 // Filename string const get accessor.
548 //------------------------------------------------------------------
549 const ConstString &FileSpec::GetFilename() const { return m_filename; }
550 
551 //------------------------------------------------------------------
552 // Extract the directory and path into a fixed buffer. This is needed as the
553 // directory and path are stored in separate string values.
554 //------------------------------------------------------------------
555 size_t FileSpec::GetPath(char *path, size_t path_max_len,
556                          bool denormalize) const {
557   if (!path)
558     return 0;
559 
560   std::string result = GetPath(denormalize);
561   ::snprintf(path, path_max_len, "%s", result.c_str());
562   return std::min(path_max_len - 1, result.length());
563 }
564 
565 std::string FileSpec::GetPath(bool denormalize) const {
566   llvm::SmallString<64> result;
567   GetPath(result, denormalize);
568   return std::string(result.begin(), result.end());
569 }
570 
571 const char *FileSpec::GetCString(bool denormalize) const {
572   return ConstString{GetPath(denormalize)}.AsCString(NULL);
573 }
574 
575 void FileSpec::GetPath(llvm::SmallVectorImpl<char> &path,
576                        bool denormalize) const {
577   path.append(m_directory.GetStringRef().begin(),
578               m_directory.GetStringRef().end());
579   // Since the path was normalized and all paths use '/' when stored in these
580   // objects, we don't need to look for the actual syntax specific path
581   // separator, we just look for and insert '/'.
582   if (m_directory && m_filename && m_directory.GetStringRef().back() != '/' &&
583       m_filename.GetStringRef().back() != '/')
584     path.insert(path.end(), '/');
585   path.append(m_filename.GetStringRef().begin(),
586               m_filename.GetStringRef().end());
587   if (denormalize && !path.empty())
588     Denormalize(path, m_style);
589 }
590 
591 ConstString FileSpec::GetFileNameExtension() const {
592   return ConstString(
593       llvm::sys::path::extension(m_filename.GetStringRef(), m_style));
594 }
595 
596 ConstString FileSpec::GetFileNameStrippingExtension() const {
597   return ConstString(llvm::sys::path::stem(m_filename.GetStringRef(), m_style));
598 }
599 
600 //------------------------------------------------------------------
601 // Return the size in bytes that this object takes in memory. This returns the
602 // size in bytes of this object, not any shared string values it may refer to.
603 //------------------------------------------------------------------
604 size_t FileSpec::MemorySize() const {
605   return m_filename.MemorySize() + m_directory.MemorySize();
606 }
607 
608 void FileSpec::EnumerateDirectory(llvm::StringRef dir_path,
609                                   bool find_directories, bool find_files,
610                                   bool find_other,
611                                   EnumerateDirectoryCallbackType callback,
612                                   void *callback_baton) {
613   namespace fs = llvm::sys::fs;
614   std::error_code EC;
615   fs::recursive_directory_iterator Iter(dir_path, EC);
616   fs::recursive_directory_iterator End;
617   for (; Iter != End && !EC; Iter.increment(EC)) {
618     const auto &Item = *Iter;
619     llvm::ErrorOr<fs::basic_file_status> Status = Item.status();
620     if (!Status)
621       break;
622     if (!find_files && fs::is_regular_file(*Status))
623       continue;
624     if (!find_directories && fs::is_directory(*Status))
625       continue;
626     if (!find_other && fs::is_other(*Status))
627       continue;
628 
629     FileSpec Spec(Item.path(), false);
630     auto Result = callback(callback_baton, Status->type(), Spec);
631     if (Result == eEnumerateDirectoryResultQuit)
632       return;
633     if (Result == eEnumerateDirectoryResultNext) {
634       // Default behavior is to recurse.  Opt out if the callback doesn't want
635       // this behavior.
636       Iter.no_push();
637     }
638   }
639 }
640 
641 FileSpec
642 FileSpec::CopyByAppendingPathComponent(llvm::StringRef component) const {
643   FileSpec ret = *this;
644   ret.AppendPathComponent(component);
645   return ret;
646 }
647 
648 FileSpec FileSpec::CopyByRemovingLastPathComponent() const {
649   // CLEANUP: Use StringRef for string handling.
650   const bool resolve = false;
651   if (m_filename.IsEmpty() && m_directory.IsEmpty())
652     return FileSpec("", resolve);
653   if (m_directory.IsEmpty())
654     return FileSpec("", resolve);
655   if (m_filename.IsEmpty()) {
656     const char *dir_cstr = m_directory.GetCString();
657     const char *last_slash_ptr = ::strrchr(dir_cstr, '/');
658 
659     // check for obvious cases before doing the full thing
660     if (!last_slash_ptr)
661       return FileSpec("", resolve);
662     if (last_slash_ptr == dir_cstr)
663       return FileSpec("/", resolve);
664 
665     size_t last_slash_pos = last_slash_ptr - dir_cstr + 1;
666     ConstString new_path(dir_cstr, last_slash_pos);
667     return FileSpec(new_path.GetCString(), resolve);
668   } else
669     return FileSpec(m_directory.GetCString(), resolve);
670 }
671 
672 ConstString FileSpec::GetLastPathComponent() const {
673   // CLEANUP: Use StringRef for string handling.
674   if (m_filename)
675     return m_filename;
676   if (m_directory) {
677     const char *dir_cstr = m_directory.GetCString();
678     const char *last_slash_ptr = ::strrchr(dir_cstr, '/');
679     if (last_slash_ptr == NULL)
680       return m_directory;
681     if (last_slash_ptr == dir_cstr) {
682       if (last_slash_ptr[1] == 0)
683         return ConstString(last_slash_ptr);
684       else
685         return ConstString(last_slash_ptr + 1);
686     }
687     if (last_slash_ptr[1] != 0)
688       return ConstString(last_slash_ptr + 1);
689     const char *penultimate_slash_ptr = last_slash_ptr;
690     while (*penultimate_slash_ptr) {
691       --penultimate_slash_ptr;
692       if (penultimate_slash_ptr == dir_cstr)
693         break;
694       if (*penultimate_slash_ptr == '/')
695         break;
696     }
697     ConstString result(penultimate_slash_ptr + 1,
698                        last_slash_ptr - penultimate_slash_ptr);
699     return result;
700   }
701   return ConstString();
702 }
703 
704 static std::string
705 join_path_components(FileSpec::Style style,
706                      const std::vector<llvm::StringRef> components) {
707   std::string result;
708   for (size_t i = 0; i < components.size(); ++i) {
709     if (components[i].empty())
710       continue;
711     result += components[i];
712     if (i != components.size() - 1 &&
713         !IsPathSeparator(components[i].back(), style))
714       result += GetPreferredPathSeparator(style);
715   }
716 
717   return result;
718 }
719 
720 void FileSpec::PrependPathComponent(llvm::StringRef component) {
721   if (component.empty())
722     return;
723 
724   const bool resolve = false;
725   if (m_filename.IsEmpty() && m_directory.IsEmpty()) {
726     SetFile(component, resolve);
727     return;
728   }
729 
730   std::string result =
731       join_path_components(m_style, {component, m_directory.GetStringRef(),
732                                      m_filename.GetStringRef()});
733   SetFile(result, resolve);
734 }
735 
736 void FileSpec::PrependPathComponent(const FileSpec &new_path) {
737   return PrependPathComponent(new_path.GetPath(false));
738 }
739 
740 void FileSpec::AppendPathComponent(llvm::StringRef component) {
741   if (component.empty())
742     return;
743 
744   component = component.drop_while(
745       [this](char c) { return IsPathSeparator(c, m_style); });
746 
747   std::string result =
748       join_path_components(m_style, {m_directory.GetStringRef(),
749                                      m_filename.GetStringRef(), component});
750 
751   SetFile(result, false);
752 }
753 
754 void FileSpec::AppendPathComponent(const FileSpec &new_path) {
755   return AppendPathComponent(new_path.GetPath(false));
756 }
757 
758 bool FileSpec::RemoveLastPathComponent() {
759   llvm::SmallString<64> current_path;
760   GetPath(current_path, false);
761   if (llvm::sys::path::has_parent_path(current_path, m_style)) {
762     SetFile(llvm::sys::path::parent_path(current_path, m_style), false);
763     return true;
764   }
765   return false;
766 }
767 //------------------------------------------------------------------
768 /// Returns true if the filespec represents an implementation source
769 /// file (files with a ".c", ".cpp", ".m", ".mm" (many more)
770 /// extension).
771 ///
772 /// @return
773 ///     \b true if the filespec represents an implementation source
774 ///     file, \b false otherwise.
775 //------------------------------------------------------------------
776 bool FileSpec::IsSourceImplementationFile() const {
777   ConstString extension(GetFileNameExtension());
778   if (!extension)
779     return false;
780 
781   static RegularExpression g_source_file_regex(llvm::StringRef(
782       "^.([cC]|[mM]|[mM][mM]|[cC][pP][pP]|[cC]\\+\\+|[cC][xX][xX]|[cC][cC]|["
783       "cC][pP]|[sS]|[aA][sS][mM]|[fF]|[fF]77|[fF]90|[fF]95|[fF]03|[fF][oO]["
784       "rR]|[fF][tT][nN]|[fF][pP][pP]|[aA][dD][aA]|[aA][dD][bB]|[aA][dD][sS])"
785       "$"));
786   return g_source_file_regex.Execute(extension.GetStringRef());
787 }
788 
789 bool FileSpec::IsRelative() const {
790   return !IsAbsolute();
791 }
792 
793 bool FileSpec::IsAbsolute() const {
794   llvm::SmallString<64> current_path;
795   GetPath(current_path, false);
796 
797   // Early return if the path is empty.
798   if (current_path.empty())
799     return false;
800 
801   // We consider paths starting with ~ to be absolute.
802   if (current_path[0] == '~')
803     return true;
804 
805   return llvm::sys::path::is_absolute(current_path, m_style);
806 }
807 
808 void llvm::format_provider<FileSpec>::format(const FileSpec &F,
809                                              raw_ostream &Stream,
810                                              StringRef Style) {
811   assert(
812       (Style.empty() || Style.equals_lower("F") || Style.equals_lower("D")) &&
813       "Invalid FileSpec style!");
814 
815   StringRef dir = F.GetDirectory().GetStringRef();
816   StringRef file = F.GetFilename().GetStringRef();
817 
818   if (dir.empty() && file.empty()) {
819     Stream << "(empty)";
820     return;
821   }
822 
823   if (Style.equals_lower("F")) {
824     Stream << (file.empty() ? "(empty)" : file);
825     return;
826   }
827 
828   // Style is either D or empty, either way we need to print the directory.
829   if (!dir.empty()) {
830     // Directory is stored in normalized form, which might be different than
831     // preferred form.  In order to handle this, we need to cut off the
832     // filename, then denormalize, then write the entire denorm'ed directory.
833     llvm::SmallString<64> denormalized_dir = dir;
834     Denormalize(denormalized_dir, F.GetPathStyle());
835     Stream << denormalized_dir;
836     Stream << GetPreferredPathSeparator(F.GetPathStyle());
837   }
838 
839   if (Style.equals_lower("D")) {
840     // We only want to print the directory, so now just exit.
841     if (dir.empty())
842       Stream << "(empty)";
843     return;
844   }
845 
846   if (!file.empty())
847     Stream << file;
848 }
849