//===-- CommandObjectSource.cpp -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "CommandObjectSource.h"

#include "lldb/Core/Debugger.h"
#include "lldb/Core/FileLineResolver.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/SourceManager.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/OptionArgParser.h"
#include "lldb/Interpreter/OptionValueFileColonLine.h"
#include "lldb/Interpreter/Options.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/Utility/FileSpec.h"
#include <optional>

using namespace lldb;
using namespace lldb_private;

#pragma mark CommandObjectSourceInfo
// CommandObjectSourceInfo - debug line entries dumping command
#define LLDB_OPTIONS_source_info
#include "CommandOptions.inc"

class CommandObjectSourceInfo : public CommandObjectParsed {
  class CommandOptions : public Options {
  public:
    CommandOptions() = default;

    ~CommandOptions() override = default;

    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
                          ExecutionContext *execution_context) override {
      Status error;
      const int short_option = GetDefinitions()[option_idx].short_option;
      switch (short_option) {
      case 'l':
        if (option_arg.getAsInteger(0, start_line))
          error.SetErrorStringWithFormat("invalid line number: '%s'",
                                         option_arg.str().c_str());
        break;

      case 'e':
        if (option_arg.getAsInteger(0, end_line))
          error.SetErrorStringWithFormat("invalid line number: '%s'",
                                         option_arg.str().c_str());
        break;

      case 'c':
        if (option_arg.getAsInteger(0, num_lines))
          error.SetErrorStringWithFormat("invalid line count: '%s'",
                                         option_arg.str().c_str());
        break;

      case 'f':
        file_name = std::string(option_arg);
        break;

      case 'n':
        symbol_name = std::string(option_arg);
        break;

      case 'a': {
        address = OptionArgParser::ToAddress(execution_context, option_arg,
                                             LLDB_INVALID_ADDRESS, &error);
      } break;
      case 's':
        modules.push_back(std::string(option_arg));
        break;
      default:
        llvm_unreachable("Unimplemented option");
      }

      return error;
    }

    void OptionParsingStarting(ExecutionContext *execution_context) override {
      file_spec.Clear();
      file_name.clear();
      symbol_name.clear();
      address = LLDB_INVALID_ADDRESS;
      start_line = 0;
      end_line = 0;
      num_lines = 0;
      modules.clear();
    }

    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
      return llvm::ArrayRef(g_source_info_options);
    }

    // Instance variables to hold the values for command options.
    FileSpec file_spec;
    std::string file_name;
    std::string symbol_name;
    lldb::addr_t address;
    uint32_t start_line;
    uint32_t end_line;
    uint32_t num_lines;
    std::vector<std::string> modules;
  };

public:
  CommandObjectSourceInfo(CommandInterpreter &interpreter)
      : CommandObjectParsed(
            interpreter, "source info",
            "Display source line information for the current target "
            "process.  Defaults to instruction pointer in current stack "
            "frame.",
            nullptr, eCommandRequiresTarget) {}

  ~CommandObjectSourceInfo() override = default;

  Options *GetOptions() override { return &m_options; }

