1 //===-- Reproducer.h --------------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #ifndef LLDB_UTILITY_REPRODUCER_H 10 #define LLDB_UTILITY_REPRODUCER_H 11 12 #include "lldb/Utility/FileSpec.h" 13 #include "llvm/ADT/DenseMap.h" 14 #include "llvm/Support/Error.h" 15 #include "llvm/Support/FileCollector.h" 16 #include "llvm/Support/YAMLTraits.h" 17 18 #include <mutex> 19 #include <string> 20 #include <vector> 21 22 namespace lldb_private { 23 namespace repro { 24 25 class Reproducer; 26 27 enum class ReproducerMode { 28 Capture, 29 Replay, 30 PassiveReplay, 31 Off, 32 }; 33 34 /// The provider defines an interface for generating files needed for 35 /// reproducing. 36 /// 37 /// Different components will implement different providers. 38 class ProviderBase { 39 public: 40 virtual ~ProviderBase() = default; 41 GetRoot()42 const FileSpec &GetRoot() const { return m_root; } 43 44 /// The Keep method is called when it is decided that we need to keep the 45 /// data in order to provide a reproducer. Keep()46 virtual void Keep(){}; 47 48 /// The Discard method is called when it is decided that we do not need to 49 /// keep any information and will not generate a reproducer. Discard()50 virtual void Discard(){}; 51 52 // Returns the class ID for this type. ClassID()53 static const void *ClassID() { return &ID; } 54 55 // Returns the class ID for the dynamic type of this Provider instance. 56 virtual const void *DynamicClassID() const = 0; 57 58 virtual llvm::StringRef GetName() const = 0; 59 virtual llvm::StringRef GetFile() const = 0; 60 61 protected: ProviderBase(const FileSpec & root)62 ProviderBase(const FileSpec &root) : m_root(root) {} 63 64 private: 65 /// Every provider knows where to dump its potential files. 66 FileSpec m_root; 67 68 virtual void anchor(); 69 static char ID; 70 }; 71 72 template <typename ThisProviderT> class Provider : public ProviderBase { 73 public: ClassID()74 static const void *ClassID() { return &ThisProviderT::ID; } 75 DynamicClassID()76 const void *DynamicClassID() const override { return &ThisProviderT::ID; } 77 GetName()78 llvm::StringRef GetName() const override { return ThisProviderT::Info::name; } GetFile()79 llvm::StringRef GetFile() const override { return ThisProviderT::Info::file; } 80 81 protected: 82 using ProviderBase::ProviderBase; // Inherit constructor. 83 }; 84 85 class FileProvider : public Provider<FileProvider> { 86 public: 87 struct Info { 88 static const char *name; 89 static const char *file; 90 }; 91 FileProvider(const FileSpec & directory)92 FileProvider(const FileSpec &directory) 93 : Provider(directory), 94 m_collector(std::make_shared<llvm::FileCollector>( 95 directory.CopyByAppendingPathComponent("root").GetPath(), 96 directory.GetPath())) {} 97 GetFileCollector()98 std::shared_ptr<llvm::FileCollector> GetFileCollector() { 99 return m_collector; 100 } 101 102 void recordInterestingDirectory(const llvm::Twine &dir); 103 Keep()104 void Keep() override { 105 auto mapping = GetRoot().CopyByAppendingPathComponent(Info::file); 106 // Temporary files that are removed during execution can cause copy errors. 107 if (auto ec = m_collector->copyFiles(/*stop_on_error=*/false)) 108 return; 109 m_collector->writeMapping(mapping.GetPath()); 110 } 111 112 static char ID; 113 114 private: 115 std::shared_ptr<llvm::FileCollector> m_collector; 116 }; 117 118 /// Provider for the LLDB version number. 119 /// 120 /// When the reproducer is kept, it writes the lldb version to a file named 121 /// version.txt in the reproducer root. 122 class VersionProvider : public Provider<VersionProvider> { 123 public: VersionProvider(const FileSpec & directory)124 VersionProvider(const FileSpec &directory) : Provider(directory) {} 125 struct Info { 126 static const char *name; 127 static const char *file; 128 }; SetVersion(std::string version)129 void SetVersion(std::string version) { 130 assert(m_version.empty()); 131 m_version = std::move(version); 132 } 133 void Keep() override; 134 std::string m_version; 135 static char ID; 136 }; 137 138 /// Provider for the LLDB current working directory. 139 /// 140 /// When the reproducer is kept, it writes lldb's current working directory to 141 /// a file named cwd.txt in the reproducer root. 142 class WorkingDirectoryProvider : public Provider<WorkingDirectoryProvider> { 143 public: WorkingDirectoryProvider(const FileSpec & directory)144 WorkingDirectoryProvider(const FileSpec &directory) : Provider(directory) { 145 llvm::SmallString<128> cwd; 146 if (std::error_code EC = llvm::sys::fs::current_path(cwd)) 147 return; 148 m_cwd = std::string(cwd.str()); 149 } 150 Update(llvm::StringRef path)151 void Update(llvm::StringRef path) { m_cwd = std::string(path); } 152 153 struct Info { 154 static const char *name; 155 static const char *file; 156 }; 157 void Keep() override; 158 std::string m_cwd; 159 static char ID; 160 }; 161 162 /// The recorder is a small object handed out by a provider to record data. It 163 /// is commonly used in combination with a MultiProvider which is meant to 164 /// record information for multiple instances of the same source of data. 165 class AbstractRecorder { 166 protected: AbstractRecorder(const FileSpec & filename,std::error_code & ec)167 AbstractRecorder(const FileSpec &filename, std::error_code &ec) 168 : m_filename(filename.GetFilename().GetStringRef()), 169 m_os(filename.GetPath(), ec, llvm::sys::fs::OF_Text), m_record(true) {} 170 171 public: GetFilename()172 const FileSpec &GetFilename() { return m_filename; } 173 Stop()174 void Stop() { 175 assert(m_record); 176 m_record = false; 177 } 178 179 private: 180 FileSpec m_filename; 181 182 protected: 183 llvm::raw_fd_ostream m_os; 184 bool m_record; 185 }; 186 187 /// Recorder that records its data as text to a file. 188 class DataRecorder : public AbstractRecorder { 189 public: DataRecorder(const FileSpec & filename,std::error_code & ec)190 DataRecorder(const FileSpec &filename, std::error_code &ec) 191 : AbstractRecorder(filename, ec) {} 192 193 static llvm::Expected<std::unique_ptr<DataRecorder>> 194 Create(const FileSpec &filename); 195 196 template <typename T> void Record(const T &t, bool newline = false) { 197 if (!m_record) 198 return; 199 m_os << t; 200 if (newline) 201 m_os << '\n'; 202 m_os.flush(); 203 } 204 }; 205 206 /// Recorder that records its data as YAML to a file. 207 class YamlRecorder : public AbstractRecorder { 208 public: YamlRecorder(const FileSpec & filename,std::error_code & ec)209 YamlRecorder(const FileSpec &filename, std::error_code &ec) 210 : AbstractRecorder(filename, ec) {} 211 212 static llvm::Expected<std::unique_ptr<YamlRecorder>> 213 Create(const FileSpec &filename); 214 Record(const T & t)215 template <typename T> void Record(const T &t) { 216 if (!m_record) 217 return; 218 llvm::yaml::Output yout(m_os); 219 // The YAML traits are defined as non-const because they are used for 220 // serialization and deserialization. The cast is safe because 221 // serialization doesn't modify the object. 222 yout << const_cast<T &>(t); 223 m_os.flush(); 224 } 225 }; 226 227 /// The MultiProvider is a provider that hands out recorder which can be used 228 /// to capture data for different instances of the same object. The recorders 229 /// can be passed around or stored as an instance member. 230 /// 231 /// The Info::file for the MultiProvider contains an index of files for every 232 /// recorder. Use the MultiLoader to read the index and get the individual 233 /// files. 234 template <typename T, typename V> 235 class MultiProvider : public repro::Provider<V> { 236 public: MultiProvider(const FileSpec & directory)237 MultiProvider(const FileSpec &directory) : Provider<V>(directory) {} 238 GetNewRecorder()239 T *GetNewRecorder() { 240 std::size_t i = m_recorders.size() + 1; 241 std::string filename = (llvm::Twine(V::Info::name) + llvm::Twine("-") + 242 llvm::Twine(i) + llvm::Twine(".yaml")) 243 .str(); 244 auto recorder_or_error = 245 T::Create(this->GetRoot().CopyByAppendingPathComponent(filename)); 246 if (!recorder_or_error) { 247 llvm::consumeError(recorder_or_error.takeError()); 248 return nullptr; 249 } 250 251 m_recorders.push_back(std::move(*recorder_or_error)); 252 return m_recorders.back().get(); 253 } 254 Keep()255 void Keep() override { 256 std::vector<std::string> files; 257 for (auto &recorder : m_recorders) { 258 recorder->Stop(); 259 files.push_back(recorder->GetFilename().GetPath()); 260 } 261 262 FileSpec file = this->GetRoot().CopyByAppendingPathComponent(V::Info::file); 263 std::error_code ec; 264 llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text); 265 if (ec) 266 return; 267 llvm::yaml::Output yout(os); 268 yout << files; 269 } 270 Discard()271 void Discard() override { m_recorders.clear(); } 272 273 private: 274 std::vector<std::unique_ptr<T>> m_recorders; 275 }; 276 277 class CommandProvider : public MultiProvider<DataRecorder, CommandProvider> { 278 public: 279 struct Info { 280 static const char *name; 281 static const char *file; 282 }; 283 CommandProvider(const FileSpec & directory)284 CommandProvider(const FileSpec &directory) 285 : MultiProvider<DataRecorder, CommandProvider>(directory) {} 286 287 static char ID; 288 }; 289 290 /// The generator is responsible for the logic needed to generate a 291 /// reproducer. For doing so it relies on providers, who serialize data that 292 /// is necessary for reproducing a failure. 293 class Generator final { 294 295 public: 296 Generator(FileSpec root); 297 ~Generator(); 298 299 /// Method to indicate we want to keep the reproducer. If reproducer 300 /// generation is disabled, this does nothing. 301 void Keep(); 302 303 /// Method to indicate we do not want to keep the reproducer. This is 304 /// unaffected by whether or not generation reproduction is enabled, as we 305 /// might need to clean up files already written to disk. 306 void Discard(); 307 308 /// Enable or disable auto generate. 309 void SetAutoGenerate(bool b); 310 311 /// Return whether auto generate is enabled. 312 bool IsAutoGenerate() const; 313 314 /// Create and register a new provider. Create()315 template <typename T> T *Create() { 316 std::unique_ptr<ProviderBase> provider = std::make_unique<T>(m_root); 317 return static_cast<T *>(Register(std::move(provider))); 318 } 319 320 /// Get an existing provider. Get()321 template <typename T> T *Get() { 322 auto it = m_providers.find(T::ClassID()); 323 if (it == m_providers.end()) 324 return nullptr; 325 return static_cast<T *>(it->second.get()); 326 } 327 328 /// Get a provider if it exists, otherwise create it. GetOrCreate()329 template <typename T> T &GetOrCreate() { 330 auto *provider = Get<T>(); 331 if (provider) 332 return *provider; 333 return *Create<T>(); 334 } 335 336 const FileSpec &GetRoot() const; 337 338 private: 339 friend Reproducer; 340 341 ProviderBase *Register(std::unique_ptr<ProviderBase> provider); 342 343 /// Builds and index with provider info. 344 void AddProvidersToIndex(); 345 346 /// Map of provider IDs to provider instances. 347 llvm::DenseMap<const void *, std::unique_ptr<ProviderBase>> m_providers; 348 std::mutex m_providers_mutex; 349 350 /// The reproducer root directory. 351 FileSpec m_root; 352 353 /// Flag to ensure that we never call both keep and discard. 354 bool m_done = false; 355 356 /// Flag to auto generate a reproducer when it would otherwise be discarded. 357 bool m_auto_generate = false; 358 }; 359 360 class Loader final { 361 public: 362 Loader(FileSpec root, bool passive = false); 363 GetFile()364 template <typename T> FileSpec GetFile() { 365 if (!HasFile(T::file)) 366 return {}; 367 368 return GetRoot().CopyByAppendingPathComponent(T::file); 369 } 370 LoadBuffer()371 template <typename T> llvm::Expected<std::string> LoadBuffer() { 372 FileSpec file = GetFile<typename T::Info>(); 373 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer = 374 llvm::vfs::getRealFileSystem()->getBufferForFile(file.GetPath()); 375 if (!buffer) 376 return llvm::errorCodeToError(buffer.getError()); 377 return (*buffer)->getBuffer().str(); 378 } 379 380 llvm::Error LoadIndex(); 381 GetRoot()382 const FileSpec &GetRoot() const { return m_root; } 383 IsPassiveReplay()384 bool IsPassiveReplay() const { return m_passive_replay; } 385 386 private: 387 bool HasFile(llvm::StringRef file); 388 389 FileSpec m_root; 390 std::vector<std::string> m_files; 391 bool m_loaded; 392 bool m_passive_replay; 393 }; 394 395 /// The reproducer enables clients to obtain access to the Generator and 396 /// Loader. 397 class Reproducer { 398 public: 399 static Reproducer &Instance(); 400 static llvm::Error Initialize(ReproducerMode mode, 401 llvm::Optional<FileSpec> root); 402 static bool Initialized(); 403 static void Terminate(); 404 405 Reproducer() = default; 406 407 Generator *GetGenerator(); 408 Loader *GetLoader(); 409 410 const Generator *GetGenerator() const; 411 const Loader *GetLoader() const; 412 413 FileSpec GetReproducerPath() const; 414 IsCapturing()415 bool IsCapturing() { return static_cast<bool>(m_generator); }; IsReplaying()416 bool IsReplaying() { return static_cast<bool>(m_loader); }; 417 418 protected: 419 llvm::Error SetCapture(llvm::Optional<FileSpec> root); 420 llvm::Error SetReplay(llvm::Optional<FileSpec> root, bool passive = false); 421 422 private: 423 static llvm::Optional<Reproducer> &InstanceImpl(); 424 425 llvm::Optional<Generator> m_generator; 426 llvm::Optional<Loader> m_loader; 427 428 mutable std::mutex m_mutex; 429 }; 430 431 /// Loader for data captured with the MultiProvider. It will read the index and 432 /// return the path to the files in the index. 433 template <typename T> class MultiLoader { 434 public: MultiLoader(std::vector<std::string> files)435 MultiLoader(std::vector<std::string> files) : m_files(files) {} 436 Create(Loader * loader)437 static std::unique_ptr<MultiLoader> Create(Loader *loader) { 438 if (!loader) 439 return {}; 440 441 FileSpec file = loader->GetFile<typename T::Info>(); 442 if (!file) 443 return {}; 444 445 auto error_or_file = llvm::MemoryBuffer::getFile(file.GetPath()); 446 if (auto err = error_or_file.getError()) 447 return {}; 448 449 std::vector<std::string> files; 450 llvm::yaml::Input yin((*error_or_file)->getBuffer()); 451 yin >> files; 452 453 if (auto err = yin.error()) 454 return {}; 455 456 for (auto &file : files) { 457 FileSpec absolute_path = 458 loader->GetRoot().CopyByAppendingPathComponent(file); 459 file = absolute_path.GetPath(); 460 } 461 462 return std::make_unique<MultiLoader<T>>(std::move(files)); 463 } 464 GetNextFile()465 llvm::Optional<std::string> GetNextFile() { 466 if (m_index >= m_files.size()) 467 return {}; 468 return m_files[m_index++]; 469 } 470 471 private: 472 std::vector<std::string> m_files; 473 unsigned m_index = 0; 474 }; 475 476 } // namespace repro 477 } // namespace lldb_private 478 479 #endif // LLDB_UTILITY_REPRODUCER_H 480