JDevlieghere updated this revision to Diff 188286.
JDevlieghere added a comment.

- Add testcase.
- Don't log sourced commands twice.

For some reason I thought that we no longer needed to differentiate between 
commands being sourced from a file. I remember an earlier discussing with Pavel 
on this topic where we considered doing this at a lower level, but there we 
face the same issue, without an easy way to differentiate.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58564/new/

https://reviews.llvm.org/D58564

Files:
  lldb/include/lldb/Interpreter/CommandInterpreter.h
  lldb/lit/Reproducer/Inputs/CommandRepro.in
  lldb/lit/Reproducer/TestCommandRepro.test
  lldb/source/Commands/CommandObjectReproducer.cpp
  lldb/source/Interpreter/CommandInterpreter.cpp

Index: lldb/source/Interpreter/CommandInterpreter.cpp
===================================================================
--- lldb/source/Interpreter/CommandInterpreter.cpp
+++ lldb/source/Interpreter/CommandInterpreter.cpp
@@ -45,6 +45,7 @@
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Core/StreamFile.h"
 #include "lldb/Utility/Log.h"
+#include "lldb/Utility/Reproducer.h"
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/Stream.h"
 #include "lldb/Utility/Timer.h"
@@ -74,6 +75,7 @@
 
 using namespace lldb;
 using namespace lldb_private;
+using namespace llvm;
 
 static const char *k_white_space = " \t\v";
 
@@ -116,6 +118,51 @@
   eEchoCommentCommands = 5
 };
 
