brintos

brintos / llvm-project-archived public Read only

0
0
Text · 24.5 KiB · 4919bd3 Raw
692 lines · cpp
1//===-- CommandObjectExpression.cpp ---------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "CommandObjectExpression.h"10#include "lldb/Core/Debugger.h"11#include "lldb/Expression/ExpressionVariable.h"12#include "lldb/Expression/REPL.h"13#include "lldb/Expression/UserExpression.h"14#include "lldb/Host/OptionParser.h"15#include "lldb/Host/StreamFile.h"16#include "lldb/Host/common/DiagnosticsRendering.h"17#include "lldb/Interpreter/CommandInterpreter.h"18#include "lldb/Interpreter/CommandOptionArgumentTable.h"19#include "lldb/Interpreter/CommandReturnObject.h"20#include "lldb/Interpreter/OptionArgParser.h"21#include "lldb/Target/Language.h"22#include "lldb/Target/Process.h"23#include "lldb/Target/StackFrame.h"24#include "lldb/Target/Target.h"25#include "lldb/lldb-enumerations.h"26#include "lldb/lldb-forward.h"27#include "lldb/lldb-private-enumerations.h"28 29using namespace lldb;30using namespace lldb_private;31 32CommandObjectExpression::CommandOptions::CommandOptions() = default;33 34CommandObjectExpression::CommandOptions::~CommandOptions() = default;35 36#define LLDB_OPTIONS_expression37#include "CommandOptions.inc"38 39Status CommandObjectExpression::CommandOptions::SetOptionValue(40    uint32_t option_idx, llvm::StringRef option_arg,41    ExecutionContext *execution_context) {42  Status error;43 44  const int short_option = GetDefinitions()[option_idx].short_option;45 46  switch (short_option) {47  case 'l':48    language = Language::GetLanguageTypeFromString(option_arg);49    if (language == eLanguageTypeUnknown) {50      StreamString sstr;51      sstr.Printf("unknown language type: '%s' for expression. "52                  "List of supported languages:\n",53                  option_arg.str().c_str());54 55      Language::PrintSupportedLanguagesForExpressions(sstr, "  ", "\n");56      error = Status(sstr.GetString().str());57    }58    break;59 60  case 'a': {61    bool success;62    bool result;63    result = OptionArgParser::ToBoolean(option_arg, true, &success);64    if (!success)65      error = Status::FromErrorStringWithFormat(66          "invalid all-threads value setting: \"%s\"",67          option_arg.str().c_str());68    else69      try_all_threads = result;70  } break;71 72  case 'i': {73    bool success;74    bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success);75    if (success)76      ignore_breakpoints = tmp_value;77    else78      error = Status::FromErrorStringWithFormat(79          "could not convert \"%s\" to a boolean value.",80          option_arg.str().c_str());81    break;82  }83 84  case 'j': {85    bool success;86    bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success);87    if (success)88      allow_jit = tmp_value;89    else90      error = Status::FromErrorStringWithFormat(91          "could not convert \"%s\" to a boolean value.",92          option_arg.str().c_str());93    break;94  }95 96  case 't':97    if (option_arg.getAsInteger(0, timeout)) {98      timeout = 0;99      error = Status::FromErrorStringWithFormat(100          "invalid timeout setting \"%s\"", option_arg.str().c_str());101    }102    break;103 104  case 'u': {105    bool success;106    bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success);107    if (success)108      unwind_on_error = tmp_value;109    else110      error = Status::FromErrorStringWithFormat(111          "could not convert \"%s\" to a boolean value.",112          option_arg.str().c_str());113    break;114  }115 116  case 'v':117    if (option_arg.empty()) {118      m_verbosity = eLanguageRuntimeDescriptionDisplayVerbosityFull;119      break;120    }121    m_verbosity = (LanguageRuntimeDescriptionDisplayVerbosity)122        OptionArgParser::ToOptionEnum(123            option_arg, GetDefinitions()[option_idx].enum_values, 0, error);124    if (!error.Success())125      error = Status::FromErrorStringWithFormat(126          "unrecognized value for description-verbosity '%s'",127          option_arg.str().c_str());128    break;129 130  case 'g':131    debug = true;132    unwind_on_error = false;133    ignore_breakpoints = false;134    break;135 136  case 'p':137    top_level = true;138    break;139 140  case 'X': {141    bool success;142    bool tmp_value = OptionArgParser::ToBoolean(option_arg, true, &success);143    if (success)144      auto_apply_fixits = tmp_value ? eLazyBoolYes : eLazyBoolNo;145    else146      error = Status::FromErrorStringWithFormat(147          "could not convert \"%s\" to a boolean value.",148          option_arg.str().c_str());149    break;150  }151 152  case '\x01': {153    bool success;154    bool persist_result =155        OptionArgParser::ToBoolean(option_arg, true, &success);156    if (success)157      suppress_persistent_result = !persist_result ? eLazyBoolYes : eLazyBoolNo;158    else159      error = Status::FromErrorStringWithFormat(160          "could not convert \"%s\" to a boolean value.",161          option_arg.str().c_str());162    break;163  }164 165  default:166    llvm_unreachable("Unimplemented option");167  }168 169  return error;170}171 172void CommandObjectExpression::CommandOptions::OptionParsingStarting(173    ExecutionContext *execution_context) {174  auto process_sp =175      execution_context ? execution_context->GetProcessSP() : ProcessSP();176  if (process_sp) {177    ignore_breakpoints = process_sp->GetIgnoreBreakpointsInExpressions();178    unwind_on_error = process_sp->GetUnwindOnErrorInExpressions();179  } else {180    ignore_breakpoints = true;181    unwind_on_error = true;182  }183 184  show_summary = true;185  try_all_threads = true;186  timeout = 0;187  debug = false;188  language = eLanguageTypeUnknown;189  m_verbosity = eLanguageRuntimeDescriptionDisplayVerbosityCompact;190  auto_apply_fixits = eLazyBoolCalculate;191  top_level = false;192  allow_jit = true;193  suppress_persistent_result = eLazyBoolCalculate;194}195 196llvm::ArrayRef<OptionDefinition>197CommandObjectExpression::CommandOptions::GetDefinitions() {198  return llvm::ArrayRef(g_expression_options);199}200 201EvaluateExpressionOptions202CommandObjectExpression::CommandOptions::GetEvaluateExpressionOptions(203    const Target &target, const OptionGroupValueObjectDisplay &display_opts) {204  EvaluateExpressionOptions options;205  options.SetCoerceToId(display_opts.use_object_desc);206  options.SetUnwindOnError(unwind_on_error);207  options.SetIgnoreBreakpoints(ignore_breakpoints);208  options.SetKeepInMemory(true);209  options.SetUseDynamic(display_opts.use_dynamic);210  options.SetTryAllThreads(try_all_threads);211  options.SetDebug(debug);212  options.SetLanguage(language);213  options.SetExecutionPolicy(214      allow_jit ? EvaluateExpressionOptions::default_execution_policy215                : lldb_private::eExecutionPolicyNever);216 217  bool auto_apply_fixits;218  if (this->auto_apply_fixits == eLazyBoolCalculate)219    auto_apply_fixits = target.GetEnableAutoApplyFixIts();220  else221    auto_apply_fixits = this->auto_apply_fixits == eLazyBoolYes;222 223  options.SetAutoApplyFixIts(auto_apply_fixits);224  options.SetRetriesWithFixIts(target.GetNumberOfRetriesWithFixits());225 226  if (top_level)227    options.SetExecutionPolicy(eExecutionPolicyTopLevel);228 229  // If there is any chance we are going to stop and want to see what went230  // wrong with our expression, we should generate debug info231  if (!ignore_breakpoints || !unwind_on_error)232    options.SetGenerateDebugInfo(true);233 234  if (timeout > 0)235    options.SetTimeout(std::chrono::microseconds(timeout));236  else237    options.SetTimeout(std::nullopt);238  return options;239}240 241bool CommandObjectExpression::CommandOptions::ShouldSuppressResult(242    const OptionGroupValueObjectDisplay &display_opts) const {243  // Explicitly disabling persistent results takes precedence over the244  // m_verbosity/use_object_desc logic.245  if (suppress_persistent_result != eLazyBoolCalculate)246    return suppress_persistent_result == eLazyBoolYes;247 248  return display_opts.use_object_desc &&249         m_verbosity == eLanguageRuntimeDescriptionDisplayVerbosityCompact;250}251 252CommandObjectExpression::CommandObjectExpression(253    CommandInterpreter &interpreter)254    : CommandObjectRaw(interpreter, "expression",255                       "Evaluate an expression on the current "256                       "thread.  Displays any returned value "257                       "with LLDB's default formatting.",258                       "",259                       eCommandProcessMustBePaused | eCommandTryTargetAPILock),260      IOHandlerDelegate(IOHandlerDelegate::Completion::Expression),261      m_format_options(eFormatDefault),262      m_repl_option(LLDB_OPT_SET_1, false, "repl", 'r', "Drop into REPL", false,263                    true),264      m_expr_line_count(0) {265  SetHelpLong(266      R"(267Single and multi-line expressions:268 269)"270      "    The expression provided on the command line must be a complete expression \271with no newlines.  To evaluate a multi-line expression, \272hit a return after an empty expression, and lldb will enter the multi-line expression editor. \273Hit return on an empty line to end the multi-line expression."274 275      R"(276 277Timeouts:278 279)"280      "    If the expression can be evaluated statically (without running code) then it will be.  \281Otherwise, by default the expression will run on the current thread with a short timeout: \282currently .25 seconds.  If it doesn't return in that time, the evaluation will be interrupted \283and resumed with all threads running.  You can use the -a option to disable retrying on all \284threads.  You can use the -t option to set a shorter timeout."285      R"(286 287User defined variables:288 289)"290      "    You can define your own variables for convenience or to be used in subsequent expressions.  \291You define them the same way you would define variables in C.  If the first character of \292your user defined variable is a $, then the variable's value will be available in future \293expressions, otherwise it will just be available in the current expression."294      R"(295 296Continuing evaluation after a breakpoint:297 298)"299      "    If the \"-i false\" option is used, and execution is interrupted by a breakpoint hit, once \300you are done with your investigation, you can either remove the expression execution frames \301from the stack with \"thread return -x\" or if you are still interested in the expression result \302you can issue the \"continue\" command and the expression evaluation will complete and the \303expression result will be available using the \"thread.completed-expression\" key in the thread \304format."305 306      R"(307 308Examples:309 310    expr my_struct->a = my_array[3]311    expr -f bin -- (index * 8) + 5312    expr unsigned int $foo = 5313    expr char c[] = \"foo\"; c[0])");314 315  AddSimpleArgumentList(eArgTypeExpression);316 317  // Add the "--format" and "--gdb-format"318  m_option_group.Append(&m_format_options,319                        OptionGroupFormat::OPTION_GROUP_FORMAT |320                            OptionGroupFormat::OPTION_GROUP_GDB_FMT,321                        LLDB_OPT_SET_1);322  m_option_group.Append(&m_command_options);323  m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL,324                        LLDB_OPT_SET_1 | LLDB_OPT_SET_2);325  m_option_group.Append(&m_repl_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_3);326  m_option_group.Finalize();327}328 329CommandObjectExpression::~CommandObjectExpression() = default;330 331Options *CommandObjectExpression::GetOptions() { return &m_option_group; }332 333void CommandObjectExpression::HandleCompletion(CompletionRequest &request) {334  EvaluateExpressionOptions options;335  options.SetCoerceToId(m_varobj_options.use_object_desc);336  options.SetLanguage(m_command_options.language);337  options.SetExecutionPolicy(lldb_private::eExecutionPolicyNever);338  options.SetAutoApplyFixIts(false);339  options.SetGenerateDebugInfo(false);340 341  ExecutionContext exe_ctx(m_interpreter.GetExecutionContext());342 343  // Get out before we start doing things that expect a valid frame pointer.344  if (exe_ctx.GetFramePtr() == nullptr)345    return;346 347  Target *exe_target = exe_ctx.GetTargetPtr();348  Target &target = exe_target ? *exe_target : GetDummyTarget();349 350  unsigned cursor_pos = request.GetRawCursorPos();351  // Get the full user input including the suffix. The suffix is necessary352  // as OptionsWithRaw will use it to detect if the cursor is cursor is in the353  // argument part of in the raw input part of the arguments. If we cut of354  // of the suffix then "expr -arg[cursor] --" would interpret the "-arg" as355  // the raw input (as the "--" is hidden in the suffix).356  llvm::StringRef code = request.GetRawLineWithUnusedSuffix();357 358  const std::size_t original_code_size = code.size();359 360  // Remove the first token which is 'expr' or some alias/abbreviation of that.361  code = llvm::getToken(code).second.ltrim();362  OptionsWithRaw args(code);363  code = args.GetRawPart();364 365  // The position where the expression starts in the command line.366  assert(original_code_size >= code.size());367  std::size_t raw_start = original_code_size - code.size();368 369  // Check if the cursor is actually in the expression string, and if not, we370  // exit.371  // FIXME: We should complete the options here.372  if (cursor_pos < raw_start)373    return;374 375  // Make the cursor_pos again relative to the start of the code string.376  assert(cursor_pos >= raw_start);377  cursor_pos -= raw_start;378 379  auto language = exe_ctx.GetFrameRef().GetLanguage();380 381  Status error;382  lldb::UserExpressionSP expr(target.GetUserExpressionForLanguage(383      code, llvm::StringRef(), language, UserExpression::eResultTypeAny,384      options, nullptr, error));385  if (error.Fail())386    return;387 388  expr->Complete(exe_ctx, request, cursor_pos);389}390 391static lldb_private::Status392CanBeUsedForElementCountPrinting(ValueObject &valobj) {393  CompilerType type(valobj.GetCompilerType());394  CompilerType pointee;395  if (!type.IsPointerType(&pointee))396    return Status::FromErrorString("as it does not refer to a pointer");397  if (pointee.IsVoidType())398    return Status::FromErrorString("as it refers to a pointer to void");399  return Status();400}401 402bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,403                                                 Stream &output_stream,404                                                 Stream &error_stream,405                                                 CommandReturnObject &result) {406  // Don't use m_exe_ctx as this might be called asynchronously after the407  // command object DoExecute has finished when doing multi-line expression408  // that use an input reader...409  ExecutionContext exe_ctx(m_interpreter.GetExecutionContext());410  Target *exe_target = exe_ctx.GetTargetPtr();411  Target &target = exe_target ? *exe_target : GetDummyTarget();412 413  lldb::ValueObjectSP result_valobj_sp;414  StackFrame *frame = exe_ctx.GetFramePtr();415 416  if (m_command_options.top_level && !m_command_options.allow_jit) {417    result.AppendErrorWithFormat(418        "Can't disable JIT compilation for top-level expressions.\n");419    return false;420  }421 422  EvaluateExpressionOptions eval_options =423      m_command_options.GetEvaluateExpressionOptions(target, m_varobj_options);424  // This command manually removes the result variable, make sure expression425  // evaluation doesn't do it first.426  eval_options.SetSuppressPersistentResult(false);427 428  ExpressionResults success = target.EvaluateExpression(429      expr, frame, result_valobj_sp, eval_options, &m_fixed_expression);430 431  // Only mention Fix-Its if the expression evaluator applied them.432  // Compiler errors refer to the final expression after applying Fix-It(s).433  if (!m_fixed_expression.empty() && target.GetEnableNotifyAboutFixIts()) {434    error_stream << "  Evaluated this expression after applying Fix-It(s):\n";435    error_stream << "    " << m_fixed_expression << "\n";436  }437 438  if (result_valobj_sp) {439    result.GetValueObjectList().Append(result_valobj_sp);440 441    Format format = m_format_options.GetFormat();442 443    if (result_valobj_sp->GetError().Success()) {444      if (format != eFormatVoid) {445        if (format != eFormatDefault)446          result_valobj_sp->SetFormat(format);447 448        if (m_varobj_options.elem_count > 0) {449          Status error(CanBeUsedForElementCountPrinting(*result_valobj_sp));450          if (error.Fail()) {451            result.AppendErrorWithFormat(452                "expression cannot be used with --element-count %s\n",453                error.AsCString(""));454            return false;455          }456        }457 458        bool suppress_result =459            m_command_options.ShouldSuppressResult(m_varobj_options);460 461        DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions(462            m_command_options.m_verbosity, format));463        options.SetHideRootName(suppress_result);464        options.SetVariableFormatDisplayLanguage(465            result_valobj_sp->GetPreferredDisplayLanguage());466 467        if (llvm::Error error =468                result_valobj_sp->Dump(output_stream, options)) {469          result.AppendError(toString(std::move(error)));470          return false;471        }472 473        m_interpreter.PrintWarningsIfNecessary(result.GetOutputStream(),474                                               m_cmd_name);475 476        if (suppress_result)477          if (auto result_var_sp =478                  target.GetPersistentVariable(result_valobj_sp->GetName())) {479            auto language = result_valobj_sp->GetPreferredDisplayLanguage();480            if (auto *persistent_state =481                    target.GetPersistentExpressionStateForLanguage(language))482              persistent_state->RemovePersistentVariable(result_var_sp);483          }484        result.SetStatus(eReturnStatusSuccessFinishResult);485      }486    } else {487      if (result_valobj_sp->GetError().GetError() ==488          UserExpression::kNoResult) {489        if (format != eFormatVoid && GetDebugger().GetNotifyVoid()) {490          error_stream.PutCString("(void)\n");491        }492 493        result.SetStatus(eReturnStatusSuccessFinishResult);494      } else {495        result.SetStatus(eReturnStatusFailed);496        result.SetError(result_valobj_sp->GetError().ToError());497      }498    }499  } else {500    error_stream.Printf("error: unknown error\n");501  }502 503  return (success != eExpressionSetupError &&504          success != eExpressionParseError);505}506 507void CommandObjectExpression::IOHandlerInputComplete(IOHandler &io_handler,508                                                     std::string &line) {509  io_handler.SetIsDone(true);510  StreamSP output_stream =511      GetCommandInterpreter().GetDebugger().GetAsyncOutputStream();512  StreamSP error_stream =513      GetCommandInterpreter().GetDebugger().GetAsyncErrorStream();514 515  CommandReturnObject return_obj(516      GetCommandInterpreter().GetDebugger().GetUseColor());517  EvaluateExpression(line.c_str(), *output_stream, *error_stream, return_obj);518 519  output_stream->Flush();520  *error_stream << return_obj.GetErrorString();521}522 523bool CommandObjectExpression::IOHandlerIsInputComplete(IOHandler &io_handler,524                                                       StringList &lines) {525  // An empty lines is used to indicate the end of input526  const size_t num_lines = lines.GetSize();527  if (num_lines > 0 && lines[num_lines - 1].empty()) {528    // Remove the last empty line from "lines" so it doesn't appear in our529    // resulting input and return true to indicate we are done getting lines530    lines.PopBack();531    return true;532  }533  return false;534}535 536void CommandObjectExpression::GetMultilineExpression() {537  m_expr_lines.clear();538  m_expr_line_count = 0;539 540  Debugger &debugger = GetCommandInterpreter().GetDebugger();541  bool color_prompt = debugger.GetUseColor();542  const bool multiple_lines = true; // Get multiple lines543  IOHandlerSP io_handler_sp(544      new IOHandlerEditline(debugger, IOHandler::Type::Expression,545                            "lldb-expr", // Name of input reader for history546                            llvm::StringRef(), // No prompt547                            llvm::StringRef(), // Continuation prompt548                            multiple_lines, color_prompt,549                            1, // Show line numbers starting at 1550                            *this));551 552  if (LockableStreamFileSP output_sp = io_handler_sp->GetOutputStreamFileSP()) {553    LockedStreamFile locked_stream = output_sp->Lock();554    locked_stream.PutCString(555        "Enter expressions, then terminate with an empty line to evaluate:\n");556  }557  debugger.RunIOHandlerAsync(io_handler_sp);558}559 560static EvaluateExpressionOptions561GetExprOptions(ExecutionContext &ctx,562               CommandObjectExpression::CommandOptions command_options) {563  command_options.OptionParsingStarting(&ctx);564 565  // Default certain settings for REPL regardless of the global settings.566  command_options.unwind_on_error = false;567  command_options.ignore_breakpoints = false;568  command_options.debug = false;569 570  EvaluateExpressionOptions expr_options;571  expr_options.SetUnwindOnError(command_options.unwind_on_error);572  expr_options.SetIgnoreBreakpoints(command_options.ignore_breakpoints);573  expr_options.SetTryAllThreads(command_options.try_all_threads);574 575  if (command_options.timeout > 0)576    expr_options.SetTimeout(std::chrono::microseconds(command_options.timeout));577  else578    expr_options.SetTimeout(std::nullopt);579 580  return expr_options;581}582 583void CommandObjectExpression::DoExecute(llvm::StringRef command,584                                        CommandReturnObject &result) {585  m_fixed_expression.clear();586  auto exe_ctx = GetCommandInterpreter().GetExecutionContext();587  m_option_group.NotifyOptionParsingStarting(&exe_ctx);588 589  if (command.empty()) {590    GetMultilineExpression();591    return;592  }593 594  OptionsWithRaw args(command);595  llvm::StringRef expr = args.GetRawPart();596 597  if (args.HasArgs()) {598    if (!ParseOptionsAndNotify(args.GetArgs(), result, m_option_group, exe_ctx))599      return;600 601    if (m_repl_option.GetOptionValue().GetCurrentValue()) {602      Target &target = GetTarget();603      // Drop into REPL604      m_expr_lines.clear();605      m_expr_line_count = 0;606 607      Debugger &debugger = target.GetDebugger();608 609      // Check if the LLDB command interpreter is sitting on top of a REPL610      // that launched it...611      if (debugger.CheckTopIOHandlerTypes(IOHandler::Type::CommandInterpreter,612                                          IOHandler::Type::REPL)) {613        // the LLDB command interpreter is sitting on top of a REPL that614        // launched it, so just say the command interpreter is done and615        // fall back to the existing REPL616        m_interpreter.GetIOHandler(false)->SetIsDone(true);617      } else {618        // We are launching the REPL on top of the current LLDB command619        // interpreter, so just push one620        bool initialize = false;621        Status repl_error;622        REPLSP repl_sp(target.GetREPL(repl_error, m_command_options.language,623                                       nullptr, false));624 625        if (!repl_sp) {626          initialize = true;627          repl_sp = target.GetREPL(repl_error, m_command_options.language,628                                    nullptr, true);629          if (repl_error.Fail()) {630            result.SetError(std::move(repl_error));631            return;632          }633        }634 635        if (repl_sp) {636          if (initialize) {637            repl_sp->SetEvaluateOptions(638                GetExprOptions(exe_ctx, m_command_options));639            repl_sp->SetFormatOptions(m_format_options);640            repl_sp->SetValueObjectDisplayOptions(m_varobj_options);641          }642 643          IOHandlerSP io_handler_sp(repl_sp->GetIOHandler());644          io_handler_sp->SetIsDone(false);645          debugger.RunIOHandlerAsync(io_handler_sp);646        } else {647          repl_error = Status::FromErrorStringWithFormat(648              "Couldn't create a REPL for %s",649              Language::GetNameForLanguageType(m_command_options.language));650          result.SetError(std::move(repl_error));651          return;652        }653      }654    }655    // No expression following options656    else if (expr.empty()) {657      GetMultilineExpression();658      return;659    }660  }661 662  // Previously the indent was set up for diagnosing command line663  // parsing errors. Now point it to the expression.664  std::optional<uint16_t> indent;665  size_t pos = m_original_command.rfind(expr);666  if (pos != llvm::StringRef::npos)667    indent = pos;668  result.SetDiagnosticIndent(indent);669 670  Target &target = GetTarget();671  if (EvaluateExpression(expr, result.GetOutputStream(),672                         result.GetErrorStream(), result)) {673 674    if (!m_fixed_expression.empty() && target.GetEnableNotifyAboutFixIts()) {675      CommandHistory &history = m_interpreter.GetCommandHistory();676      // FIXME: Can we figure out what the user actually typed (e.g. some alias677      // for expr???)678      // If we can it would be nice to show that.679      std::string fixed_command("expression ");680      if (args.HasArgs()) {681        // Add in any options that might have been in the original command:682        fixed_command.append(std::string(args.GetArgStringWithDelimiter()));683        fixed_command.append(m_fixed_expression);684      } else685        fixed_command.append(m_fixed_expression);686      history.AppendString(fixed_command);687    }688    return;689  }690  result.SetStatus(eReturnStatusFailed);691}692