protected:
  // Dump the line entries in each symbol context. Return the number of entries
  // found. If module_list is set, only dump lines contained in one of the
  // modules. If file_spec is set, only dump lines in the file. If the
  // start_line option was specified, don't print lines less than start_line.
  // If the end_line option was specified, don't print lines greater than
  // end_line. If the num_lines option was specified, dont print more than
  // num_lines entries.
  uint32_t DumpLinesInSymbolContexts(Stream &strm,
                                     const SymbolContextList &sc_list,
                                     const ModuleList &module_list,
                                     const FileSpec &file_spec) {
    uint32_t start_line = m_options.start_line;
    uint32_t end_line = m_options.end_line;
    uint32_t num_lines = m_options.num_lines;
    Target *target = m_exe_ctx.GetTargetPtr();

    uint32_t num_matches = 0;
    // Dump all the line entries for the file in the list.
    ConstString last_module_file_name;
    uint32_t num_scs = sc_list.GetSize();
    for (uint32_t i = 0; i < num_scs; ++i) {
      SymbolContext sc;
      sc_list.GetContextAtIndex(i, sc);
      if (sc.comp_unit) {
        Module *module = sc.module_sp.get();
        CompileUnit *cu = sc.comp_unit;
        const LineEntry &line_entry = sc.line_entry;
        assert(module && cu);

        // Are we looking for specific modules, files or lines?
        if (module_list.GetSize() &&
            module_list.GetIndexForModule(module) == LLDB_INVALID_INDEX32)
          continue;
        if (!FileSpec::Match(file_spec, line_entry.file))
          continue;
        if (start_line > 0 && line_entry.line < start_line)
          continue;
        if (end_line > 0 && line_entry.line > end_line)
          continue;
        if (num_lines > 0 && num_matches > num_lines)
          continue;

        // Print a new header if the module changed.
        ConstString module_file_name = module->GetFileSpec().GetFilename();
        assert(module_file_name);
        if (module_file_name != last_module_file_name) {
          if (num_matches > 0)
            strm << "\n\n";
          strm << "Lines found in module `" << module_file_name << "\n";
        }
        // Dump the line entry.
        line_entry.GetDescription(&strm, lldb::eDescriptionLevelBrief, cu,
                                  target, /*show_address_only=*/false);
        strm << "\n";
        last_module_file_name = module_file_name;
        num_matches++;
      }
    }
    return num_matches;
  }

  // Dump the requested line entries for the file in the compilation unit.
  // Return the number of entries found. If module_list is set, only dump lines
  // contained in one of the modules. If the start_line option was specified,
  // don't print lines less than start_line. If the end_line option was
  // specified, don't print lines greater than end_line. If the num_lines
  // option was specified, dont print more than num_lines entries.
  uint32_t DumpFileLinesInCompUnit(Stream &strm, Module *module,
                                   CompileUnit *cu, const FileSpec &file_spec) {
    uint32_t start_line = m_options.start_line;
    uint32_t end_line = m_options.end_line;
    uint32_t num_lines = m_options.num_lines;
    Target *target = m_exe_ctx.GetTargetPtr();

    uint32_t num_matches = 0;
    assert(module);
    if (cu) {
      assert(file_spec.GetFilename().AsCString());
      bool has_path = (file_spec.GetDirectory().AsCString() != nullptr);
      const FileSpecList &cu_file_list = cu->GetSupportFiles();
      size_t file_idx = cu_file_list.FindFileIndex(0, file_spec, has_path);
      if (file_idx != UINT32_MAX) {
        // Update the file to how it appears in the CU.
        const FileSpec &cu_file_spec =
            cu_file_list.GetFileSpecAtIndex(file_idx);

        // Dump all matching lines at or above start_line for the file in the
        // CU.
        ConstString file_spec_name = file_spec.GetFilename();
        ConstString module_file_name = module->GetFileSpec().GetFilename();
        bool cu_header_printed = false;
        uint32_t line = start_line;
        while (true) {
          LineEntry line_entry;

          // Find the lowest index of a line entry with a line equal to or
          // higher than 'line'.
          uint32_t start_idx = 0;
          start_idx = cu->FindLineEntry(start_idx, line, &cu_file_spec,
                                        /*exact=*/false, &line_entry);
          if (start_idx == UINT32_MAX)
            // No more line entries for our file in this CU.
            break;

          if (end_line > 0 && line_entry.line > end_line)
            break;

          // Loop through to find any other entries for this line, dumping
          // each.
          line = line_entry.line;
          do {
            num_matches++;
            if (num_lines > 0 && num_matches > num_lines)
              break;
            assert(cu_file_spec == line_entry.file);
            if (!cu_header_printed) {
              if (num_matches > 0)
                strm << "\n\n";
              strm << "Lines found for file " << file_spec_name
                   << " in compilation unit "
                   << cu->GetPrimaryFile().GetFilename() << " in `"
                   << module_file_name << "\n";
              cu_header_printed = true;
            }
            line_entry.GetDescription(&strm, lldb::eDescriptionLevelBrief, cu,
                                      target, /*show_address_only=*/false);
            strm << "\n";

            // Anymore after this one?
            start_idx++;
            start_idx = cu->FindLineEntry(start_idx, line, &cu_file_spec,
                                          /*exact=*/true, &line_entry);
          } while (start_idx != UINT32_MAX);

          // Try the next higher line, starting over at start_idx 0.
          line++;
        }
      }
    }
    return num_matches;
  }

  // Dump the requested line entries for the file in the module. Return the
  // number of entries found. If module_list is set, only dump lines contained
  // in one of the modules. If the start_line option was specified, don't print
  // lines less than start_line. If the end_line option was specified, don't
  // print lines greater than end_line. If the num_lines option was specified,
  // dont print more than num_lines entries.
  uint32_t DumpFileLinesInModule(Stream &strm, Module *module,
                                 const FileSpec &file_spec) {
    uint32_t num_matches = 0;
    if (module) {
      // Look through all the compilation units (CUs) in this module for ones
      // that contain lines of code from this source file.
      for (size_t i = 0; i < module->GetNumCompileUnits(); i++) {
        // Look for a matching source file in this CU.
        CompUnitSP cu_sp(module->GetCompileUnitAtIndex(i));
        if (cu_sp) {
          num_matches +=
              DumpFileLinesInCompUnit(strm, module, cu_sp.get(), file_spec);
        }
      }
    }
    return num_matches;
  }

  // Given an address and a list of modules, append the symbol contexts of all
  // line entries containing the address found in the modules and return the
  // count of matches.  If none is found, return an error in 'error_strm'.
  size_t GetSymbolContextsForAddress(const ModuleList &module_list,
                                     lldb::addr_t addr,
                                     SymbolContextList &sc_list,
                                     StreamString &error_strm) {
    Address so_addr;
    size_t num_matches = 0;
    assert(module_list.GetSize() > 0);
    Target *target = m_exe_ctx.GetTargetPtr();
    if (target->GetSectionLoadList().IsEmpty()) {
      // The target isn't loaded yet, we need to lookup the file address in all
      // modules.  Note: the module list option does not apply to addresses.
      const size_t num_modules = module_list.GetSize();
      for (size_t i = 0; i < num_modules; ++i) {
        ModuleSP module_sp(module_list.GetModuleAtIndex(i));
        if (!module_sp)
          continue;
        if (module_sp->ResolveFileAddress(addr, so_addr)) {
          SymbolContext sc;
          sc.Clear(true);
          if (module_sp->ResolveSymbolContextForAddress(
                  so_addr, eSymbolContextEverything, sc) &
              eSymbolContextLineEntry) {
            sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/false);
            ++num_matches;
          }
        }
      }
      if (num_matches == 0)
        error_strm.Printf("Source information for file address 0x%" PRIx64
                          " not found in any modules.\n",
                          addr);
    } else {
      // The target has some things loaded, resolve this address to a compile
      // unit + file + line and display
      if (target->GetSectionLoadList().ResolveLoadAddress(addr, so_addr)) {
        ModuleSP module_sp(so_addr.GetModule());
        // Check to make sure this module is in our list.
        if (module_sp && module_list.GetIndexForModule(module_sp.get()) !=
                             LLDB_INVALID_INDEX32) {
          SymbolContext sc;
          sc.Clear(true);
          if (module_sp->ResolveSymbolContextForAddress(
                  so_addr, eSymbolContextEverything, sc) &
              eSymbolContextLineEntry) {
            sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/false);
            ++num_matches;
          } else {
            StreamString addr_strm;
            so_addr.Dump(&addr_strm, nullptr,
                         Address::DumpStyleModuleWithFileAddress);
            error_strm.Printf(
                "Address 0x%" PRIx64 " resolves to %s, but there is"
                " no source information available for this address.\n",
                addr, addr_strm.GetData());
          }
        } else {
          StreamString addr_strm;
          so_addr.Dump(&addr_strm, nullptr,
                       Address::DumpStyleModuleWithFileAddress);
          error_strm.Printf("Address 0x%" PRIx64
                            " resolves to %s, but it cannot"
                            " be found in any modules.\n",
                            addr, addr_strm.GetData());
        }
      } else
        error_strm.Printf("Unable to resolve address 0x%" PRIx64 ".\n", addr);
    }
    return num_matches;
  }

  // Dump the line entries found in functions matching the name specified in
  // the option.
  bool DumpLinesInFunctions(CommandReturnObject &result) {
    SymbolContextList sc_list_funcs;
    ConstString name(m_options.symbol_name.c_str());
    SymbolContextList sc_list_lines;
    Target *target = m_exe_ctx.GetTargetPtr();
    uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize();

    ModuleFunctionSearchOptions function_options;
    function_options.include_symbols = false;
    function_options.include_inlines = true;

    // Note: module_list can't be const& because FindFunctionSymbols isn't
    // const.
    ModuleList module_list =
        (m_module_list.GetSize() > 0) ? m_module_list : target->GetImages();
    module_list.FindFunctions(name, eFunctionNameTypeAuto, function_options,
                              sc_list_funcs);
    size_t num_matches = sc_list_funcs.GetSize();

    if (!num_matches) {
      // If we didn't find any functions with that name, try searching for
      // symbols that line up exactly with function addresses.
      SymbolContextList sc_list_symbols;
      module_list.FindFunctionSymbols(name, eFunctionNameTypeAuto,
                                      sc_list_symbols);
      size_t num_symbol_matches = sc_list_symbols.GetSize();
      for (size_t i = 0; i < num_symbol_matches; i++) {
        SymbolContext sc;
        sc_list_symbols.GetContextAtIndex(i, sc);
        if (sc.symbol && sc.symbol->ValueIsAddress()) {
          const Address &base_address = sc.symbol->GetAddressRef();
          Function *function = base_address.CalculateSymbolContextFunction();
          if (function) {
            sc_list_funcs.Append(SymbolContext(function));
            num_matches++;
          }
        }
      }
    }
    if (num_matches == 0) {
      result.AppendErrorWithFormat("Could not find function named \'%s\'.\n",
                                   m_options.symbol_name.c_str());
      return false;
    }
    for (size_t i = 0; i < num_matches; i++) {
      SymbolContext sc;
      sc_list_funcs.GetContextAtIndex(i, sc);
      bool context_found_for_symbol = false;
      // Loop through all the ranges in the function.
      AddressRange range;
      for (uint32_t r = 0;
           sc.GetAddressRange(eSymbolContextEverything, r,
                              /*use_inline_block_range=*/true, range);
           ++r) {
        // Append the symbol contexts for each address in the range to
        // sc_list_lines.
        const Address &base_address = range.GetBaseAddress();
        const addr_t size = range.GetByteSize();
        lldb::addr_t start_addr = base_address.GetLoadAddress(target);
        if (start_addr == LLDB_INVALID_ADDRESS)
          start_addr = base_address.GetFileAddress();
        lldb::addr_t end_addr = start_addr + size;
        for (lldb::addr_t addr = start_addr; addr < end_addr;
             addr += addr_byte_size) {
          StreamString error_strm;
          if (!GetSymbolContextsForAddress(module_list, addr, sc_list_lines,
                                           error_strm))
            result.AppendWarningWithFormat("in symbol '%s': %s",
                                           sc.GetFunctionName().AsCString(),
                                           error_strm.GetData());
          else
            context_found_for_symbol = true;
        }
      }
      if (!context_found_for_symbol)
        result.AppendWarningWithFormat("Unable to find line information"
                                       " for matching symbol '%s'.\n",
                                       sc.GetFunctionName().AsCString());
    }
    if (sc_list_lines.GetSize() == 0) {
      result.AppendErrorWithFormat("No line information could be found"
                                   " for any symbols matching '%s'.\n",
                                   name.AsCString());
      return false;
    }
    FileSpec file_spec;
    if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list_lines,
                                   module_list, file_spec)) {
      result.AppendErrorWithFormat(
          "Unable to dump line information for symbol '%s'.\n",
          name.AsCString());
      return false;
    }
    return true;
  }

  // Dump the line entries found for the address specified in the option.
  bool DumpLinesForAddress(CommandReturnObject &result) {
    Target *target = m_exe_ctx.GetTargetPtr();
    SymbolContextList sc_list;

    StreamString error_strm;
    if (!GetSymbolContextsForAddress(target->GetImages(), m_options.address,
                                     sc_list, error_strm)) {
      result.AppendErrorWithFormat("%s.\n", error_strm.GetData());
      return false;
    }
    ModuleList module_list;
    FileSpec file_spec;
    if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list,
                                   module_list, file_spec)) {
      result.AppendErrorWithFormat("No modules contain load address 0x%" PRIx64
                                   ".\n",
                                   m_options.address);
      return false;
    }
    return true;
  }

  // Dump the line entries found in the file specified in the option.
  bool DumpLinesForFile(CommandReturnObject &result) {
    FileSpec file_spec(m_options.file_name);
    const char *filename = m_options.file_name.c_str();
    Target *target = m_exe_ctx.GetTargetPtr();
    const ModuleList &module_list =
        (m_module_list.GetSize() > 0) ? m_module_list : target->GetImages();

    bool displayed_something = false;
    const size_t num_modules = module_list.GetSize();
    for (uint32_t i = 0; i < num_modules; ++i) {
      // Dump lines for this module.
      Module *module = module_list.GetModulePointerAtIndex(i);
      assert(module);
      if (DumpFileLinesInModule(result.GetOutputStream(), module, file_spec))
        displayed_something = true;
    }
    if (!displayed_something) {
      result.AppendErrorWithFormat("No source filenames matched '%s'.\n",
                                   filename);
      return false;
    }
    return true;
  }

  // Dump the line entries for the current frame.
  bool DumpLinesForFrame(CommandReturnObject &result) {
    StackFrame *cur_frame = m_exe_ctx.GetFramePtr();
    if (cur_frame == nullptr) {
      result.AppendError(
          "No selected frame to use to find the default source.");
      return false;
    } else if (!cur_frame->HasDebugInformation()) {
      result.AppendError("No debug info for the selected frame.");
      return false;
    } else {
      const SymbolContext &sc =
          cur_frame->GetSymbolContext(eSymbolContextLineEntry);
      SymbolContextList sc_list;
      sc_list.Append(sc);
      ModuleList module_list;
      FileSpec file_spec;
      if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list,
                                     module_list, file_spec)) {
        result.AppendError(
            "No source line info available for the selected frame.");
        return false;
      }
    }
    return true;
  }

  bool DoExecute(Args &command, CommandReturnObject &result) override {
    Target *target = m_exe_ctx.GetTargetPtr();
    if (target == nullptr) {
      target = GetDebugger().GetSelectedTarget().get();
      if (target == nullptr) {
        result.AppendError("invalid target, create a debug target using the "
                           "'target create' command.");
        return false;
      }
    }

    uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize();
    result.GetOutputStream().SetAddressByteSize(addr_byte_size);
    result.GetErrorStream().SetAddressByteSize(addr_byte_size);

    // Collect the list of modules to search.
    m_module_list.Clear();
    if (!m_options.modules.empty()) {
      for (size_t i = 0, e = m_options.modules.size(); i < e; ++i) {
        FileSpec module_file_spec(m_options.modules[i]);
        if (module_file_spec) {
          ModuleSpec module_spec(module_file_spec);
          target->GetImages().FindModules(module_spec, m_module_list);
          if (m_module_list.IsEmpty())
            result.AppendWarningWithFormat("No module found for '%s'.\n",
                                           m_options.modules[i].c_str());
        }
      }
      if (!m_module_list.GetSize()) {
        result.AppendError("No modules match the input.");
        return false;
      }
    } else if (target->GetImages().GetSize() == 0) {
      result.AppendError("The target has no associated executable images.");
      return false;
    }

    // Check the arguments to see what lines we should dump.
    if (!m_options.symbol_name.empty()) {
      // Print lines for symbol.
      if (DumpLinesInFunctions(result))
        result.SetStatus(eReturnStatusSuccessFinishResult);
      else
        result.SetStatus(eReturnStatusFailed);
    } else if (m_options.address != LLDB_INVALID_ADDRESS) {
      // Print lines for an address.
      if (DumpLinesForAddress(result))
        result.SetStatus(eReturnStatusSuccessFinishResult);
      else
        result.SetStatus(eReturnStatusFailed);
    } else if (!m_options.file_name.empty()) {
      // Dump lines for a file.
      if (DumpLinesForFile(result))
        result.SetStatus(eReturnStatusSuccessFinishResult);
      else
        result.SetStatus(eReturnStatusFailed);
    } else {
      // Dump the line for the current frame.
      if (DumpLinesForFrame(result))
        result.SetStatus(eReturnStatusSuccessFinishResult);
      else
        result.SetStatus(eReturnStatusFailed);
    }
    return result.Succeeded();
  }

  CommandOptions m_options;
  ModuleList m_module_list;
};