+/// Provider for the command interpreter. Every command is logged to file which
+/// is used as input during replay. The latter takes place at the SB API layer
+/// by changing the input file handle.
+class lldb_private::CommandProvider
+    : public repro::Provider<lldb_private::CommandProvider> {
+public:
+  typedef CommandProviderInfo info;
+
+  CommandProvider(const FileSpec &directory) : Provider(directory) {}
+
+  /// Capture a single command.
+  void CaptureCommand(std::string command) {
+    m_commands.push_back(std::move(command));
+  }
+
+  /// Commands are kept in memory and written to a file when the reproducer
+  /// needs to be kept.
+  void Keep() override {
+    FileSpec file =
+        GetRoot().CopyByAppendingPathComponent(CommandProviderInfo::file);
+
+    std::error_code ec;
+    llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::F_Text);
+
+    if (ec)
+      return;
+
+    for (auto &command : m_commands)
+      os << command << '\n';
+  }
+
+  /// Commands are kept in memory and are cleared when the reproducer is
+  /// discarded.
+  void Discard() override { m_commands.clear(); }
+
+  static char ID;
+
+private:
+  std::vector<std::string> m_commands;
+};
+
+char CommandProvider::ID = 0;
+const char *CommandProviderInfo::name = "command-interpreter";
+const char *CommandProviderInfo::file = "command-interpreter.txt";
+
 ConstString &CommandInterpreter::GetStaticBroadcasterClass() {
   static ConstString class_name("lldb.commandInterpreter");
   return class_name;
@@ -141,6 +188,9 @@
   SetEventName(eBroadcastBitQuitCommandReceived, "quit");
   CheckInWithManager();
   m_collection_sp->Initialize(g_properties);
+
+  if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator())
+    m_provider = &g->GetOrCreate<CommandProvider>();
 }
 
 bool CommandInterpreter::GetExpandRegexAliases() const {
@@ -1604,7 +1654,8 @@
                                        CommandReturnObject &result,
                                        ExecutionContext *override_context,
                                        bool repeat_on_empty_command,
-                                       bool no_context_switching)
+                                       bool no_context_switching,
+                                       bool add_to_reproducer)
 
 {
 
@@ -1694,6 +1745,9 @@
 
   Status error(PreprocessCommand(command_string));
 
+  if (m_provider && add_to_reproducer)
+    m_provider->CaptureCommand(original_command_string);
+
   if (error.Fail()) {
     result.AppendError(error.AsCString());
     result.SetStatus(eReturnStatusFailed);
@@ -2175,6 +2229,7 @@
     options.SetSilent(true);
     options.SetStopOnError(false);
     options.SetStopOnContinue(true);
+    options.SetAddToReproducer(false);
 
     HandleCommandsFromFile(init_file,
                            nullptr, // Execution context
@@ -2252,7 +2307,8 @@
         HandleCommand(cmd, options.m_add_to_history, tmp_result,
                       nullptr, /* override_context */
                       true,    /* repeat_on_empty_command */
-                      override_context != nullptr /* no_context_switching */);
+                      override_context != nullptr /* no_context_switching */,
+                      options.GetAddToReproducer());
     if (!options.GetAddToHistory())
       m_command_source_depth--;
 
@@ -2364,7 +2420,8 @@
   eHandleCommandFlagEchoCommand = (1u << 2),
   eHandleCommandFlagEchoCommentCommand = (1u << 3),
   eHandleCommandFlagPrintResult = (1u << 4),
-  eHandleCommandFlagStopOnCrash = (1u << 5)
+  eHandleCommandFlagStopOnCrash = (1u << 5),
+  eHandleCommandFlagAddToReproducer = (1u << 6)
 };
 
 void CommandInterpreter::HandleCommandsFromFile(
@@ -2463,6 +2520,10 @@
     flags |= eHandleCommandFlagPrintResult;
   }
 
+  if (options.GetAddToReproducer()) {
+    flags |= eHandleCommandFlagAddToReproducer;
+  }
+
   if (flags & eHandleCommandFlagPrintResult) {
     debugger.GetOutputFile()->Printf("Executing commands in '%s'.\n",
                                      cmd_file_path.c_str());
@@ -2799,7 +2860,8 @@
   StartHandlingCommand();
 
   lldb_private::CommandReturnObject result;
-  HandleCommand(line.c_str(), eLazyBoolCalculate, result);
+  HandleCommand(line.c_str(), eLazyBoolCalculate, result, nullptr, true, false,
+                io_handler.GetFlags().Test(eHandleCommandFlagAddToReproducer));
 
   // Now emit the command output text from the command we just executed
   if (io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) {
@@ -2969,6 +3031,8 @@
         flags |= eHandleCommandFlagEchoCommentCommand;
       if (options->m_print_results != eLazyBoolNo)
         flags |= eHandleCommandFlagPrintResult;
+      if (options->m_add_to_reproducer != eLazyBoolNo)
+        flags |= eHandleCommandFlagAddToReproducer;
     } else {
       flags = eHandleCommandFlagEchoCommand | eHandleCommandFlagPrintResult;
     }
Index: lldb/source/Commands/CommandObjectReproducer.cpp
===================================================================
--- lldb/source/Commands/CommandObjectReproducer.cpp
+++ lldb/source/Commands/CommandObjectReproducer.cpp
@@ -37,8 +37,13 @@
     auto &r = repro::Reproducer::Instance();
     if (auto generator = r.GetGenerator()) {
       generator->Keep();
+    } else if (r.GetLoader()) {
+      // Make this operation a NOP in replay mode.
+      result.SetStatus(eReturnStatusSuccessFinishNoResult);
+      return result.Succeeded();
     } else {
       result.AppendErrorWithFormat("Unable to get the reproducer generator");
+      result.SetStatus(eReturnStatusFailed);
       return false;
     }
 
Index: lldb/lit/Reproducer/TestCommandRepro.test
===================================================================
--- /dev/null
+++ lldb/lit/Reproducer/TestCommandRepro.test
@@ -0,0 +1,11 @@
+# This tests the recording of commands
+
+# RUN: %lldb -x -b -o 'expr 1' -s %S/Inputs/CommandRepro.in -o 'expr 4' -o 'reproducer generate' --capture %t.repro
+# RUN: cat %t.repro/command-interpreter.txt | FileCheck %s
+
+# CHECK: expr 1
+# CHECK: command source
+# CHECK-NOT: expr 2
+# CHECK-NOT: expr 3
+# CHECK: expr 4
+# CHECK: reproducer generate
Index: lldb/lit/Reproducer/Inputs/CommandRepro.in
===================================================================
--- /dev/null
+++ lldb/lit/Reproducer/Inputs/CommandRepro.in
@@ -0,0 +1,2 @@
+expr 2
+expr 3
Index: lldb/include/lldb/Interpreter/CommandInterpreter.h
===================================================================
--- lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -27,6 +27,14 @@
 
 namespace lldb_private {
 
+/// Reproducer provider for the command interpreter. The info struct needs to
+/// be public because replay takes place at the SB API layer.
+class CommandProvider;
+struct CommandProviderInfo {
+  static const char *name;
+  static const char *file;
+};
+
 class CommandInterpreterRunOptions {
 public:
   //------------------------------------------------------------------
@@ -135,6 +143,12 @@
     m_add_to_history = add_to_history ? eLazyBoolYes : eLazyBoolNo;
   }
 
+  bool GetAddToReproducer() const { return DefaultToYes(m_add_to_reproducer); }
+
+  void SetAddToReproducer(bool add_to_reproducer) {
+    m_add_to_reproducer = add_to_reproducer ? eLazyBoolYes : eLazyBoolNo;
+  }
+
   LazyBool m_stop_on_continue;
   LazyBool m_stop_on_error;
   LazyBool m_stop_on_crash;
@@ -142,6 +156,7 @@
   LazyBool m_echo_comment_commands;
   LazyBool m_print_results;
   LazyBool m_add_to_history;
+  LazyBool m_add_to_reproducer;
 
 private:
   static bool DefaultToYes(LazyBool flag) {
@@ -250,7 +265,8 @@
                      CommandReturnObject &result,
                      ExecutionContext *override_context = nullptr,
                      bool repeat_on_empty_command = true,
-                     bool no_context_switching = false);
+                     bool no_context_switching = false,
+                     bool add_to_reproducer = false);
 
   bool WasInterrupted() const;
 
@@ -606,6 +622,9 @@
   bool m_quit_requested;
   bool m_stopped_for_crash;
 
+  /// Reproducer provider.
+  CommandProvider *m_provider = nullptr;
+
   // The exit code the user has requested when calling the 'quit' command.
   // No value means the user hasn't set a custom exit code so far.
   llvm::Optional<int> m_quit_exit_code;
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to