aboutsummaryrefslogtreecommitdiff
path: root/source/Utility/FileSpec.cpp
blob: 35d22404b94804e2fd4622a14ba544fc1fd15fc9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
//===-- FileSpec.cpp --------------------------------------------*- 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
//
//===----------------------------------------------------------------------===//

#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/RegularExpression.h"
#include "lldb/Utility/Stream.h"

#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Triple.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/raw_ostream.h"

#include <algorithm>
#include <system_error>
#include <vector>

#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>

using namespace lldb;
using namespace lldb_private;

namespace {

static constexpr FileSpec::Style GetNativeStyle() {
#if defined(_WIN32)
  return FileSpec::Style::windows;
#else
  return FileSpec::Style::posix;
#endif
}

bool PathStyleIsPosix(FileSpec::Style style) {
  return (style == FileSpec::Style::posix ||
          (style == FileSpec::Style::native &&
           GetNativeStyle() == FileSpec::Style::posix));
}

const char *GetPathSeparators(FileSpec::Style style) {
  return llvm::sys::path::get_separator(style).data();
}

char GetPreferredPathSeparator(FileSpec::Style style) {
  return GetPathSeparators(style)[0];
}

void Denormalize(llvm::SmallVectorImpl<char> &path, FileSpec::Style style) {
  if (PathStyleIsPosix(style))
    return;

  std::replace(path.begin(), path.end(), '/', '\\');
}

} // end anonymous namespace

FileSpec::FileSpec() : m_style(GetNativeStyle()) {}

// Default constructor that can take an optional full path to a file on disk.
FileSpec::FileSpec(llvm::StringRef path, Style style) : m_style(style) {
  SetFile(path, style);
}

FileSpec::FileSpec(llvm::StringRef path, const llvm::Triple &Triple)
    : FileSpec{path, Triple.isOSWindows() ? Style::windows : Style::posix} {}

// Copy constructor
FileSpec::FileSpec(const FileSpec *rhs) : m_directory(), m_filename() {
  if (rhs)
    *this = *rhs;
}

// Virtual destructor in case anyone inherits from this class.
FileSpec::~FileSpec() {}

namespace {
/// Safely get a character at the specified index.
///
/// \param[in] path
///     A full, partial, or relative path to a file.
///
/// \param[in] i
///     An index into path which may or may not be valid.
///
/// \return
///   The character at index \a i if the index is valid, or 0 if
///   the index is not valid.
inline char safeCharAtIndex(const llvm::StringRef &path, size_t i) {
  if (i < path.size())
    return path[i];
  return 0;
}

/// Check if a path needs to be normalized.
///
/// Check if a path needs to be normalized. We currently consider a
/// path to need normalization if any of the following are true
///  - path contains "/./"
///  - path contains "/../"
///  - path contains "//"
///  - path ends with "/"
/// Paths that start with "./" or with "../" are not considered to
/// need normalization since we aren't trying to resolve the path,
/// we are just trying to remove redundant things from the path.
///
/// \param[in] path
///     A full, partial, or relative path to a file.
///
/// \return
///   Returns \b true if the path needs to be normalized.
bool needsNormalization(const llvm::StringRef &path) {
  if (path.empty())
    return false;
  // We strip off leading "." values so these paths need to be normalized
  if (path[0] == '.')
    return true;
  for (auto i = path.find_first_of("\\/"); i != llvm::StringRef::npos;
       i = path.find_first_of("\\/", i + 1)) {
    const auto next = safeCharAtIndex(path, i+1);
    switch (next) {
      case 0:
        // path separator char at the end of the string which should be
        // stripped unless it is the one and only character
        return i > 0;
      case '/':
      case '\\':
        // two path separator chars in the middle of a path needs to be
        // normalized
        if (i > 0)
          return true;
        ++i;
        break;

      case '.': {
          const auto next_next = safeCharAtIndex(path, i+2);
          switch (next_next) {
            default: break;
            case 0: return true; // ends with "/."
            case '/':
            case '\\':
              return true; // contains "/./"
            case '.': {
              const auto next_next_next = safeCharAtIndex(path, i+3);
              switch (next_next_next) {
                default: break;
                case 0: return true; // ends with "/.."
                case '/':
                case '\\':
                  return true; // contains "/../"
              }
              break;
            }
          }
        }
        break;

      default:
        break;
    }
  }
  return false;
}


}
// Assignment operator.
const FileSpec &FileSpec::operator=(const FileSpec &rhs) {
  if (this != &rhs) {
    m_directory = rhs.m_directory;
    m_filename = rhs.m_filename;
    m_is_resolved = rhs.m_is_resolved;
    m_style = rhs.m_style;
  }
  return *this;
}