#pragma mark CommandObjectSourceList
// CommandObjectSourceList
#define LLDB_OPTIONS_source_list
#include "CommandOptions.inc"

class CommandObjectSourceList : public CommandObjectParsed {
  class CommandOptions : public Options {
  public:
    CommandOptions() = default;

    ~CommandOptions() override = default;

    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
                          ExecutionContext *execution_context) override {
      Status error;
      const int short_option = GetDefinitions()[option_idx].short_option;
      switch (short_option) {
      case 'l':
        if (option_arg.getAsInteger(0, start_line))
          error.SetErrorStringWithFormat("invalid line number: '%s'",
                                         option_arg.str().c_str());
        break;

      case 'c':
        if (option_arg.getAsInteger(0, num_lines))
          error.SetErrorStringWithFormat("invalid line count: '%s'",
                                         option_arg.str().c_str());
        break;

      case 'f':
        file_name = std::string(option_arg);
        break;

      case 'n':
        symbol_name = std::string(option_arg);
        break;

      case 'a': {
        address = OptionArgParser::ToAddress(execution_context, option_arg,
                                             LLDB_INVALID_ADDRESS, &error);
      } break;
      case 's':
        modules.push_back(std::string(option_arg));
        break;

      case 'b':
        show_bp_locs = true;
        break;
      case 'r':
        reverse = true;
        break;
      case 'y':
      {
        OptionValueFileColonLine value;
        Status fcl_err = value.SetValueFromString(option_arg);
        if (!fcl_err.Success()) {
          error.SetErrorStringWithFormat(
              "Invalid value for file:line specifier: %s",
              fcl_err.AsCString());
        } else {
          file_name = value.GetFileSpec().GetPath();
          start_line = value.GetLineNumber();
          // I don't see anything useful to do with a column number, but I don't
          // want to complain since someone may well have cut and pasted a
          // listing from somewhere that included a column.
        }
      } break;
      default:
        llvm_unreachable("Unimplemented option");
      }

      return error;
    }

