aboutsummaryrefslogtreecommitdiff
path: root/include/clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h
blob: 7d0881343478a51874e89066abb5e0b1666f6f99 (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
//===- DependencyScanningFilesystem.h - clang-scan-deps fs ===---*- 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 LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H
#define LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H

#include "clang/Basic/LLVM.h"
#include "clang/Lex/PreprocessorExcludedConditionalDirectiveSkipMapping.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/VirtualFileSystem.h"
#include <mutex>

namespace clang {
namespace tooling {
namespace dependencies {

/// An in-memory representation of a file system entity that is of interest to
/// the dependency scanning filesystem.
///
/// It represents one of the following:
/// - an opened source file with minimized contents and a stat value.
/// - an opened source file with original contents and a stat value.
/// - a directory entry with its stat value.
/// - an error value to represent a file system error.
/// - a placeholder with an invalid stat indicating a not yet initialized entry.
class CachedFileSystemEntry {
public:
  /// Default constructor creates an entry with an invalid stat.
  CachedFileSystemEntry() : MaybeStat(llvm::vfs::Status()) {}

  CachedFileSystemEntry(std::error_code Error) : MaybeStat(std::move(Error)) {}

  /// Create an entry that represents an opened source file with minimized or
  /// original contents.
  ///
  /// The filesystem opens the file even for `stat` calls open to avoid the
  /// issues with stat + open of minimized files that might lead to a
  /// mismatching size of the file. If file is not minimized, the full file is
  /// read and copied into memory to ensure that it's not memory mapped to avoid
  /// running out of file descriptors.
  static CachedFileSystemEntry createFileEntry(StringRef Filename,
                                               llvm::vfs::FileSystem &FS,
                                               bool Minimize = true);

  /// Create an entry that represents a directory on the filesystem.
  static CachedFileSystemEntry createDirectoryEntry(llvm::vfs::Status &&Stat);

  /// \returns True if the entry is valid.
  bool isValid() const { return !MaybeStat || MaybeStat->isStatusKnown(); }

  /// \returns True if the current entry points to a directory.
  bool isDirectory() const { return MaybeStat && MaybeStat->isDirectory(); }

  /// \returns The error or the file's contents.
  llvm::ErrorOr<StringRef> getContents() const {
    if (!MaybeStat)
      return MaybeStat.getError();
    assert(!MaybeStat->isDirectory() && "not a file");
    assert(isValid() && "not initialized");
    return StringRef(Contents);
  }

  /// \returns The error or the status of the entry.
  llvm::ErrorOr<llvm::vfs::Status> getStatus() const {
    assert(isValid() && "not initialized");
    return MaybeStat;
  }

  /// \returns the name of the file.
  StringRef getName() const {
    assert(isValid() && "not initialized");
    return MaybeStat->getName();
  }

  /// Return the mapping between location -> distance that is used to speed up
  /// the block skipping in the preprocessor.
  const PreprocessorSkippedRangeMapping &getPPSkippedRangeMapping() const {
    return PPSkippedRangeMapping;
  }

  CachedFileSystemEntry(CachedFileSystemEntry &&) = default;
  CachedFileSystemEntry &operator=(CachedFileSystemEntry &&) = default;

  CachedFileSystemEntry(const CachedFileSystemEntry &) = delete;
  CachedFileSystemEntry &operator=(const CachedFileSystemEntry &) = delete;

private:
  llvm::ErrorOr<llvm::vfs::Status> MaybeStat;
  // Store the contents in a small string to allow a
  // move from the small string for the minimized contents.
  // Note: small size of 1 allows us to store an empty string with an implicit
  // null terminator without any allocations.
  llvm::SmallString<1> Contents;
  PreprocessorSkippedRangeMapping PPSkippedRangeMapping;
};

/// This class is a shared cache, that caches the 'stat' and 'open' calls to the
/// underlying real file system.
///
/// It is sharded based on the hash of the key to reduce the lock contention for
/// the worker threads.
class DependencyScanningFilesystemSharedCache {
public:
  struct SharedFileSystemEntry {
    std::mutex ValueLock;
    CachedFileSystemEntry Value;
  };

  DependencyScanningFilesystemSharedCache();

  /// Returns a cache entry for the corresponding key.
  ///
  /// A new cache entry is created if the key is not in the cache. This is a
  /// thread safe call.
  SharedFileSystemEntry &get(StringRef Key);

private:
  struct CacheShard {
    std::mutex CacheLock;
    llvm::StringMap<SharedFileSystemEntry, llvm::BumpPtrAllocator> Cache;
  };
  std::unique_ptr<CacheShard[]> CacheShards;
  unsigned NumShards;
};

/// A virtual file system optimized for the dependency discovery.
///
/// It is primarily designed to work with source files whose contents was was
/// preprocessed to remove any tokens that are unlikely to affect the dependency
/// computation.
///
/// This is not a thread safe VFS. A single instance is meant to be used only in
/// one thread. Multiple instances are allowed to service multiple threads
/// running in parallel.
class DependencyScanningWorkerFilesystem : public llvm::vfs::ProxyFileSystem {
public:
  DependencyScanningWorkerFilesystem(
      DependencyScanningFilesystemSharedCache &SharedCache,
      IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
      ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings)
      : ProxyFileSystem(std::move(FS)), SharedCache(SharedCache),
        PPSkipMappings(PPSkipMappings) {}

  llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override;
  llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
  openFileForRead(const Twine &Path) override;

  /// The set of files that should not be minimized.
  llvm::StringSet<> IgnoredFiles;

private:
  void setCachedEntry(StringRef Filename, const CachedFileSystemEntry *Entry) {
    bool IsInserted = Cache.try_emplace(Filename, Entry).second;
    (void)IsInserted;
    assert(IsInserted && "local cache is updated more than once");
  }

  const CachedFileSystemEntry *getCachedEntry(StringRef Filename) {
    auto It = Cache.find(Filename);
    return It == Cache.end() ? nullptr : It->getValue();
  }

  llvm::ErrorOr<const CachedFileSystemEntry *>
  getOrCreateFileSystemEntry(const StringRef Filename);

  DependencyScanningFilesystemSharedCache &SharedCache;
  /// The local cache is used by the worker thread to cache file system queries
  /// locally instead of querying the global cache every time.
  llvm::StringMap<const CachedFileSystemEntry *, llvm::BumpPtrAllocator> Cache;
  /// The optional mapping structure which records information about the
  /// excluded conditional directive skip mappings that are used by the
  /// currently active preprocessor.
  ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings;
};

} // end namespace dependencies
} // end namespace tooling
} // end namespace clang

#endif // LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H