void FileSpec::SetFile(llvm::StringRef pathname) { SetFile(pathname, m_style); }

// Update the contents of this object with a new path. The path will be split
// 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;
  m_style = (style == Style::native) ? GetNativeStyle() : style;

  if (pathname.empty())
    return;

  llvm::SmallString<128> resolved(pathname);

  // Normalize the path by removing ".", ".." and other redundant components.
  if (needsNormalization(resolved))
    llvm::sys::path::remove_dots(resolved, true, m_style);

  // Normalize back slashes to forward slashes
  if (m_style == Style::windows)
    std::replace(resolved.begin(), resolved.end(), '\\', '/');

  if (resolved.empty()) {
    // If we have no path after normalization set the path to the current
    // directory. This matches what python does and also a few other path
    // utilities.
    m_filename.SetString(".");
    return;
  }

  // Split path into filename and directory. We rely on the underlying char
  // pointer to be nullptr when the components are empty.
  llvm::StringRef filename = llvm::sys::path::filename(resolved, m_style);
  if(!filename.empty())
    m_filename.SetString(filename);

  llvm::StringRef directory = llvm::sys::path::parent_path(resolved, m_style);
  if(!directory.empty())
    m_directory.SetString(directory);
}

void FileSpec::SetFile(llvm::StringRef path, const llvm::Triple &Triple) {
  return SetFile(path, Triple.isOSWindows() ? Style::windows : Style::posix);
}

// Convert to pointer operator. This allows code to check any FileSpec objects
// to see if they contain anything valid using code such as:
//
//  if (file_spec)
//  {}
FileSpec::operator bool() const { return m_filename || m_directory; }

// Logical NOT operator. This allows code to check any FileSpec objects to see
// if they are invalid using code such as:
//
//  if (!file_spec)
//  {}
bool FileSpec::operator!() const { return !m_directory && !m_filename; }

bool FileSpec::DirectoryEquals(const FileSpec &rhs) const {
  const bool case_sensitive = IsCaseSensitive() || rhs.IsCaseSensitive();
  return ConstString::Equals(m_directory, rhs.m_directory, case_sensitive);
}

bool FileSpec::FileEquals(const FileSpec &rhs) const {
  const bool case_sensitive = IsCaseSensitive() || rhs.IsCaseSensitive();
  return ConstString::Equals(m_filename, rhs.m_filename, case_sensitive);
}

// Equal to operator
bool FileSpec::operator==(const FileSpec &rhs) const {
  return FileEquals(rhs) && DirectoryEquals(rhs);
}

// Not equal to operator
bool FileSpec::operator!=(const FileSpec &rhs) const { return !(*this == rhs); }

// Less than operator
bool FileSpec::operator<(const FileSpec &rhs) const {
  return FileSpec::Compare(*this, rhs, true) < 0;
}

// Dump a FileSpec object to a stream
Stream &lldb_private::operator<<(Stream &s, const FileSpec &f) {
  f.Dump(&s);
  return s;
}

// Clear this object by releasing both the directory and filename string values
// and making them both the empty string.
void FileSpec::Clear() {
  m_directory.Clear();
  m_filename.Clear();
}

// Compare two FileSpec objects. If "full" is true, then both the directory and
// the filename must match. If "full" is false, then the directory names for
// "a" and "b" are only compared if they are both non-empty. This allows a
// FileSpec object to only contain a filename and it can match FileSpec objects
// that have matching filenames with different paths.
//
// Return -1 if the "a" is less than "b", 0 if "a" is equal to "b" and "1" if
// "a" is greater than "b".
int FileSpec::Compare(const FileSpec &a, const FileSpec &b, bool full) {
  int result = 0;

  // case sensitivity of compare
  const bool case_sensitive = a.IsCaseSensitive() || b.IsCaseSensitive();

  // If full is true, then we must compare both the directory and filename.

  // If full is false, then if either directory is empty, then we match on the
  // basename only, and if both directories have valid values, we still do a
  // full compare. This allows for matching when we just have a filename in one
  // of the FileSpec objects.

  if (full || (a.m_directory && b.m_directory)) {
    result = ConstString::Compare(a.m_directory, b.m_directory, case_sensitive);
    if (result)
      return result;
  }
  return ConstString::Compare(a.m_filename, b.m_filename, case_sensitive);
}

