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: //------------------------------------------------------------------ @@ -606,6 +614,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 m_quit_exit_code; 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/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 { +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 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(); } bool CommandInterpreter::GetExpandRegexAliases() const { @@ -1694,6 +1744,9 @@ Status error(PreprocessCommand(command_string)); + if (m_provider) + m_provider->CaptureCommand(original_command_string); + if (error.Fail()) { result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed);