[Reproducer] Add reproducer dump command.
authorJonas Devlieghere <jonas@devlieghere.com>
Fri, 13 Sep 2019 23:27:31 +0000 (23:27 +0000)
committerJonas Devlieghere <jonas@devlieghere.com>
Fri, 13 Sep 2019 23:27:31 +0000 (23:27 +0000)
This adds a reproducer dump commands which makes it possible to inspect
a reproducer from inside LLDB. Currently it supports the Files, Commands
and Version providers. I'm planning to add support for the GDB Remote
provider in a follow-up patch.

Differential revision: https://reviews.llvm.org/D67474

llvm-svn: 371909

lldb/lit/Reproducer/Inputs/FileCapture.in
lldb/lit/Reproducer/TestDump.test [new file with mode: 0644]
lldb/source/Commands/CommandObjectReproducer.cpp
lldb/source/Commands/Options.td
llvm/include/llvm/Support/VirtualFileSystem.h
llvm/lib/Support/VirtualFileSystem.cpp

index bf6f852..2c69487 100644 (file)
@@ -1,3 +1,4 @@
 run
 reproducer status
+reproducer dump -p files
 reproducer generate
diff --git a/lldb/lit/Reproducer/TestDump.test b/lldb/lit/Reproducer/TestDump.test
new file mode 100644 (file)
index 0000000..472c563
--- /dev/null
@@ -0,0 +1,21 @@
+# This tests the reproducer dump functionality.
+
+# Generate a reproducer.
+# RUN: mkdir -p %t
+# RUN: rm -rf %t.repro
+# RUN: %clang %S/Inputs/simple.c -g -o %t/reproducer.out
+# RUN: %lldb -x -b -s %S/Inputs/FileCapture.in -o 'reproducer dump -p files' --capture --capture-path %t.repro %t/reproducer.out
+
+# RUN: %lldb -b -o 'reproducer dump -p files -f %t.repro' | FileCheck %s --check-prefix FILES
+# FILES: 'reproducer.out'
+# FILES: 'FileCapture.in'
+
+# RUN: %lldb -b -o 'reproducer dump -p version -f %t.repro' | FileCheck %s --check-prefix VERSION
+# VERSION: lldb version
+
+# RUN: %lldb -b -o 'reproducer dump -p commands -f %t.repro' | FileCheck %s --check-prefix COMMANDS
+# COMMANDS: command source
+# COMMANDS: target create
+# COMMANDS: command source
+
+# RUN: %lldb --replay %t.repro | FileCheck %s --check-prefix FILES
index 895a9cc..fcfeb58 100644 (file)
@@ -8,6 +8,7 @@
 
 #include "CommandObjectReproducer.h"
 
+#include "lldb/Host/OptionParser.h"
 #include "lldb/Utility/Reproducer.h"
 
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/OptionGroupBoolean.h"
 
 using namespace lldb;
+using namespace llvm;
 using namespace lldb_private;
+using namespace lldb_private::repro;
+
+enum ReproducerProvider {
+  eReproducerProviderCommands,
+  eReproducerProviderFiles,
+  eReproducerProviderGDB,
+  eReproducerProviderVersion,
+  eReproducerProviderNone
+};
+
+static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
+    {
+        eReproducerProviderCommands,
+        "commands",
+        "Command Interpreter Commands",
+    },
+    {
+        eReproducerProviderFiles,
+        "files",
+        "Files",
+    },
+    {
+        eReproducerProviderGDB,
+        "gdb",
+        "GDB Remote Packets",
+    },
+    {
+        eReproducerProviderVersion,
+        "version",
+        "Version",
+    },
+    {
+        eReproducerProviderNone,
+        "none",
+        "None",
+    },
+};
+
+static constexpr OptionEnumValues ReproducerProviderType() {
+  return OptionEnumValues(g_reproducer_provider_type);
+}
+
+#define LLDB_OPTIONS_reproducer
+#include "CommandOptions.inc"
 
 class CommandObjectReproducerGenerate : public CommandObjectParsed {
 public:
@@ -38,7 +84,7 @@ protected:
       return false;
     }
 
-    auto &r = repro::Reproducer::Instance();
+    auto &r = Reproducer::Instance();
     if (auto generator = r.GetGenerator()) {
       generator->Keep();
     } else if (r.GetLoader()) {
@@ -84,7 +130,7 @@ protected:
       return false;
     }
 