bool FileSpec::Equal(const FileSpec &a, const FileSpec &b, bool full) {
  // case sensitivity of equality test
  const bool case_sensitive = a.IsCaseSensitive() || b.IsCaseSensitive();

  const bool filenames_equal = ConstString::Equals(a.m_filename,
                                                   b.m_filename,
                                                   case_sensitive);

  if (!filenames_equal)
    return false;

  if (!full && (a.GetDirectory().IsEmpty() || b.GetDirectory().IsEmpty()))
    return filenames_equal;

  return a == b;
}

llvm::Optional<FileSpec::Style> FileSpec::GuessPathStyle(llvm::StringRef absolute_path) {
  if (absolute_path.startswith("/"))
    return Style::posix;
  if (absolute_path.startswith(R"(\\)"))
    return Style::windows;
  if (absolute_path.size() > 3 && llvm::isAlpha(absolute_path[0]) &&
      absolute_path.substr(1, 2) == R"(:\)")
    return Style::windows;
  return llvm::None;
}

// Dump the object to the supplied stream. If the object contains a valid
// directory name, it will be displayed followed by a directory delimiter, and
// the filename.
void FileSpec::Dump(Stream *s) const {
  if (s) {
    std::string path{GetPath(true)};
    s->PutCString(path);
    char path_separator = GetPreferredPathSeparator(m_style);
    if (!m_filename && !path.empty() && path.back() != path_separator)
      s->PutChar(path_separator);
  }
}

FileSpec::Style FileSpec::GetPathStyle() const { return m_style; }

// Directory string get accessor.
ConstString &FileSpec::GetDirectory() { return m_directory; }

// Directory string const get accessor.
ConstString FileSpec::GetDirectory() const { return m_directory; }

// Filename string get accessor.
ConstString &FileSpec::GetFilename() { return m_filename; }

// Filename string const get accessor.
ConstString FileSpec::GetFilename() const { return m_filename; }

// Extract the directory and path into a fixed buffer. This is needed as the
// directory and path are stored in separate string values.
size_t FileSpec::GetPath(char *path, size_t path_max_len,
                         bool denormalize) const {
  if (!path)
    return 0;

  std::string result = GetPath(denormalize);
  ::snprintf(path, path_max_len, "%s", result.c_str());
  return std::min(path_max_len - 1, result.length());
}

std::string FileSpec::GetPath(bool denormalize) const {
  llvm::SmallString<64> result;
  GetPath(result, denormalize);
  return std::string(result.begin(), result.end());
}

const char *FileSpec::GetCString(bool denormalize) const {
  return ConstString{GetPath(denormalize)}.AsCString(nullptr);
}

void FileSpec::GetPath(llvm::SmallVectorImpl<char> &path,
                       bool denormalize) const {
  path.append(m_directory.GetStringRef().begin(),
              m_directory.GetStringRef().end());
  // Since the path was normalized and all paths use '/' when stored in these
  // objects, we don't need to look for the actual syntax specific path
  // separator, we just look for and insert '/'.
  if (m_directory && m_filename && m_directory.GetStringRef().back() != '/' &&
      m_filename.GetStringRef().back() != '/')
    path.insert(path.end(), '/');
  path.append(m_filename.GetStringRef().begin(),
              m_filename.GetStringRef().end());
  if (denormalize && !path.empty())
    Denormalize(path, m_style);
}

ConstString FileSpec::GetFileNameExtension() const {
  return ConstString(
      llvm::sys::path::extension(m_filename.GetStringRef(), m_style));
}

ConstString FileSpec::GetFileNameStrippingExtension() const {
  return ConstString(llvm::sys::path::stem(m_filename.GetStringRef(), m_style));
}

// Return the size in bytes that this object takes in memory. This returns the
// size in bytes of this object, not any shared string values it may refer to.
size_t FileSpec::MemorySize() const {
  return m_filename.MemorySize() + m_directory.MemorySize();
}

FileSpec
FileSpec::CopyByAppendingPathComponent(llvm::StringRef component) const {
  FileSpec ret = *this;
  ret.AppendPathComponent(component);
  return ret;
}

FileSpec FileSpec::CopyByRemovingLastPathComponent() const {
  llvm::SmallString<64> current_path;
  GetPath(current_path, false);
  if (llvm::sys::path::has_parent_path(current_path, m_style))
    return FileSpec(llvm::sys::path::parent_path(current_path, m_style),
                    m_style);
  return *this;
}