    void OptionParsingStarting(ExecutionContext *execution_context) override {
      file_spec.Clear();
      file_name.clear();
      symbol_name.clear();
      address = LLDB_INVALID_ADDRESS;
      start_line = 0;
      num_lines = 0;
      show_bp_locs = false;
      reverse = false;
      modules.clear();
    }

    llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
      return llvm::ArrayRef(g_source_list_options);
    }

    // Instance variables to hold the values for command options.
    FileSpec file_spec;
    std::string file_name;
    std::string symbol_name;
    lldb::addr_t address;
    uint32_t start_line;
    uint32_t num_lines;
    std::vector<std::string> modules;
    bool show_bp_locs;
    bool reverse;
  };

public:
  CommandObjectSourceList(CommandInterpreter &interpreter)
      : CommandObjectParsed(interpreter, "source list",
                            "Display source code for the current target "
                            "process as specified by options.",
                            nullptr, eCommandRequiresTarget) {}

  ~CommandObjectSourceList() override = default;

  Options *GetOptions() override { return &m_options; }

  std::optional<std::string> GetRepeatCommand(Args &current_command_args,
                                              uint32_t index) override {
    // This is kind of gross, but the command hasn't been parsed yet so we
    // can't look at the option values for this invocation...  I have to scan
    // the arguments directly.
    auto iter =
        llvm::find_if(current_command_args, [](const Args::ArgEntry &e) {
          return e.ref() == "-r" || e.ref() == "--reverse";
        });
    if (iter == current_command_args.end())
      return m_cmd_name;

    if (m_reverse_name.empty()) {
      m_reverse_name = m_cmd_name;
      m_reverse_name.append(" -r");
    }
    return m_reverse_name;
  }

