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