//===-- CommandObjectSource.cpp ---------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "CommandObjectSource.h"

// C Includes
// C++ Includes
// Other libraries and framework includes
// Project includes
#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/CommandCompletions.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/Options.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/Target/TargetList.h"
#include "lldb/Utility/FileSpec.h"

using namespace lldb;
using namespace lldb_private;

#pragma mark CommandObjectSourceInfo
//----------------------------------------------------------------------
// CommandObjectSourceInfo - debug line entries dumping command
//----------------------------------------------------------------------

static OptionDefinition g_source_info_options[] = {
    // clang-format off
  { LLDB_OPT_SET_ALL,                false, "count",    'c', OptionParser::eRequiredArgument, nullptr, nullptr, 0,                                         eArgTypeCount,               "The number of line entries to display." },
  { LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "shlib",    's', OptionParser::eRequiredArgument, nullptr, nullptr, CommandCompletions::eModuleCompletion,     eArgTypeShlibName,           "Look up the source in the given module or shared library (can be specified more than once)." },
  { LLDB_OPT_SET_1,                  false, "file",     'f', OptionParser::eRequiredArgument, nullptr, nullptr, CommandCompletions::eSourceFileCompletion, eArgTypeFilename,            "The file from which to display source." },
  { LLDB_OPT_SET_1,                  false, "line",     'l', OptionParser::eRequiredArgument, nullptr, nullptr, 0,                                         eArgTypeLineNum,             "The line number at which to start the displaying lines." },
  { LLDB_OPT_SET_1,                  false, "end-line", 'e', OptionParser::eRequiredArgument, nullptr, nullptr, 0,                                         eArgTypeLineNum,             "The line number at which to stop displaying lines." },
  { LLDB_OPT_SET_2,                  false, "name",     'n', OptionParser::eRequiredArgument, nullptr, nullptr, CommandCompletions::eSymbolCompletion,     eArgTypeSymbol,              "The name of a function whose source to display." },
  { LLDB_OPT_SET_3,                  false, "address",  'a', OptionParser::eRequiredArgument, nullptr, nullptr, 0,                                         eArgTypeAddressOrExpression, "Lookup the address and display the source information for the corresponding file and line." },
    // clang-format on
};

class CommandObjectSourceInfo : public CommandObjectParsed {
  class CommandOptions : public Options {
  public:
    CommandOptions() : Options() {}

    ~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 = option_arg;
        break;

      case 'n':
        symbol_name = option_arg;
        break;

      case 'a': {
        address = Args::StringToAddress(execution_context, option_arg,
                                        LLDB_INVALID_ADDRESS, &error);
      } break;
      case 's':
        modules.push_back(std::string(option_arg));
        break;
      default:
        error.SetErrorStringWithFormat("unrecognized short option '%c'",
                                       short_option);
        break;
      }

      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::makeArrayRef(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;
    STLStringArray 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),
        m_options() {}

  ~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;
    bool has_path = false;
    if (file_spec) {
      assert(file_spec.GetFilename().AsCString());
      has_path = (file_spec.GetDirectory().AsCString() != nullptr);
    }

