text: // Copyright 2018 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/developer/debug/zxdb/console/command_utils.h"
  
  #include <inttypes.h>
  #include <stdio.h>
  
  #include <limits>
  
  #include "src/developer/debug/zxdb/client/breakpoint.h"
  #include "src/developer/debug/zxdb/client/frame.h"
  #include "src/developer/debug/zxdb/client/job.h"
  #include "src/developer/debug/zxdb/client/job_context.h"
  #include "src/developer/debug/zxdb/client/process.h"
  #include "src/developer/debug/zxdb/client/target.h"
  #include "src/developer/debug/zxdb/client/thread.h"
  #include "src/developer/debug/zxdb/common/err.h"
  #include "src/developer/debug/zxdb/console/command.h"
  #include "src/developer/debug/zxdb/console/console_context.h"
  #include "src/developer/debug/zxdb/console/format_function.h"
  #include "src/developer/debug/zxdb/console/format_target.h"
  #include "src/developer/debug/zxdb/console/output_buffer.h"
  #include "src/developer/debug/zxdb/console/string_util.h"
  #include "src/developer/debug/zxdb/expr/eval_context_impl.h"
  #include "src/developer/debug/zxdb/expr/expr.h"
  #include "src/developer/debug/zxdb/expr/expr_parser.h"
  #include "src/developer/debug/zxdb/expr/expr_value.h"
  #include "src/developer/debug/zxdb/expr/number_parser.h"
  #include "src/developer/debug/zxdb/symbols/base_type.h"
  #include "src/developer/debug/zxdb/symbols/function.h"
  #include "src/developer/debug/zxdb/symbols/identifier.h"
  #include "src/developer/debug/zxdb/symbols/location.h"
  #include "src/developer/debug/zxdb/symbols/modified_type.h"
  #include "src/developer/debug/zxdb/symbols/process_symbols.h"
  #include "src/developer/debug/zxdb/symbols/symbol_utils.h"
  #include "src/developer/debug/zxdb/symbols/target_symbols.h"
  #include "src/developer/debug/zxdb/symbols/variable.h"
  #include "src/lib/fxl/logging.h"
  #include "src/lib/fxl/strings/string_printf.h"
  #include "src/lib/fxl/strings/trim.h"
  
  namespace zxdb {
  
  Err AssertRunningTarget(ConsoleContext* context, const char* command_name, Target* target) {
    Target::State state = target->GetState();
    if (state == Target::State::kRunning)
      return Err();
    return Err(ErrType::kInput,
               fxl::StringPrintf("%s requires a running process but process %d is %s.", command_name,
                                 context->IdForTarget(target), TargetStateToString(state)));
  }
  
  Err AssertStoppedThreadCommand(ConsoleContext* context, const Command& cmd, bool validate_nouns,
                                 const char* command_name) {
    Err err;
    if (validate_nouns) {
      err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread});
      if (err.has_error())
        return err;
    }
  
    if (!cmd.thread()) {
      return Err("\"%s\" requires a thread but there is no current thread.", command_name);
    }
    if (cmd.thread()->GetState() != debug_ipc::ThreadRecord::State::kBlocked &&
        cmd.thread()->GetState() != debug_ipc::ThreadRecord::State::kCoreDump &&
        cmd.thread()->GetState() != debug_ipc::ThreadRecord::State::kSuspended) {
      return Err(
          "\"%s\" requires a suspended thread but thread %d is %s.\n"
          "To view and sync thread state with the remote system, type "
          "\"thread\".",
          command_name, context->IdForThread(cmd.thread()),
          ThreadStateToString(cmd.thread()->GetState(), cmd.thread()->GetBlockedReason()).c_str());
    }
    return Err();
  }
  
  Err AssertStoppedThreadWithFrameCommand(ConsoleContext* context, const Command& cmd,
                                          const char* command_name) {
    // Does most validation except noun checking.
    Err err = AssertStoppedThreadCommand(context, cmd, false, command_name);
    if (err.has_error())
      return err;
  
    // Stopped threads should always have a frame.
    FXL_DCHECK(cmd.frame());
  
    return cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame});
  }
  
  size_t CheckHexPrefix(const std::string& s) {
    if (s.size() >= 2u && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
      return 2u;
    return 0u;
  }
  
  Err StringToInt(const std::string& s, int* out) {
    int64_t value64;
    Err err = StringToInt64(s, &value64);
    if (err.has_error())
      return err;
  
    // Range check it can be stored in an int.
    if (value64 < static_cast<int64_t>(std::numeric_limits<int>::min()) ||
        value64 > static_cast<int64_t>(std::numeric_limits<int>::max()))
      return Err("This value is too large for an integer.");
  
    *out = static_cast<int>(value64);
    return Err();
  }
  
  Err StringToInt64(const std::string& s, int64_t* out) {
    *out = 0;
  
    // StringToNumber expects pre-trimmed input.
    std::string trimmed = fxl::TrimString(s, " ").ToString();
  
    ExprValue number_value;
    Err err = StringToNumber(trimmed, &number_value);
    if (err.has_error())
      return err;
  
    // Be careful to read the number out in its original sign-edness.
    if (number_value.GetBaseType() == BaseType::kBaseTypeUnsigned) {
      uint64_t u64;
      err = number_value.PromoteTo64(&u64);
      if (err.has_error())
        return err;
  
      // Range-check that the unsigned value can be put in a signed.
      if (u64 > static_cast<uint64_t>(std::numeric_limits<int64_t>::max()))
        return Err("This value is too large.");
      *out = static_cast<int64_t>(u64);
      return Err();
    }
  
    // Expect everything else to be a signed number.
    if (number_value.GetBaseType() != BaseType::kBaseTypeSigned)
      return Err("This value is not the correct type.");
    return number_value.PromoteTo64(out);
  }
  
  Err StringToUint32(const std::string& s, uint32_t* out) {
    // Re-uses StringToUint64's and just size-checks the output.
    uint64_t value64;
    Err err = StringToUint64(s, &value64);
    if (err.has_error())
      return err;
  
    if (value64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
      return Err("Expected 32-bit unsigned value, but %s is too large.", s.c_str());
    }
    *out = static_cast<uint32_t>(value64);
    return Err();
  }
  
  Err StringToUint64(const std::string& s, uint64_t* out) {
    *out = 0;
  
    // StringToNumber expects pre-trimmed input.
    std::string trimmed = fxl::TrimString(s, " ").ToString();
  
    ExprValue number_value;
    Err err = StringToNumber(trimmed, &number_value);
    if (err.has_error())
      return err;
  
    // Be careful to read the number out in its original sign-edness.
    if (number_value.GetBaseType() == BaseType::kBaseTypeSigned) {
      int64_t s64;
      err = number_value.PromoteTo64(&s64);
      if (err.has_error())
        return err;
  
      // Range-check that the signed value can be put in an unsigned.
      if (s64 < 0)
        return Err("This value can not be negative.");
      *out = static_cast<uint64_t>(s64);
      return Err();
    }
  
    // Expect everything else to be an unsigned number.
    if (number_value.GetBaseType() != BaseType::kBaseTypeUnsigned)
      return Err("This value is not the correct type.");
    return number_value.PromoteTo64(out);
  }
  
  Err ReadUint64Arg(const Command& cmd, size_t arg_index, const char* param_desc, uint64_t* out) {
    if (cmd.args().size() <= arg_index) {
      return Err(ErrType::kInput,
                 fxl::StringPrintf("Not enough arguments when reading the %s.", param_desc));
    }
    Err result = StringToUint64(cmd.args()[arg_index], out);
    if (result.has_error()) {
      return Err(ErrType::kInput, fxl::StringPrintf("Invalid number \"%s\" when reading the %s.",
                                                    cmd.args()[arg_index].c_str(), param_desc));
    }
    return Err();
  }
  
  Err ParseHostPort(const std::string& in_host, const std::string& in_port, std::string* out_host,
                    uint16_t* out_port) {
    if (in_host.empty())
      return Err(ErrType::kInput, "No host component specified.");
    if (in_port.empty())
      return Err(ErrType::kInput, "No port component specified.");
  
    // Trim brackets from the host name for IPv6 addresses.
    if (in_host.front() == '[' && in_host.back() == ']')
      *out_host = in_host.substr(1, in_host.size() - 2);
    else
      *out_host = in_host;
  
    // Re-use paranoid int64 parsing.
    uint64_t port64;
    Err err = StringToUint64(in_port, &port64);
    if (err.has_error())
      return err;
    if (port64 == 0 || port64 > std::numeric_limits<uint16_t>::max())
      return Err(ErrType::kInput, "Port value out of range.");
    *out_port = static_cast<uint16_t>(port64);
  
    return Err();
  }
  
  Err ParseHostPort(const std::string& input, std::string* out_host, uint16_t* out_port) {
    // Separate based on the last colon.
    size_t colon = input.rfind(':');
    if (colon == std::string::npos)
      return Err(ErrType::kInput, "Expected colon to separate host/port.");
  
    // If the host has a colon in it, it could be an IPv6 address. In this case,
    // require brackets around it to differentiate the case where people
    // supplied an IPv6 address and we just picked out the last component above.
    std::string host = input.substr(0, colon);
    if (host.empty())
      return Err(ErrType::kInput, "No host component specified.");
    if (host.find(':') != std::string::npos) {
      if (host.front() != '[' || host.back() != ']') {
        return Err(ErrType::kInput,
                   "For IPv6 addresses use either: \"[::1]:1234\"\n"
                   "or the two-parameter form: \"::1 1234");
      }
    }
  
    std::string port = input.substr(colon + 1);
  
    return ParseHostPort(host, port, out_host, out_port);
  }
  
  std::string ThreadStateToString(debug_ipc::ThreadRecord::State state,
                                  debug_ipc::ThreadRecord::BlockedReason blocked_reason) {
    // Blocked can have many cases, so we handle it separately.
    if (state != debug_ipc::ThreadRecord::State::kBlocked)
      return debug_ipc::ThreadRecord::StateToString(state);
  
    FXL_DCHECK(blocked_reason != debug_ipc::ThreadRecord::BlockedReason::kNotBlocked)
        << "A blocked thread has to have a valid reason.";
    return fxl::StringPrintf("Blocked (%s)",
                             debug_ipc::ThreadRecord::BlockedReasonToString(blocked_reason));
  }
  
  std::string BreakpointScopeToString(const ConsoleContext* context,
                                      const BreakpointSettings& settings) {
    switch (settings.scope) {
      case BreakpointSettings::Scope::kSystem:
        return "Global";
      case BreakpointSettings::Scope::kTarget:
        return fxl::StringPrintf("pr %d", context->IdForTarget(settings.scope_target));
      case BreakpointSettings::Scope::kThread:
        return fxl::StringPrintf(
            "pr %d t %d", context->IdForTarget(settings.scope_thread->GetProcess()->GetTarget()),
            context->IdForThread(settings.scope_thread));
    }
    FXL_NOTREACHED();
    return std::string();
  }
  
  std::string BreakpointStopToString(BreakpointSettings::StopMode mode) {
    switch (mode) {
      case BreakpointSettings::StopMode::kNone:
        return "None";
      case BreakpointSettings::StopMode::kThread:
        return "Thread";
      case BreakpointSettings::StopMode::kProcess:
        return "Process";
      case BreakpointSettings::StopMode::kAll:
        return "All";
    }
    FXL_NOTREACHED();
    return std::string();
  }
  
  const char* BreakpointEnabledToString(bool enabled) { return enabled ? "Enabled" : "Disabled"; }
  
  std::string DescribeThread(const ConsoleContext* context, const Thread* thread) {
    return fxl::StringPrintf(
        "Thread %d [%s] koid=%" PRIu64 " %s", context->IdForThread(thread),
        ThreadStateToString(thread->GetState(), thread->GetBlockedReason()).c_str(),
        thread->GetKoid(), thread->GetName().c_str());
  }
  
  OutputBuffer FormatBreakpoint(const ConsoleContext* context, const Breakpoint* breakpoint) {
    BreakpointSettings settings = breakpoint->GetSettings();
  
    std::string scope = BreakpointScopeToString(context, settings);
    std::string stop = BreakpointStopToString(settings.stop_mode);
    const char* enabled = BreakpointEnabledToString(settings.enabled);
    const char* type = BreakpointTypeToString(settings.type);
    OutputBuffer location = FormatInputLocation(settings.location);
  
    OutputBuffer result("Breakpoint ");
    result.Append(Syntax::kSpecial, fxl::StringPrintf("%d", context->IdForBreakpoint(breakpoint)));
    result.Append(fxl::StringPrintf(" (%s) on %s, %s, stop=%s, @ ", type, scope.c_str(), enabled,
                                    stop.c_str()));
  
    result.Append(std::move(location));
    return result;
  }
  
  OutputBuffer FormatInputLocation(const InputLocation& location) {
    switch (location.type) {
      case InputLocation::Type::kNone:
        return OutputBuffer(Syntax::kComment, "<no location>");
      case InputLocation::Type::kLine:
        // Don't pass a TargetSymbols to DescribeFileLine because we always want
        // the full file name as passed-in by the user (as this is an "input"
        // location object). It is surprising if the debugger deletes some input.
        return OutputBuffer(DescribeFileLine(nullptr, location.line));
      case InputLocation::Type::kSymbol:
        return FormatIdentifier(location.symbol, true);
      case InputLocation::Type::kAddress:
        return OutputBuffer(fxl::StringPrintf("0x%" PRIx64, location.address));
    }
    FXL_NOTREACHED();
    return OutputBuffer();
  }
  
  OutputBuffer FormatIdentifier(const Identifier& identifier, bool bold_last) {
    return FormatIdentifier(ToParsedIdentifier(identifier), bold_last);
  }
  
  // This annoyingly duplicates Identifier::GetName but is required to get
  // syntax highlighting for all the components.
  OutputBuffer FormatIdentifier(const ParsedIdentifier& identifier, bool bold_last) {
    OutputBuffer result;
    if (identifier.qualification() == IdentifierQualification::kGlobal)
      result.Append(identifier.GetSeparator());
  
    const auto& comps = identifier.components();
    for (size_t i = 0; i < comps.size(); i++) {
      const auto& comp = comps[i];
      if (i > 0)
        result.Append(identifier.GetSeparator());
  
      // Name.
      if (bold_last && i == comps.size() - 1)
        result.Append(Syntax::kHeading, comp.name());
      else
        result.Append(Syntax::kNormal, comp.name());
  
      // Template.
      if (comp.has_template()) {
        std::string t_string("<");
  
        for (size_t t_i = 0; t_i < comp.template_contents().size(); t_i++) {
          if (t_i > 0)
            t_string += ", ";
          t_string += comp.template_contents()[t_i];
        }
  
        t_string.push_back('>');
        result.Append(Syntax::kComment, std::move(t_string));
      }
    }
  
    return result;
  }
  
  OutputBuffer FormatLocation(const TargetSymbols* optional_target_symbols, const Location& loc,
                              bool always_show_address, bool always_show_types) {
    if (!loc.is_valid())
      return OutputBuffer("<invalid address>");
    if (!loc.has_symbols())
      return OutputBuffer(fxl::StringPrintf("0x%" PRIx64, loc.address()));
  
    OutputBuffer result;
    if (always_show_address) {
      result = OutputBuffer(Syntax::kComment, fxl::StringPrintf("0x%" PRIx64 ", ", loc.address()));
    }
  
    const Function* func = loc.symbol().Get()->AsFunction();
    if (func) {
      OutputBuffer func_output = FormatFunctionName(func, always_show_types);
      if (!func_output.empty()) {
        result.Append(std::move(func_output));
        if (loc.file_line().is_valid()) {
          // Separator between function and file/line.
          result.Append(" " + GetBullet() + " ");
        } else {
          // Check if the address is inside a function and show the offset.
          AddressRange function_range = func->GetFullRange(loc.symbol_context());
          if (function_range.InRange(loc.address())) {
            // Inside a function but no file/line known. Show the offset.
            result.Append(fxl::StringPrintf(" + 0x%" PRIx64, loc.address() - function_range.begin()));
            result.Append(Syntax::kComment, " (no line info)");
          }
        }
      }
    }
  
    if (loc.file_line().is_valid())
      result.Append(DescribeFileLine(optional_target_symbols, loc.file_line()));
    return result;
  }
  
  std::string DescribeFileLine(const TargetSymbols* optional_target_symbols,
                               const FileLine& file_line) {
    std::string result;
  
    // Name.
    if (file_line.file().empty()) {
      result = "?";
    } else if (!optional_target_symbols) {
      result = file_line.file();
    } else {
      result = optional_target_symbols->GetShortestUniqueFileName(file_line.file());
    }
  
    result.push_back(':');
  
    // Line.
    if (file_line.line() == 0)
      result.push_back('?');
    else
      result.append(fxl::StringPrintf("%d", file_line.line()));
  
    return result;
  }
  
  Err SetElementsToAdd(const std::vector<std::string>& args, AssignType* assign_type,
                       std::vector<std::string>* elements_to_set) {
    if (args.size() < 2u)
      return Err("Expected at least two arguments.");
  
    elements_to_set->clear();
  
    // Validation.
    auto& token = args[1];
    if (token == "=" || token == "+=" || token == "-=") {
      if (args.size() < 3)
        return Err("Expected a value after \"=\"");
      elements_to_set->insert(elements_to_set->end(), args.begin() + 2, args.end());
      if (token == "=") {
        *assign_type = AssignType::kAssign;
      } else if (token == "+=") {
        *assign_type = AssignType::kAppend;
      }
      if (token == "-=") {
        *assign_type = AssignType::kRemove;
      }
    } else {
      *assign_type = AssignType::kAssign;
      // We just append everything after the setting name.
      elements_to_set->insert(elements_to_set->end(), args.begin() + 1, args.end());
    }
  
    return Err();
  }
  
  const char* AssignTypeToString(AssignType assign_type) {
    switch (assign_type) {
      case AssignType::kAssign:
        return "Assign";
      case AssignType::kAppend:
        return "Append";
      case AssignType::kRemove:
        return "Remove";
    }
  
    FXL_NOTREACHED();
    return "";
  }
  
  fxl::RefPtr<EvalContext> GetEvalContextForCommand(const Command& cmd) {
    if (cmd.frame())
      return cmd.frame()->GetEvalContext();
  
    if (Process* process = cmd.target()->GetProcess()) {
      // Process context only.
      return fxl::MakeRefCounted<EvalContextImpl>(process->GetSymbols()->GetWeakPtr(),
                                                  process->GetSymbolDataProvider(), Location());
    }
  
    // No context.
    return fxl::MakeRefCounted<EvalContextImpl>(
        fxl::WeakPtr<ProcessSymbols>(), fxl::MakeRefCounted<SymbolDataProvider>(), Location());
  }
  
  Err EvalCommandExpression(const Command& cmd, const char* verb,
                            fxl::RefPtr<EvalContext> eval_context, bool follow_references,
                            std::function<void(const Err& err, ExprValue value)> cb) {
    Err err = cmd.ValidateNouns({Noun::kProcess, Noun::kThread, Noun::kFrame});
    if (err.has_error())
      return err;
  
    // This takes one expression that may have spaces, so concatenate everything
    // the command parser has split apart back into one thing.
    //
    // If we run into limitations of this, we should add a "don't parse the args"
    // flag to the command record.
    std::string expr;
    for (const auto& cur : cmd.args()) {
      if (!expr.empty())
        expr.push_back(' ');
      expr += cur;
    }
  
    if (expr.empty())
      return Err("Usage: %s <expression>\nSee \"help %s\" for more.", verb, verb);
  
    EvalExpression(expr, std::move(eval_context), follow_references, std::move(cb));
    return Err();
  }
  
  Err EvalCommandAddressExpression(
      const Command& cmd, const char* verb, fxl::RefPtr<EvalContext> eval_context,
      std::function<void(const Err& err, uint64_t address, std::optional<uint32_t> size)> cb) {
    return EvalCommandExpression(
        cmd, verb, eval_context, true,
        [eval_context, cb = std::move(cb)](const Err& err, ExprValue value) {
          if (err.has_error()) {
            cb(err, 0, std::nullopt);
            return;
          }
  
          fxl::RefPtr<Type> concrete_type = value.GetConcreteType(eval_context.get());
          if (concrete_type->AsCollection()) {
            // Don't allow structs and classes that are <= 64 bits to be converted
            // to addresses.
            cb(Err("Can't convert '%s' to an address.", concrete_type->GetFullName().c_str()), 0,
               std::nullopt);
            return;
          }
  
          // See if there's an intrinsic size to the object being pointed to. This
          // is true for pointers. References should have been followed and
          // stripped before here.
          std::optional<uint32_t> size;
          if (auto modified = concrete_type->AsModifiedType();
              modified && modified->tag() == DwarfTag::kPointerType) {
            if (auto modified_type = modified->modified().Get()->AsType())
              size = modified_type->byte_size();
          }
  
          // Convert anything else <= 64 bits to a number.
          uint64_t address = 0;
          Err conversion_err = value.PromoteTo64(&address);
          cb(conversion_err, address, size);
        });
  }
  
  }  // namespace zxdb
  