protected:
  struct SourceInfo {
    ConstString function;
    LineEntry line_entry;

    SourceInfo(ConstString name, const LineEntry &line_entry)
        : function(name), line_entry(line_entry) {}

    SourceInfo() = default;

    bool IsValid() const { return (bool)function && line_entry.IsValid(); }

    bool operator==(const SourceInfo &rhs) const {
      return function == rhs.function &&
             line_entry.original_file == rhs.line_entry.original_file &&
             line_entry.line == rhs.line_entry.line;
    }

    bool operator!=(const SourceInfo &rhs) const {
      return function != rhs.function ||
             line_entry.original_file != rhs.line_entry.original_file ||
             line_entry.line != rhs.line_entry.line;
    }

    bool operator<(const SourceInfo &rhs) const {
      if (function.GetCString() < rhs.function.GetCString())
        return true;
      if (line_entry.file.GetDirectory().GetCString() <
          rhs.line_entry.file.GetDirectory().GetCString())
        return true;
      if (line_entry.file.GetFilename().GetCString() <
          rhs.line_entry.file.GetFilename().GetCString())
        return true;
      if (line_entry.line < rhs.line_entry.line)
        return true;
      return false;
    }
  };

  size_t DisplayFunctionSource(const SymbolContext &sc, SourceInfo &source_info,
                               CommandReturnObject &result) {
    if (!source_info.IsValid()) {
      source_info.function = sc.GetFunctionName();
      source_info.line_entry = sc.GetFunctionStartLineEntry();
    }

    if (sc.function) {
      Target *target = m_exe_ctx.GetTargetPtr();

      FileSpec start_file;
      uint32_t start_line;
      uint32_t end_line;
      FileSpec end_file;

      if (sc.block == nullptr) {
        // Not an inlined function
        sc.function->GetStartLineSourceInfo(start_file, start_line);
        if (start_line == 0) {
          result.AppendErrorWithFormat("Could not find line information for "
                                       "start of function: \"%s\".\n",
                                       source_info.function.GetCString());
          return 0;
        }
        sc.function->GetEndLineSourceInfo(end_file, end_line);
      } else {
        // We have an inlined function
        start_file = source_info.line_entry.file;
        start_line = source_info.line_entry.line;
        end_line = start_line + m_options.num_lines;
      }

      // This is a little hacky, but the first line table entry for a function
      // points to the "{" that starts the function block.  It would be nice to
      // actually get the function declaration in there too.  So back up a bit,
      // but not further than what you're going to display.
      uint32_t extra_lines;
      if (m_options.num_lines >= 10)
        extra_lines = 5;
      else
        extra_lines = m_options.num_lines / 2;
      uint32_t line_no;
      if (start_line <= extra_lines)
        line_no = 1;
      else
        line_no = start_line - extra_lines;

      // For fun, if the function is shorter than the number of lines we're
      // supposed to display, only display the function...
      if (end_line != 0) {
        if (m_options.num_lines > end_line - line_no)
          m_options.num_lines = end_line - line_no + extra_lines;
      }

      m_breakpoint_locations.Clear();

      if (m_options.show_bp_locs) {
        const bool show_inlines = true;
        m_breakpoint_locations.Reset(start_file, 0, show_inlines);
        SearchFilterForUnconstrainedSearches target_search_filter(
            m_exe_ctx.GetTargetSP());
        target_search_filter.Search(m_breakpoint_locations);
      }

      result.AppendMessageWithFormat("File: %s\n",
                                     start_file.GetPath().c_str());
      // We don't care about the column here.
      const uint32_t column = 0;
      return target->GetSourceManager().DisplaySourceLinesWithLineNumbers(
          start_file, line_no, column, 0, m_options.num_lines, "",
          &result.GetOutputStream(), GetBreakpointLocations());
    } else {
      result.AppendErrorWithFormat(
          "Could not find function info for: \"%s\".\n",
          m_options.symbol_name.c_str());
    }
    return 0;
  }

  // From Jim: The FindMatchingFunctions / FindMatchingFunctionSymbols
  // functions "take a possibly empty vector of strings which are names of
  // modules, and run the two search functions on the subset of the full module
  // list that matches the strings in the input vector". If we wanted to put
  // these somewhere, there should probably be a module-filter-list that can be
  // passed to the various ModuleList::Find* calls, which would either be a
  // vector of string names or a ModuleSpecList.
  void FindMatchingFunctions(Target *target, ConstString name,
                             SymbolContextList &sc_list) {
    // Displaying the source for a symbol:
    if (m_options.num_lines == 0)
      m_options.num_lines = 10;

    ModuleFunctionSearchOptions function_options;
    function_options.include_symbols = true;
    function_options.include_inlines = false;

    const size_t num_modules = m_options.modules.size();
    if (num_modules > 0) {
      ModuleList matching_modules;
      for (size_t i = 0; i < num_modules; ++i) {
        FileSpec module_file_spec(m_options.modules[i]);
        if (module_file_spec) {
          ModuleSpec module_spec(module_file_spec);
          matching_modules.Clear();
          target->GetImages().FindModules(module_spec, matching_modules);

          matching_modules.FindFunctions(name, eFunctionNameTypeAuto,
                                         function_options, sc_list);
        }
      }
    } else {
      target->GetImages().FindFunctions(name, eFunctionNameTypeAuto,
                                        function_options, sc_list);
    }
  }

  void FindMatchingFunctionSymbols(Target *target, ConstString name,
                                   SymbolContextList &sc_list) {
    const size_t num_modules = m_options.modules.size();
    if (num_modules > 0) {
      ModuleList matching_modules;
      for (size_t i = 0; i < num_modules; ++i) {
        FileSpec module_file_spec(m_options.modules[i]);
        if (module_file_spec) {
          ModuleSpec module_spec(module_file_spec);
          matching_modules.Clear();
          target->GetImages().FindModules(module_spec, matching_modules);
          matching_modules.FindFunctionSymbols(name, eFunctionNameTypeAuto,
                                               sc_list);
        }
      }
    } else {
      target->GetImages().FindFunctionSymbols(name, eFunctionNameTypeAuto,
                                              sc_list);
    }
  }

  bool DoExecute(Args &command, CommandReturnObject &result) override {
    Target *target = m_exe_ctx.GetTargetPtr();

    if (!m_options.symbol_name.empty()) {
      SymbolContextList sc_list;
      ConstString name(m_options.symbol_name.c_str());

      // Displaying the source for a symbol. Search for function named name.
      FindMatchingFunctions(target, name, sc_list);
      size_t num_matches = sc_list.GetSize();
      if (!num_matches) {
        // If we didn't find any functions with that name, try searching for
        // symbols that line up exactly with function addresses.
        SymbolContextList sc_list_symbols;
        FindMatchingFunctionSymbols(target, name, sc_list_symbols);
        size_t num_symbol_matches = sc_list_symbols.GetSize();

        for (size_t i = 0; i < num_symbol_matches; i++) {
          SymbolContext sc;
          sc_list_symbols.GetContextAtIndex(i, sc);
          if (sc.symbol && sc.symbol->ValueIsAddress()) {
            const Address &base_address = sc.symbol->GetAddressRef();
            Function *function = base_address.CalculateSymbolContextFunction();
            if (function) {
              sc_list.Append(SymbolContext(function));
              num_matches++;
              break;
            }
          }
        }
      }

      if (num_matches == 0) {
        result.AppendErrorWithFormat("Could not find function named: \"%s\".\n",
                                     m_options.symbol_name.c_str());
        return false;
      }

      if (num_matches > 1) {
        std::set<SourceInfo> source_match_set;

        bool displayed_something = false;
        for (size_t i = 0; i < num_matches; i++) {
          SymbolContext sc;
          sc_list.GetContextAtIndex(i, sc);
          SourceInfo source_info(sc.GetFunctionName(),
                                 sc.GetFunctionStartLineEntry());

          if (source_info.IsValid()) {
            if (source_match_set.find(source_info) == source_match_set.end()) {
              source_match_set.insert(source_info);
              if (DisplayFunctionSource(sc, source_info, result))
                displayed_something = true;
            }
          }
        }

        if (displayed_something)
          result.SetStatus(eReturnStatusSuccessFinishResult);
        else
          result.SetStatus(eReturnStatusFailed);
      } else {
        SymbolContext sc;
        sc_list.GetContextAtIndex(0, sc);
        SourceInfo source_info;

        if (DisplayFunctionSource(sc, source_info, result)) {
          result.SetStatus(eReturnStatusSuccessFinishResult);
        } else {
          result.SetStatus(eReturnStatusFailed);
        }
      }
      return result.Succeeded();
    } else if (m_options.address != LLDB_INVALID_ADDRESS) {
      Address so_addr;
      StreamString error_strm;
      SymbolContextList sc_list;

      if (target->GetSectionLoadList().IsEmpty()) {
        // The target isn't loaded yet, we need to lookup the file address in
        // all modules
        const ModuleList &module_list = target->GetImages();
        const size_t num_modules = module_list.GetSize();
        for (size_t i = 0; i < num_modules; ++i) {
          ModuleSP module_sp(module_list.GetModuleAtIndex(i));
          if (module_sp &&
              module_sp->ResolveFileAddress(m_options.address, so_addr)) {
            SymbolContext sc;
            sc.Clear(true);
            if (module_sp->ResolveSymbolContextForAddress(
                    so_addr, eSymbolContextEverything, sc) &
                eSymbolContextLineEntry)
              sc_list.Append(sc);
          }
        }

        if (sc_list.GetSize() == 0) {
          result.AppendErrorWithFormat(
              "no modules have source information for file address 0x%" PRIx64
              ".\n",
              m_options.address);
          return false;
        }
      } else {
        // The target has some things loaded, resolve this address to a compile
        // unit + file + line and display
        if (target->GetSectionLoadList().ResolveLoadAddress(m_options.address,
                                                            so_addr)) {
          ModuleSP module_sp(so_addr.GetModule());
          if (module_sp) {
            SymbolContext sc;
            sc.Clear(true);
            if (module_sp->ResolveSymbolContextForAddress(
                    so_addr, eSymbolContextEverything, sc) &
                eSymbolContextLineEntry) {
              sc_list.Append(sc);
            } else {
              so_addr.Dump(&error_strm, nullptr,
                           Address::DumpStyleModuleWithFileAddress);
              result.AppendErrorWithFormat("address resolves to %s, but there "
                                           "is no line table information "
                                           "available for this address.\n",
                                           error_strm.GetData());
              return false;
            }
          }
        }

        if (sc_list.GetSize() == 0) {
          result.AppendErrorWithFormat(
              "no modules contain load address 0x%" PRIx64 ".\n",
              m_options.address);
          return false;
        }
      }
      uint32_t num_matches = sc_list.GetSize();
      for (uint32_t i = 0; i < num_matches; ++i) {
        SymbolContext sc;
        sc_list.GetContextAtIndex(i, sc);
        if (sc.comp_unit) {
          if (m_options.show_bp_locs) {
            m_breakpoint_locations.Clear();
            const bool show_inlines = true;
            m_breakpoint_locations.Reset(sc.comp_unit->GetPrimaryFile(), 0,
                                         show_inlines);
            SearchFilterForUnconstrainedSearches target_search_filter(
                target->shared_from_this());
            target_search_filter.Search(m_breakpoint_locations);
          }

          bool show_fullpaths = true;
          bool show_module = true;
          bool show_inlined_frames = true;
          const bool show_function_arguments = true;
          const bool show_function_name = true;
          sc.DumpStopContext(&result.GetOutputStream(),
                             m_exe_ctx.GetBestExecutionContextScope(),
                             sc.line_entry.range.GetBaseAddress(),
                             show_fullpaths, show_module, show_inlined_frames,
                             show_function_arguments, show_function_name);
          result.GetOutputStream().EOL();

          if (m_options.num_lines == 0)
            m_options.num_lines = 10;

          size_t lines_to_back_up =
              m_options.num_lines >= 10 ? 5 : m_options.num_lines / 2;

          const uint32_t column =
              (GetDebugger().GetStopShowColumn() != eStopShowColumnNone)
                  ? sc.line_entry.column
                  : 0;
          target->GetSourceManager().DisplaySourceLinesWithLineNumbers(
              sc.comp_unit->GetPrimaryFile(), sc.line_entry.line, column,
              lines_to_back_up, m_options.num_lines - lines_to_back_up, "->",
              &result.GetOutputStream(), GetBreakpointLocations());
          result.SetStatus(eReturnStatusSuccessFinishResult);
        }
      }
    } else if (m_options.file_name.empty()) {
      // Last valid source manager context, or the current frame if no valid
      // last context in source manager. One little trick here, if you type the
      // exact same list command twice in a row, it is more likely because you
      // typed it once, then typed it again
      if (m_options.start_line == 0) {
        if (target->GetSourceManager().DisplayMoreWithLineNumbers(
                &result.GetOutputStream(), m_options.num_lines,
                m_options.reverse, GetBreakpointLocations())) {
          result.SetStatus(eReturnStatusSuccessFinishResult);
        }
      } else {
        if (m_options.num_lines == 0)
          m_options.num_lines = 10;

        if (m_options.show_bp_locs) {
          SourceManager::FileSP last_file_sp(
              target->GetSourceManager().GetLastFile());
          if (last_file_sp) {
            const bool show_inlines = true;
            m_breakpoint_locations.Reset(last_file_sp->GetFileSpec(), 0,
                                         show_inlines);
            SearchFilterForUnconstrainedSearches target_search_filter(
                target->shared_from_this());
            target_search_filter.Search(m_breakpoint_locations);
          }
        } else
          m_breakpoint_locations.Clear();

        const uint32_t column = 0;
        if (target->GetSourceManager()
                .DisplaySourceLinesWithLineNumbersUsingLastFile(
                    m_options.start_line, // Line to display
                    m_options.num_lines,  // Lines after line to
                    UINT32_MAX,           // Don't mark "line"
                    column,
                    "", // Don't mark "line"
                    &result.GetOutputStream(), GetBreakpointLocations())) {
          result.SetStatus(eReturnStatusSuccessFinishResult);
        }
      }
    } else {
      const char *filename = m_options.file_name.c_str();

      bool check_inlines = false;
      SymbolContextList sc_list;
      size_t num_matches = 0;

      if (!m_options.modules.empty()) {
        ModuleList matching_modules;
        for (size_t i = 0, e = m_options.modules.size(); i < e; ++i) {
          FileSpec module_file_spec(m_options.modules[i]);
          if (module_file_spec) {
            ModuleSpec module_spec(module_file_spec);
            matching_modules.Clear();
            target->GetImages().FindModules(module_spec, matching_modules);
            num_matches += matching_modules.ResolveSymbolContextForFilePath(
                filename, 0, check_inlines,
                SymbolContextItem(eSymbolContextModule |
                                  eSymbolContextCompUnit),
                sc_list);
          }
        }
      } else {
        num_matches = target->GetImages().ResolveSymbolContextForFilePath(
            filename, 0, check_inlines,
            eSymbolContextModule | eSymbolContextCompUnit, sc_list);
      }

      if (num_matches == 0) {
        result.AppendErrorWithFormat("Could not find source file \"%s\".\n",
                                     m_options.file_name.c_str());
        return false;
      }

      if (num_matches > 1) {
        bool got_multiple = false;
        CompileUnit *test_cu = nullptr;

        for (unsigned i = 0; i < num_matches; i++) {
          SymbolContext sc;
          sc_list.GetContextAtIndex(i, sc);
          if (sc.comp_unit) {
            if (test_cu) {
              if (test_cu != sc.comp_unit)
                got_multiple = true;
              break;
            } else
              test_cu = sc.comp_unit;
          }
        }
        if (got_multiple) {
          result.AppendErrorWithFormat(
              "Multiple source files found matching: \"%s.\"\n",
              m_options.file_name.c_str());
          return false;
        }
      }

      SymbolContext sc;
      if (sc_list.GetContextAtIndex(0, sc)) {
        if (sc.comp_unit) {
          if (m_options.show_bp_locs) {
            const bool show_inlines = true;
            m_breakpoint_locations.Reset(sc.comp_unit->GetPrimaryFile(), 0,
                                         show_inlines);
            SearchFilterForUnconstrainedSearches target_search_filter(
                target->shared_from_this());
            target_search_filter.Search(m_breakpoint_locations);
          } else
            m_breakpoint_locations.Clear();

          if (m_options.num_lines == 0)
            m_options.num_lines = 10;
          const uint32_t column = 0;
          target->GetSourceManager().DisplaySourceLinesWithLineNumbers(
              sc.comp_unit->GetPrimaryFile(), m_options.start_line, column, 0,
              m_options.num_lines, "", &result.GetOutputStream(),
              GetBreakpointLocations());

          result.SetStatus(eReturnStatusSuccessFinishResult);
        } else {
          result.AppendErrorWithFormat("No comp unit found for: \"%s.\"\n",
                                       m_options.file_name.c_str());
          return false;
        }
      }
    }
    return result.Succeeded();
  }

  const SymbolContextList *GetBreakpointLocations() {
    if (m_breakpoint_locations.GetFileLineMatches().GetSize() > 0)
      return &m_breakpoint_locations.GetFileLineMatches();
    return nullptr;
  }

  CommandOptions m_options;
  FileLineResolver m_breakpoint_locations;
  std::string m_reverse_name;
};

#pragma mark CommandObjectMultiwordSource
// CommandObjectMultiwordSource

CommandObjectMultiwordSource::CommandObjectMultiwordSource(
    CommandInterpreter &interpreter)
    : CommandObjectMultiword(interpreter, "source",
                             "Commands for examining "
                             "source code described by "
                             "debug information for the "
                             "current target process.",
                             "source <subcommand> [<subcommand-options>]") {
  LoadSubCommand("info",
                 CommandObjectSP(new CommandObjectSourceInfo(interpreter)));
  LoadSubCommand("list",
                 CommandObjectSP(new CommandObjectSourceList(interpreter)));
}

CommandObjectMultiwordSource::~CommandObjectMultiwordSource() = default;