ConstString FileSpec::GetLastPathComponent() const {
  llvm::SmallString<64> current_path;
  GetPath(current_path, false);
  return ConstString(llvm::sys::path::filename(current_path, m_style));
}

void FileSpec::PrependPathComponent(llvm::StringRef component) {
  llvm::SmallString<64> new_path(component);
  llvm::SmallString<64> current_path;
  GetPath(current_path, false);
  llvm::sys::path::append(new_path,
                          llvm::sys::path::begin(current_path, m_style),
                          llvm::sys::path::end(current_path), m_style);
  SetFile(new_path, m_style);
}

void FileSpec::PrependPathComponent(const FileSpec &new_path) {
  return PrependPathComponent(new_path.GetPath(false));
}

void FileSpec::AppendPathComponent(llvm::StringRef component) {
  llvm::SmallString<64> current_path;
  GetPath(current_path, false);
  llvm::sys::path::append(current_path, m_style, component);
  SetFile(current_path, m_style);
}

void FileSpec::AppendPathComponent(const FileSpec &new_path) {
  return AppendPathComponent(new_path.GetPath(false));
}

bool FileSpec::RemoveLastPathComponent() {
  llvm::SmallString<64> current_path;
  GetPath(current_path, false);
  if (llvm::sys::path::has_parent_path(current_path, m_style)) {
    SetFile(llvm::sys::path::parent_path(current_path, m_style));
    return true;
  }
  return false;
}
/// Returns true if the filespec represents an implementation source
/// file (files with a ".c", ".cpp", ".m", ".mm" (many more)
/// extension).
///
/// \return
///     \b true if the filespec represents an implementation source
///     file, \b false otherwise.
bool FileSpec::IsSourceImplementationFile() const {
  ConstString extension(GetFileNameExtension());
  if (!extension)
    return false;

  static RegularExpression g_source_file_regex(llvm::StringRef(
      "^.([cC]|[mM]|[mM][mM]|[cC][pP][pP]|[cC]\\+\\+|[cC][xX][xX]|[cC][cC]|["
      "cC][pP]|[sS]|[aA][sS][mM]|[fF]|[fF]77|[fF]90|[fF]95|[fF]03|[fF][oO]["
      "rR]|[fF][tT][nN]|[fF][pP][pP]|[aA][dD][aA]|[aA][dD][bB]|[aA][dD][sS])"
      "$"));
  return g_source_file_regex.Execute(extension.GetStringRef());
}

bool FileSpec::IsRelative() const {
  return !IsAbsolute();
}

bool FileSpec::IsAbsolute() const {
  llvm::SmallString<64> current_path;
  GetPath(current_path, false);

  // Early return if the path is empty.
  if (current_path.empty())
    return false;

  // We consider paths starting with ~ to be absolute.
  if (current_path[0] == '~')
    return true;

  return llvm::sys::path::is_absolute(current_path, m_style);
}

void FileSpec::MakeAbsolute(const FileSpec &dir) {
  if (IsRelative())
    PrependPathComponent(dir);
}

void llvm::format_provider<FileSpec>::format(const FileSpec &F,
                                             raw_ostream &Stream,
                                             StringRef Style) {
  assert(
      (Style.empty() || Style.equals_lower("F") || Style.equals_lower("D")) &&
      "Invalid FileSpec style!");

  StringRef dir = F.GetDirectory().GetStringRef();
  StringRef file = F.GetFilename().GetStringRef();

  if (dir.empty() && file.empty()) {
    Stream << "(empty)";
    return;
  }

  if (Style.equals_lower("F")) {
    Stream << (file.empty() ? "(empty)" : file);
    return;
  }

  // Style is either D or empty, either way we need to print the directory.
  if (!dir.empty()) {
    // Directory is stored in normalized form, which might be different than
    // preferred form.  In order to handle this, we need to cut off the
    // filename, then denormalize, then write the entire denorm'ed directory.
    llvm::SmallString<64> denormalized_dir = dir;
    Denormalize(denormalized_dir, F.GetPathStyle());
    Stream << denormalized_dir;
    Stream << GetPreferredPathSeparator(F.GetPathStyle());
  }

  if (Style.equals_lower("D")) {
    // We only want to print the directory, so now just exit.
    if (dir.empty())
      Stream << "(empty)";
    return;
  }

  if (!file.empty())
    Stream << file;
}