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