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