-    auto &r = repro::Reproducer::Instance();
+    auto &r = Reproducer::Instance();
     if (r.GetGenerator()) {
       result.GetOutputStream() << "Reproducer is in capture mode.\n";
     } else if (r.GetLoader()) {
@@ -98,6 +144,191 @@ protected:
   }
 };
 
+static void SetError(CommandReturnObject &result, Error err) {
+  result.GetErrorStream().Printf("error: %s\n",
+                                 toString(std::move(err)).c_str());
+  result.SetStatus(eReturnStatusFailed);
+}
+
+class CommandObjectReproducerDump : public CommandObjectParsed {
+public:
+  CommandObjectReproducerDump(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "reproducer dump",
+                            "Dump the information contained in a reproducer.",
+                            nullptr) {}
+
+  ~CommandObjectReproducerDump() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+  class CommandOptions : public Options {
+  public:
+    CommandOptions() : Options(), file() {}
+
+    ~CommandOptions() override = default;
+
+    Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      Status error;
+      const int short_option = m_getopt_table[option_idx].val;
+
+      switch (short_option) {
+      case 'f':
+        file.SetFile(option_arg, FileSpec::Style::native);
+        FileSystem::Instance().Resolve(file);
+        break;
+      case 'p':
+        provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
+            option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
+        if (!error.Success())
+          error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
+                                         option_arg.str().c_str());
+        break;
+      default:
+        llvm_unreachable("Unimplemented option");
+      }
+
+      return error;
+    }
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {
+      file.Clear();
+      provider = eReproducerProviderNone;
+    }
+
+    ArrayRef<OptionDefinition> GetDefinitions() override {
+      return makeArrayRef(g_reproducer_options);
+    }
+
+    FileSpec file;
+    ReproducerProvider provider = eReproducerProviderNone;
+  };
+
+protected:
+  bool DoExecute(Args &command, CommandReturnObject &result) override {
+    if (!command.empty()) {
+      result.AppendErrorWithFormat("'%s' takes no arguments",
+                                   m_cmd_name.c_str());
+      return false;
+    }
+
+    // If no reproducer path is specified, use the loader currently used for
+    // replay. Otherwise create a new loader just for dumping.
+    llvm::Optional<Loader> loader_storage;
+    Loader *loader = nullptr;
+    if (!m_options.file) {
+      loader = Reproducer::Instance().GetLoader();
+      if (loader == nullptr) {
+        result.SetError(
+            "Not specifying a reproducer is only support during replay.");
+        result.SetStatus(eReturnStatusSuccessFinishNoResult);
+        return false;
+      }
+    } else {
+      loader_storage.emplace(m_options.file);
+      loader = &(*loader_storage);
+      if (Error err = loader->LoadIndex()) {
+        SetError(result, std::move(err));
+        return false;
+      }
+    }
+
+    // If we get here we should have a valid loader.
+    assert(loader);
+
+    switch (m_options.provider) {
+    case eReproducerProviderFiles: {
+      FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
+
+      // Read the VFS mapping.
+      ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
+          vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
+      if (!buffer) {
+        SetError(result, errorCodeToError(buffer.getError()));
+        return false;
+      }
+
+      // Initialize a VFS from the given mapping.
+      IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
+          std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
+
+      // Dump the VFS to a buffer.
+      std::string str;
+      raw_string_ostream os(str);
+      static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
+      os.flush();
+
+      // Return the string.
+      result.AppendMessage(str);
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return true;
+    }
+    case eReproducerProviderVersion: {
+      FileSpec version_file = loader->GetFile<VersionProvider::Info>();
+
+      // Load the version info into a buffer.
+      ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
+          vfs::getRealFileSystem()->getBufferForFile(version_file.GetPath());
+      if (!buffer) {
+        SetError(result, errorCodeToError(buffer.getError()));
+        return false;
+      }
+
+      // Return the version string.
+      StringRef version = (*buffer)->getBuffer();
+      result.AppendMessage(version.str());
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return true;
+    }
+    case eReproducerProviderCommands: {
+      // Create a new command loader.
+      std::unique_ptr<repro::CommandLoader> command_loader =
+          repro::CommandLoader::Create(loader);
+      if (!command_loader) {
+        SetError(result,
+                 make_error<StringError>(llvm::inconvertibleErrorCode(),
+                                         "Unable to create command loader."));
+        return false;
+      }
+
+      // Iterate over the command files and dump them.
+      while (true) {
+        llvm::Optional<std::string> command_file =
+            command_loader->GetNextFile();
+        if (!command_file)
+          break;
+
+        auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
+        if (auto err = command_buffer.getError()) {
+          SetError(result, errorCodeToError(err));
+          return false;
+        }
+        result.AppendMessage((*command_buffer)->getBuffer());
+      }
+
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return true;
+    }
+    case eReproducerProviderGDB: {
+      // FIXME: Dumping the GDB remote packets means moving the
+      // (de)serialization code out of the GDB-remote plugin.
+      result.AppendMessage("Dumping GDB remote packets isn't implemented yet.");
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return true;
+    }
+    case eReproducerProviderNone:
+      result.SetError("No valid provider specified.");
+      return false;
+    }
+
+    result.SetStatus(eReturnStatusSuccessFinishNoResult);
+    return result.Succeeded();
+  }
+
+private:
+  CommandOptions m_options;
+};
+
 CommandObjectReproducer::CommandObjectReproducer(
     CommandInterpreter &interpreter)
     : CommandObjectMultiword(
@@ -109,6 +340,8 @@ CommandObjectReproducer::CommandObjectReproducer(
       CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
   LoadSubCommand("status", CommandObjectSP(
                                new CommandObjectReproducerStatus(interpreter)));
+  LoadSubCommand("dump",
+                 CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
 }
 
 CommandObjectReproducer::~CommandObjectReproducer() = default;
index d710290..7791302 100644 (file)
@@ -442,6 +442,15 @@ let Command = "log" in {
     Desc<"Prepend the names of files and function that generate the logs.">;
 }
 
+let Command = "reproducer" in {
+  def reproducer_provider : Option<"provider", "p">, Group<1>,
+    EnumArg<"None", "ReproducerProviderType()">,
+    Required, Desc<"The reproducer provider to dump.">;
+  def reproducer_file : Option<"file", "f">, Group<1>, Arg<"Filename">,
+    Desc<"The reproducer path. If a reproducer is replayed and no path is "
+    "provided, that reproducer is dumped.">;
+}
+
 let Command = "memory read" in {
   def memory_read_num_per_line : Option<"num-per-line", "l">, Group<1>,
     Arg<"NumberPerLine">, Desc<"The number of items per line to display.">;
index 31c9e85..627831e 100644 (file)
@@ -730,9 +730,10 @@ public:
 
   StringRef getExternalContentsPrefixDir() const;
 
+  void dump(raw_ostream &OS) const;
+  void dumpEntry(raw_ostream &OS, Entry *E, int NumSpaces = 0) const;
 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
   LLVM_DUMP_METHOD void dump() const;
-  LLVM_DUMP_METHOD void dumpEntry(Entry *E, int NumSpaces = 0) const;
 #endif
 };
 
index e4197c1..f4b77fc 100644 (file)
@@ -1082,20 +1082,19 @@ StringRef RedirectingFileSystem::getExternalContentsPrefixDir() const {
   return ExternalContentsPrefixDir;
 }
 
-#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
-LLVM_DUMP_METHOD void RedirectingFileSystem::dump() const {
+void RedirectingFileSystem::dump(raw_ostream &OS) const {
   for (const auto &Root : Roots)
-    dumpEntry(Root.get());
+    dumpEntry(OS, Root.get());
 }
 
-LLVM_DUMP_METHOD void
-RedirectingFileSystem::dumpEntry(RedirectingFileSystem::Entry *E,
-                                 int NumSpaces) const {
+void RedirectingFileSystem::dumpEntry(raw_ostream &OS,
+                                      RedirectingFileSystem::Entry *E,
+                                      int NumSpaces) const {
   StringRef Name = E->getName();
   for (int i = 0, e = NumSpaces; i < e; ++i)
-    dbgs() << " ";
-  dbgs() << "'" << Name.str().c_str() << "'"
-         << "\n";
+    OS << " ";
+  OS << "'" << Name.str().c_str() << "'"
+     << "\n";
 
   if (E->getKind() == RedirectingFileSystem::EK_Directory) {
     auto *DE = dyn_cast<RedirectingFileSystem::RedirectingDirectoryEntry>(E);
@@ -1103,9 +1102,12 @@ RedirectingFileSystem::dumpEntry(RedirectingFileSystem::Entry *E,
 
     for (std::unique_ptr<Entry> &SubEntry :
          llvm::make_range(DE->contents_begin(), DE->contents_end()))
-      dumpEntry(SubEntry.get(), NumSpaces + 2);
+      dumpEntry(OS, SubEntry.get(), NumSpaces + 2);
   }
 }
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void RedirectingFileSystem::dump() const { dump(dbgs()); }
 #endif
 
 /// A helper class to hold the common YAML parsing state.