    // 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 (file_spec &&
            !lldb_private::FileSpec::Equal(file_spec, line_entry.file,
                                           has_path))
          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.
        const 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.
        const ConstString &file_spec_name = file_spec.GetFilename();
        const 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(lldb_private::FileSpec::Equal(cu_file_spec, line_entry.file,
                                                 has_path));
            if (!cu_header_printed) {
              if (num_matches > 0)
                strm << "\n\n";
              strm << "Lines found for file " << file_spec_name
                   << " in compilation unit " << cu->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();

    // 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();
    size_t num_matches =
        module_list.FindFunctions(name, eFunctionNameTypeAuto,
                                  /*include_symbols=*/false,
                                  /*include_inlines=*/true,
                                  /*append=*/true, sc_list_funcs);
    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;
      size_t num_symbol_matches = module_list.FindFunctionSymbols(
          name, eFunctionNameTypeAuto, sc_list_symbols);
      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, false);
    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 {
    const size_t argc = command.GetArgumentCount();

    if (argc != 0) {
      result.AppendErrorWithFormat("'%s' takes no arguments, only flags.\n",
                                   GetCommandName().str().c_str());
      result.SetStatus(eReturnStatusFailed);
      return false;
    }

    Target *target = m_exe_ctx.GetTargetPtr();
    if (target == nullptr) {
      target = m_interpreter.GetDebugger().GetSelectedTarget().get();
      if (target == nullptr) {
        result.AppendError("invalid target, create a debug target using the "
                           "'target create' command.");
        result.SetStatus(eReturnStatusFailed);
        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], false);
        if (module_file_spec) {
          ModuleSpec module_spec(module_file_spec);
          if (target->GetImages().FindModules(module_spec, m_module_list) == 0)
            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.");
        result.SetStatus(eReturnStatusFailed);
        return false;
      }
    } else if (target->GetImages().GetSize() == 0) {
      result.AppendError("The target has no associated executable images.");
      result.SetStatus(eReturnStatusFailed);
      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
//-------------------------------------------------------------------------

static OptionDefinition g_source_list_options[] = {
    // clang-format off
  { LLDB_OPT_SET_ALL,                false, "count",            'c', OptionParser::eRequiredArgument, nullptr, nullptr, 0,                                         eArgTypeCount,               "The number of source lines to display." },
  { LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "shlib",            's', OptionParser::eRequiredArgument, nullptr, nullptr, CommandCompletions::eModuleCompletion,     eArgTypeShlibName,           "Look up the source file in the given shared library." },
  { LLDB_OPT_SET_ALL,                false, "show-breakpoints", 'b', OptionParser::eNoArgument,       nullptr, nullptr, 0,                                         eArgTypeNone,                "Show the line table locations from the debug information that indicate valid places to set source level breakpoints." },
  { LLDB_OPT_SET_1,                  false, "file",             'f', OptionParser::eRequiredArgument, nullptr, nullptr, CommandCompletions::eSourceFileCompletion, eArgTypeFilename,            "The file from which to display source." },
  { LLDB_OPT_SET_1,                  false, "line",             'l', OptionParser::eRequiredArgument, nullptr, nullptr, 0,                                         eArgTypeLineNum,             "The line number at which to start the display source." },
  { LLDB_OPT_SET_2,                  false, "name",             'n', OptionParser::eRequiredArgument, nullptr, nullptr, CommandCompletions::eSymbolCompletion,     eArgTypeSymbol,              "The name of a function whose source to display." },
  { LLDB_OPT_SET_3,                  false, "address",          'a', OptionParser::eRequiredArgument, nullptr, nullptr, 0,                                         eArgTypeAddressOrExpression, "Lookup the address and display the source information for the corresponding file and line." },
  { LLDB_OPT_SET_4,                  false, "reverse",          'r', OptionParser::eNoArgument,       nullptr, nullptr, 0,                                         eArgTypeNone,                "Reverse the listing to look backwards from the last displayed block of source." },
    // clang-format on
};

class CommandObjectSourceList : public CommandObjectParsed {
  class CommandOptions : public Options {
  public:
    CommandOptions() : Options() {}

    ~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 = option_arg;
        break;

      case 'n':
        symbol_name = option_arg;
        break;

      case 'a': {
        address = Args::StringToAddress(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;
      default:
        error.SetErrorStringWithFormat("unrecognized short option '%c'",
                                       short_option);
        break;
      }

      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::makeArrayRef(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;
    STLStringArray 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),
        m_options() {}

  ~CommandObjectSourceList() override = default;

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

  const char *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.c_str();

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

protected:
  struct SourceInfo {
    ConstString function;
    LineEntry line_entry;

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

    SourceInfo() : function(), line_entry() {}

    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());
          result.SetStatus(eReturnStatusFailed);
          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, 0, m_options.num_lines, column, "",
          &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.
  size_t FindMatchingFunctions(Target *target, const ConstString &name,
                               SymbolContextList &sc_list) {
    // Displaying the source for a symbol:
    bool include_inlines = true;
    bool append = true;
    bool include_symbols = false;
    size_t num_matches = 0;

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

    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], false);
        if (module_file_spec) {
          ModuleSpec module_spec(module_file_spec);
          matching_modules.Clear();
          target->GetImages().FindModules(module_spec, matching_modules);
          num_matches += matching_modules.FindFunctions(
              name, eFunctionNameTypeAuto, include_symbols, include_inlines,
              append, sc_list);
        }
      }
    } else {
      num_matches = target->GetImages().FindFunctions(
          name, eFunctionNameTypeAuto, include_symbols, include_inlines, append,
          sc_list);
    }
    return num_matches;
  }

  size_t FindMatchingFunctionSymbols(Target *target, const ConstString &name,
                                     SymbolContextList &sc_list) {
    size_t num_matches = 0;
    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], false);
        if (module_file_spec) {
          ModuleSpec module_spec(module_file_spec);
          matching_modules.Clear();
          target->GetImages().FindModules(module_spec, matching_modules);
          num_matches += matching_modules.FindFunctionSymbols(
              name, eFunctionNameTypeAuto, sc_list);
        }
      }
    } else {
      num_matches = target->GetImages().FindFunctionSymbols(
          name, eFunctionNameTypeAuto, sc_list);
    }
    return num_matches;
  }

  bool DoExecute(Args &command, CommandReturnObject &result) override {
    const size_t argc = command.GetArgumentCount();

    if (argc != 0) {
      result.AppendErrorWithFormat("'%s' takes no arguments, only flags.\n",
                                   GetCommandName().str().c_str());
      result.SetStatus(eReturnStatusFailed);
      return false;
    }

    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.
      size_t num_matches = FindMatchingFunctions(target, name, sc_list);
      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;
        size_t num_symbol_matches =
            FindMatchingFunctionSymbols(target, name, sc_list_symbols);
        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());
        result.SetStatus(eReturnStatusFailed);
        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);
          result.SetStatus(eReturnStatusFailed);
          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());
              result.SetStatus(eReturnStatusFailed);
              return false;
            }
          }
        }

        if (sc_list.GetSize() == 0) {
          result.AppendErrorWithFormat(
              "no modules contain load address 0x%" PRIx64 ".\n",
              m_options.address);
          result.SetStatus(eReturnStatusFailed);
          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, 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 =
              (m_interpreter.GetDebugger().GetStopShowColumn() !=
               eStopShowColumnNone)
                  ? sc.line_entry.column
                  : 0;
          target->GetSourceManager().DisplaySourceLinesWithLineNumbers(
              sc.comp_unit, sc.line_entry.line, lines_to_back_up, column,
              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], false);
          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,
                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());
        result.SetStatus(eReturnStatusFailed);
        return false;
      }

      if (num_matches > 1) {
        bool got_multiple = false;
        FileSpec *test_cu_spec = nullptr;

        for (unsigned i = 0; i < num_matches; i++) {
          SymbolContext sc;
          sc_list.GetContextAtIndex(i, sc);
          if (sc.comp_unit) {
            if (test_cu_spec) {
              if (test_cu_spec != static_cast<FileSpec *>(sc.comp_unit))
                got_multiple = true;
              break;
            } else
              test_cu_spec = sc.comp_unit;
          }
        }
        if (got_multiple) {
          result.AppendErrorWithFormat(
              "Multiple source files found matching: \"%s.\"\n",
              m_options.file_name.c_str());
          result.SetStatus(eReturnStatusFailed);
          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, 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, m_options.start_line, 0, m_options.num_lines,
              column, "", &result.GetOutputStream(), GetBreakpointLocations());

          result.SetStatus(eReturnStatusSuccessFinishResult);
        } else {
          result.AppendErrorWithFormat("No comp unit found for: \"%s.\"\n",
                                       m_options.file_name.c_str());
          result.SetStatus(eReturnStatusFailed);
          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;
