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