Index: lldb/include/lldb/Utility/ReproducerProvider.h =================================================================== --- lldb/include/lldb/Utility/ReproducerProvider.h +++ lldb/include/lldb/Utility/ReproducerProvider.h @@ -400,6 +400,16 @@ return std::string(llvm::StringRef(*dir).rtrim()); } +class Verifier { +public: + Verifier(Loader *loader) : m_loader(loader) {} + void Verify(std::function error_callback, + std::function warning_callback) const; + +private: + Loader *m_loader; +}; + } // namespace repro } // namespace lldb_private Index: lldb/source/Commands/CommandObjectReproducer.cpp =================================================================== --- lldb/source/Commands/CommandObjectReproducer.cpp +++ lldb/source/Commands/CommandObjectReproducer.cpp @@ -116,6 +116,9 @@ #define LLDB_OPTIONS_reproducer_xcrash #include "CommandOptions.inc" +#define LLDB_OPTIONS_reproducer_verify +#include "CommandOptions.inc" + template llvm::Expected static ReadFromYAML(StringRef filename) { auto error_or_file = MemoryBuffer::getFile(filename); @@ -583,6 +586,114 @@ CommandOptions m_options; }; +class CommandObjectReproducerVerify : public CommandObjectParsed { +public: + CommandObjectReproducerVerify(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "reproducer verify", + "Verify the contents of a reproducer. " + "If no reproducer is specified during replay, it " + "dumps the content of the current reproducer.", + nullptr) {} + + ~CommandObjectReproducerVerify() 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; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + file.Clear(); + } + + ArrayRef GetDefinitions() override { + return makeArrayRef(g_reproducer_verify_options); + } + + FileSpec file; + }; + +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_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); + + bool errors = false; + auto error_callback = [&](llvm::StringRef error) { + result.AppendError(error); + }; + + bool warnings = false; + auto warning_callback = [&](llvm::StringRef warning) { + warnings = true; + result.AppendWarning(warning); + }; + + Verifier verifier(loader); + verifier.Verify(error_callback, warning_callback); + + if (warnings || errors) { + result.AppendMessage("reproducer verification failed"); + result.SetStatus(eReturnStatusFailed); + } else { + result.AppendMessage("reproducer verification succeeded"); + result.SetStatus(eReturnStatusSuccessFinishResult); + } + + return result.Succeeded(); + } + +private: + CommandOptions m_options; +}; + CommandObjectReproducer::CommandObjectReproducer( CommandInterpreter &interpreter) : CommandObjectMultiword( @@ -605,6 +716,8 @@ new CommandObjectReproducerStatus(interpreter))); LoadSubCommand("dump", CommandObjectSP(new CommandObjectReproducerDump(interpreter))); + LoadSubCommand("verify", CommandObjectSP( + new CommandObjectReproducerVerify(interpreter))); LoadSubCommand("xcrash", CommandObjectSP( new CommandObjectReproducerXCrash(interpreter))); } Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -202,8 +202,8 @@ Desc<"Move breakpoints to nearest code. If not set the " "target.move-to-nearest-codesetting is used.">; def breakpoint_set_file_colon_line : Option<"joint-specifier", "y">, Group<12>, Arg<"FileLineColumn">, - Required, Completion<"SourceFile">, - Desc<"A specifier in the form filename:line[:column] for setting file & line breakpoints.">; + Required, Completion<"SourceFile">, + Desc<"A specifier in the form filename:line[:column] for setting file & line breakpoints.">; /* Don't add this option till it actually does something useful... def breakpoint_set_exception_typename : Option<"exception-typename", "O">, Arg<"TypeName">, Desc<"The breakpoint will only stop if an " @@ -451,6 +451,12 @@ "provided, that reproducer is dumped.">; } +let Command = "reproducer verify" in { + def reproducer_verify_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 = "reproducer xcrash" in { def reproducer_signal : Option<"signal", "s">, Group<1>, EnumArg<"None", "ReproducerSignalType()">, @@ -751,7 +757,7 @@ def source_list_reverse : Option<"reverse", "r">, Group<4>, Desc<"Reverse the" " listing to look backwards from the last displayed block of source.">; def source_list_file_colon_line : Option<"joint-specifier", "y">, Group<5>, - Arg<"FileLineColumn">, Completion<"SourceFile">, + Arg<"FileLineColumn">, Completion<"SourceFile">, Desc<"A specifier in the form filename:line[:column] from which to display" " source.">; } Index: lldb/source/Utility/ReproducerProvider.cpp =================================================================== --- lldb/source/Utility/ReproducerProvider.cpp +++ lldb/source/Utility/ReproducerProvider.cpp @@ -9,6 +9,7 @@ #include "lldb/Utility/ReproducerProvider.h" #include "lldb/Utility/ProcessInfo.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" using namespace lldb_private; @@ -160,6 +161,87 @@ FileSpec(it->symbol_path)); } +void Verifier::Verify( + std::function error_callback, + std::function warning_callback) const { + if (!m_loader) { + error_callback("invalid loader"); + return; + } + + FileSpec vfs_mapping = m_loader->GetFile(); + ErrorOr> buffer = + vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath()); + if (!buffer) { + error_callback("unable to read files: " + buffer.getError().message()); + return; + } + + IntrusiveRefCntPtr vfs = vfs::getVFSFromYAML( + std::move(buffer.get()), nullptr, vfs_mapping.GetPath()); + if (!vfs) { + error_callback("unable to initialize the virtual file system"); + return; + } + + auto &redirecting_vfs = static_cast(*vfs); + redirecting_vfs.setFallthrough(false); + + llvm::Expected working_dir = + GetDirectoryFrom(m_loader); + if (working_dir) { + if (!vfs->exists(*working_dir)) + warning_callback("working directory '" + *working_dir + "' not in VFS"); + vfs->setCurrentWorkingDirectory(*working_dir); + } else { + warning_callback("no working directory in reproducer: " + + toString(working_dir.takeError())); + } + + llvm::Expected home_dir = + GetDirectoryFrom(m_loader); + if (home_dir) { + if (!vfs->exists(*working_dir)) + warning_callback("home directory '" + *working_dir + "' not in VFS"); + } else { + warning_callback("no home directory in reproducer: " + + toString(working_dir.takeError())); + } + + Expected symbol_files = + m_loader->LoadBuffer(); + if (symbol_files) { + std::vector entries; + llvm::yaml::Input yin(*symbol_files); + yin >> entries; + for (const auto &entry : entries) { + if (!entry.module_path.empty() && !vfs->exists(entry.module_path)) { + warning_callback("'" + entry.module_path + "': module path for " + + entry.uuid + " not in VFS"); + } + if (!entry.symbol_path.empty() && !vfs->exists(entry.symbol_path)) { + warning_callback("'" + entry.symbol_path + "': symbol path for " + + entry.uuid + " not in VFS"); + } + } + } else { + llvm::consumeError(symbol_files.takeError()); + } + + std::vector roots = redirecting_vfs.getRoots(); + for (llvm::StringRef root : roots) { + std::error_code ec; + vfs::recursive_directory_iterator iter(*vfs, root, ec); + vfs::recursive_directory_iterator end; + for (; iter != end && !ec; iter.increment(ec)) { + ErrorOr status = vfs->status(iter->path()); + if (!status) + warning_callback("'" + iter->path().str() + + "': " + status.getError().message()); + } + } +} + void ProviderBase::anchor() {} char CommandProvider::ID = 0; char FileProvider::ID = 0; Index: llvm/include/llvm/Support/VirtualFileSystem.h =================================================================== --- llvm/include/llvm/Support/VirtualFileSystem.h +++ llvm/include/llvm/Support/VirtualFileSystem.h @@ -749,6 +749,10 @@ StringRef getExternalContentsPrefixDir() const; + void setFallthrough(bool Fallthrough); + + std::vector getRoots() 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) Index: llvm/lib/Support/VirtualFileSystem.cpp =================================================================== --- llvm/lib/Support/VirtualFileSystem.cpp +++ llvm/lib/Support/VirtualFileSystem.cpp @@ -1159,6 +1159,17 @@ return ExternalContentsPrefixDir; } +void RedirectingFileSystem::setFallthrough(bool Fallthrough) { + IsFallthrough = Fallthrough; +} + +std::vector RedirectingFileSystem::getRoots() const { + std::vector R; + for (const auto &Root : Roots) + R.push_back(Root->getName()); + return R; +} + void RedirectingFileSystem::dump(raw_ostream &OS) const { for (const auto &Root : Roots) dumpEntry(OS, Root.get());