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