1 //===-- CommandObjectFrame.cpp ----------------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "CommandObjectFrame.h" 11 12 // C Includes 13 // C++ Includes 14 #include <string> 15 // Other libraries and framework includes 16 // Project includes 17 #include "lldb/Breakpoint/Watchpoint.h" 18 #include "lldb/Core/DataVisualization.h" 19 #include "lldb/Core/Debugger.h" 20 #include "lldb/Core/Module.h" 21 #include "lldb/Core/StreamFile.h" 22 #include "lldb/Core/StreamString.h" 23 #include "lldb/Core/Timer.h" 24 #include "lldb/Core/Value.h" 25 #include "lldb/Core/ValueObject.h" 26 #include "lldb/Core/ValueObjectVariable.h" 27 #include "lldb/Host/Host.h" 28 #include "lldb/Interpreter/Args.h" 29 #include "lldb/Interpreter/CommandInterpreter.h" 30 #include "lldb/Interpreter/CommandReturnObject.h" 31 #include "lldb/Interpreter/Options.h" 32 #include "lldb/Interpreter/OptionGroupFormat.h" 33 #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" 34 #include "lldb/Interpreter/OptionGroupVariable.h" 35 #include "lldb/Interpreter/OptionGroupWatchpoint.h" 36 #include "lldb/Symbol/ClangASTType.h" 37 #include "lldb/Symbol/ClangASTContext.h" 38 #include "lldb/Symbol/ObjectFile.h" 39 #include "lldb/Symbol/SymbolContext.h" 40 #include "lldb/Symbol/Type.h" 41 #include "lldb/Symbol/Variable.h" 42 #include "lldb/Symbol/VariableList.h" 43 #include "lldb/Target/Process.h" 44 #include "lldb/Target/StackFrame.h" 45 #include "lldb/Target/Thread.h" 46 #include "lldb/Target/Target.h" 47 48 using namespace lldb; 49 using namespace lldb_private; 50 51 #pragma mark CommandObjectFrameInfo 52 53 //------------------------------------------------------------------------- 54 // CommandObjectFrameInfo 55 //------------------------------------------------------------------------- 56 57 class CommandObjectFrameInfo : public CommandObject 58 { 59 public: 60 61 CommandObjectFrameInfo (CommandInterpreter &interpreter) : 62 CommandObject (interpreter, 63 "frame info", 64 "List information about the currently selected frame in the current thread.", 65 "frame info", 66 eFlagProcessMustBeLaunched | eFlagProcessMustBePaused) 67 { 68 } 69 70 ~CommandObjectFrameInfo () 71 { 72 } 73 74 bool 75 Execute (Args& command, 76 CommandReturnObject &result) 77 { 78 ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); 79 StackFrame *frame = exe_ctx.GetFramePtr(); 80 if (frame) 81 { 82 frame->DumpUsingSettingsFormat (&result.GetOutputStream()); 83 result.SetStatus (eReturnStatusSuccessFinishResult); 84 } 85 else 86 { 87 result.AppendError ("no current frame"); 88 result.SetStatus (eReturnStatusFailed); 89 } 90 return result.Succeeded(); 91 } 92 }; 93 94 #pragma mark CommandObjectFrameSelect 95 96 //------------------------------------------------------------------------- 97 // CommandObjectFrameSelect 98 //------------------------------------------------------------------------- 99 100 class CommandObjectFrameSelect : public CommandObject 101 { 102 public: 103 104 class CommandOptions : public Options 105 { 106 public: 107 108 CommandOptions (CommandInterpreter &interpreter) : 109 Options(interpreter) 110 { 111 OptionParsingStarting (); 112 } 113 114 virtual 115 ~CommandOptions () 116 { 117 } 118 119 virtual Error 120 SetOptionValue (uint32_t option_idx, const char *option_arg) 121 { 122 Error error; 123 bool success = false; 124 char short_option = (char) m_getopt_table[option_idx].val; 125 switch (short_option) 126 { 127 case 'r': 128 relative_frame_offset = Args::StringToSInt32 (option_arg, INT32_MIN, 0, &success); 129 if (!success) 130 error.SetErrorStringWithFormat ("invalid frame offset argument '%s'", option_arg); 131 break; 132 133 default: 134 error.SetErrorStringWithFormat ("invalid short option character '%c'", short_option); 135 break; 136 } 137 138 return error; 139 } 140 141 void 142 OptionParsingStarting () 143 { 144 relative_frame_offset = INT32_MIN; 145 } 146 147 const OptionDefinition* 148 GetDefinitions () 149 { 150 return g_option_table; 151 } 152 153 // Options table: Required for subclasses of Options. 154 155 static OptionDefinition g_option_table[]; 156 int32_t relative_frame_offset; 157 }; 158 159 CommandObjectFrameSelect (CommandInterpreter &interpreter) : 160 CommandObject (interpreter, 161 "frame select", 162 "Select a frame by index from within the current thread and make it the current frame.", 163 NULL, 164 eFlagProcessMustBeLaunched | eFlagProcessMustBePaused), 165 m_options (interpreter) 166 { 167 CommandArgumentEntry arg; 168 CommandArgumentData index_arg; 169 170 // Define the first (and only) variant of this arg. 171 index_arg.arg_type = eArgTypeFrameIndex; 172 index_arg.arg_repetition = eArgRepeatOptional; 173 174 // There is only one variant this argument could be; put it into the argument entry. 175 arg.push_back (index_arg); 176 177 // Push the data for the first argument into the m_arguments vector. 178 m_arguments.push_back (arg); 179 } 180 181 ~CommandObjectFrameSelect () 182 { 183 } 184 185 virtual 186 Options * 187 GetOptions () 188 { 189 return &m_options; 190 } 191 192 193 bool 194 Execute (Args& command, 195 CommandReturnObject &result) 196 { 197 ExecutionContext exe_ctx (m_interpreter.GetExecutionContext()); 198 Thread *thread = exe_ctx.GetThreadPtr(); 199 if (thread) 200 { 201 const uint32_t num_frames = thread->GetStackFrameCount(); 202 uint32_t frame_idx = UINT32_MAX; 203 if (m_options.relative_frame_offset != INT32_MIN) 204 { 205 // The one and only argument is a signed relative frame index 206 frame_idx = thread->GetSelectedFrameIndex (); 207 if (frame_idx == UINT32_MAX) 208 frame_idx = 0; 209 210 if (m_options.relative_frame_offset < 0) 211 { 212 if (frame_idx >= -m_options.relative_frame_offset) 213 frame_idx += m_options.relative_frame_offset; 214 else 215 { 216 if (frame_idx == 0) 217 { 218 //If you are already at the bottom of the stack, then just warn and don't reset the frame. 219 result.AppendError("Already at the bottom of the stack"); 220 result.SetStatus(eReturnStatusFailed); 221 return false; 222 } 223 else 224 frame_idx = 0; 225 } 226 } 227 else if (m_options.relative_frame_offset > 0) 228 { 229 if (num_frames - frame_idx > m_options.relative_frame_offset) 230 frame_idx += m_options.relative_frame_offset; 231 else 232 { 233 if (frame_idx == num_frames - 1) 234 { 235 //If we are already at the top of the stack, just warn and don't reset the frame. 236 result.AppendError("Already at the top of the stack"); 237 result.SetStatus(eReturnStatusFailed); 238 return false; 239 } 240 else 241 frame_idx = num_frames - 1; 242 } 243 } 244 } 245 else 246 { 247 if (command.GetArgumentCount() == 1) 248 { 249 const char *frame_idx_cstr = command.GetArgumentAtIndex(0); 250 frame_idx = Args::StringToUInt32 (frame_idx_cstr, UINT32_MAX, 0); 251 } 252 else if (command.GetArgumentCount() == 0) 253 { 254 frame_idx = thread->GetSelectedFrameIndex (); 255 if (frame_idx == UINT32_MAX) 256 { 257 frame_idx = 0; 258 } 259 } 260 else 261 { 262 result.AppendError ("invalid arguments.\n"); 263 m_options.GenerateOptionUsage (result.GetErrorStream(), this); 264 } 265 } 266 267 if (frame_idx < num_frames) 268 { 269 thread->SetSelectedFrameByIndex (frame_idx); 270 exe_ctx.SetFrameSP(thread->GetSelectedFrame ()); 271 StackFrame *frame = exe_ctx.GetFramePtr(); 272 if (frame) 273 { 274 bool already_shown = false; 275 SymbolContext frame_sc(frame->GetSymbolContext(eSymbolContextLineEntry)); 276 if (m_interpreter.GetDebugger().GetUseExternalEditor() && frame_sc.line_entry.file && frame_sc.line_entry.line != 0) 277 { 278 already_shown = Host::OpenFileInExternalEditor (frame_sc.line_entry.file, frame_sc.line_entry.line); 279 } 280 281 bool show_frame_info = true; 282 bool show_source = !already_shown; 283 uint32_t source_lines_before = 3; 284 uint32_t source_lines_after = 3; 285 if (frame->GetStatus (result.GetOutputStream(), 286 show_frame_info, 287 show_source, 288 source_lines_before, 289 source_lines_after)) 290 { 291 result.SetStatus (eReturnStatusSuccessFinishResult); 292 return result.Succeeded(); 293 } 294 } 295 } 296 result.AppendErrorWithFormat ("Frame index (%u) out of range.\n", frame_idx); 297 } 298 else 299 { 300 result.AppendError ("no current thread"); 301 } 302 result.SetStatus (eReturnStatusFailed); 303 return false; 304 } 305 protected: 306 307 CommandOptions m_options; 308 }; 309 310 OptionDefinition 311 CommandObjectFrameSelect::CommandOptions::g_option_table[] = 312 { 313 { LLDB_OPT_SET_1, false, "relative", 'r', required_argument, NULL, 0, eArgTypeOffset, "A relative frame index offset from the current frame index."}, 314 { 0, false, NULL, 0, 0, NULL, NULL, eArgTypeNone, NULL } 315 }; 316 317 #pragma mark CommandObjectFrameVariable 318 //---------------------------------------------------------------------- 319 // List images with associated information 320 //---------------------------------------------------------------------- 321 class CommandObjectFrameVariable : public CommandObject 322 { 323 public: 324 325 CommandObjectFrameVariable (CommandInterpreter &interpreter) : 326 CommandObject (interpreter, 327 "frame variable", 328 "Show frame variables. All argument and local variables " 329 "that are in scope will be shown when no arguments are given. " 330 "If any arguments are specified, they can be names of " 331 "argument, local, file static and file global variables. " 332 "Children of aggregate variables can be specified such as " 333 "'var->child.x'. " 334 "You can choose to watch a variable with the '-w' option; " 335 "with the additional '-x' option to specify the region size, " 336 "the variable's value will be used as the starting address of " 337 "the region to watch for, instead. " 338 "Note that hardware resources for watching are often limited. " 339 "See alo 'watchpoint set' where you can use an expression to " 340 "specify the address to watch for.", 341 NULL, 342 eFlagProcessMustBeLaunched | eFlagProcessMustBePaused), 343 m_option_group (interpreter), 344 m_option_variable(true), // Include the frame specific options by passing "true" 345 m_option_format (eFormatDefault), 346 m_option_watchpoint(), 347 m_varobj_options() 348 { 349 CommandArgumentEntry arg; 350 CommandArgumentData var_name_arg; 351 352 // Define the first (and only) variant of this arg. 353 var_name_arg.arg_type = eArgTypeVarName; 354 var_name_arg.arg_repetition = eArgRepeatStar; 355 356 // There is only one variant this argument could be; put it into the argument entry. 357 arg.push_back (var_name_arg); 358 359 // Push the data for the first argument into the m_arguments vector. 360 m_arguments.push_back (arg); 361 362 m_option_group.Append (&m_option_variable, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); 363 m_option_group.Append (&m_option_format, OptionGroupFormat::OPTION_GROUP_FORMAT | OptionGroupFormat::OPTION_GROUP_GDB_FMT, LLDB_OPT_SET_1); 364 m_option_group.Append (&m_option_watchpoint, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); 365 m_option_group.Append (&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); 366 m_option_group.Finalize(); 367 } 368 369 virtual 370 ~CommandObjectFrameVariable () 371 { 372 } 373 374 virtual 375 Options * 376 GetOptions () 377 { 378 return &m_option_group; 379 } 380 381 382 virtual bool 383 Execute 384 ( 385 Args& command, 386 CommandReturnObject &result 387 ) 388 { 389 ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); 390 StackFrame *frame = exe_ctx.GetFramePtr(); 391 if (frame == NULL) 392 { 393 result.AppendError ("you must be stopped in a valid stack frame to view frame variables."); 394 result.SetStatus (eReturnStatusFailed); 395 return false; 396 } 397 398 Stream &s = result.GetOutputStream(); 399 400 bool get_file_globals = true; 401 402 // Be careful about the stack frame, if any summary formatter runs code, it might clear the StackFrameList 403 // for the thread. So hold onto a shared pointer to the frame so it stays alive. 404 405 VariableList *variable_list = frame->GetVariableList (get_file_globals); 406 407 VariableSP var_sp; 408 ValueObjectSP valobj_sp; 409 410 const char *name_cstr = NULL; 411 size_t idx; 412 413 SummaryFormatSP summary_format_sp; 414 if (!m_option_variable.summary.empty()) 415 DataVisualization::NamedSummaryFormats::GetSummaryFormat(ConstString(m_option_variable.summary.c_str()), summary_format_sp); 416 417 ValueObject::DumpValueObjectOptions options; 418 419 options.SetPointerDepth(m_varobj_options.ptr_depth) 420 .SetMaximumDepth(m_varobj_options.max_depth) 421 .SetShowTypes(m_varobj_options.show_types) 422 .SetShowLocation(m_varobj_options.show_location) 423 .SetUseObjectiveC(m_varobj_options.use_objc) 424 .SetUseDynamicType(m_varobj_options.use_dynamic) 425 .SetUseSyntheticValue((lldb::SyntheticValueType)m_varobj_options.use_synth) 426 .SetFlatOutput(m_varobj_options.flat_output) 427 .SetOmitSummaryDepth(m_varobj_options.no_summary_depth) 428 .SetIgnoreCap(m_varobj_options.ignore_cap); 429 430 if (m_varobj_options.be_raw) 431 options.SetRawDisplay(true); 432 433 if (variable_list) 434 { 435 // If watching a variable, there are certain restrictions to be followed. 436 if (m_option_watchpoint.watch_variable) 437 { 438 if (command.GetArgumentCount() != 1) { 439 result.GetErrorStream().Printf("error: specify exactly one variable when using the '-w' option\n"); 440 result.SetStatus(eReturnStatusFailed); 441 return false; 442 } else if (m_option_variable.use_regex) { 443 result.GetErrorStream().Printf("error: specify your variable name exactly (no regex) when using the '-w' option\n"); 444 result.SetStatus(eReturnStatusFailed); 445 return false; 446 } 447 448 // Things have checked out ok... 449 // m_option_watchpoint.watch_type specifies the type of watching. 450 } 451 452 const Format format = m_option_format.GetFormat(); 453 454 if (command.GetArgumentCount() > 0) 455 { 456 VariableList regex_var_list; 457 458 // If we have any args to the variable command, we will make 459 // variable objects from them... 460 for (idx = 0; (name_cstr = command.GetArgumentAtIndex(idx)) != NULL; ++idx) 461 { 462 if (m_option_variable.use_regex) 463 { 464 const uint32_t regex_start_index = regex_var_list.GetSize(); 465 RegularExpression regex (name_cstr); 466 if (regex.Compile(name_cstr)) 467 { 468 size_t num_matches = 0; 469 const size_t num_new_regex_vars = variable_list->AppendVariablesIfUnique(regex, 470 regex_var_list, 471 num_matches); 472 if (num_new_regex_vars > 0) 473 { 474 for (uint32_t regex_idx = regex_start_index, end_index = regex_var_list.GetSize(); 475 regex_idx < end_index; 476 ++regex_idx) 477 { 478 var_sp = regex_var_list.GetVariableAtIndex (regex_idx); 479 if (var_sp) 480 { 481 valobj_sp = frame->GetValueObjectForFrameVariable (var_sp, m_varobj_options.use_dynamic); 482 if (valobj_sp) 483 { 484 // if (format != eFormatDefault) 485 // valobj_sp->SetFormat (format); 486 487 if (m_option_variable.show_decl && var_sp->GetDeclaration ().GetFile()) 488 { 489 bool show_fullpaths = false; 490 bool show_module = true; 491 if (var_sp->DumpDeclaration(&s, show_fullpaths, show_module)) 492 s.PutCString (": "); 493 } 494 if (summary_format_sp) 495 valobj_sp->SetCustomSummaryFormat(summary_format_sp); 496 ValueObject::DumpValueObject (result.GetOutputStream(), 497 valobj_sp.get(), 498 options, 499 format); 500 } 501 } 502 } 503 } 504 else if (num_matches == 0) 505 { 506 result.GetErrorStream().Printf ("error: no variables matched the regular expression '%s'.\n", name_cstr); 507 } 508 } 509 else 510 { 511 char regex_error[1024]; 512 if (regex.GetErrorAsCString(regex_error, sizeof(regex_error))) 513 result.GetErrorStream().Printf ("error: %s\n", regex_error); 514 else 515 result.GetErrorStream().Printf ("error: unkown regex error when compiling '%s'\n", name_cstr); 516 } 517 } 518 else // No regex, either exact variable names or variable expressions. 519 { 520 Error error; 521 uint32_t expr_path_options = StackFrame::eExpressionPathOptionCheckPtrVsMember; 522 lldb::VariableSP var_sp; 523 valobj_sp = frame->GetValueForVariableExpressionPath (name_cstr, 524 m_varobj_options.use_dynamic, 525 expr_path_options, 526 var_sp, 527 error); 528 if (valobj_sp) 529 { 530 // if (format != eFormatDefault) 531 // valobj_sp->SetFormat (format); 532 if (m_option_variable.show_decl && var_sp && var_sp->GetDeclaration ().GetFile()) 533 { 534 var_sp->GetDeclaration ().DumpStopContext (&s, false); 535 s.PutCString (": "); 536 } 537 if (summary_format_sp) 538 valobj_sp->SetCustomSummaryFormat(summary_format_sp); 539 540 Stream &output_stream = result.GetOutputStream(); 541 ValueObject::DumpValueObject (output_stream, 542 valobj_sp.get(), 543 valobj_sp->GetParent() ? name_cstr : NULL, 544 options, 545 format); 546 // Process watchpoint if necessary. 547 if (m_option_watchpoint.watch_variable) 548 { 549 AddressType addr_type; 550 lldb::addr_t addr = 0; 551 size_t size = 0; 552 if (m_option_watchpoint.watch_size == 0) { 553 addr = valobj_sp->GetAddressOf(false, &addr_type); 554 if (addr_type == eAddressTypeLoad) { 555 // We're in business. 556 // Find out the size of this variable. 557 size = valobj_sp->GetByteSize(); 558 } 559 } else { 560 // The '-xsize'/'-x' option means to treat the value object as 561 // a pointer and to watch the pointee with the specified size. 562 addr = valobj_sp->GetValueAsUnsigned(0); 563 size = m_option_watchpoint.watch_size; 564 } 565 uint32_t watch_type = m_option_watchpoint.watch_type; 566 Watchpoint *wp = exe_ctx.GetTargetRef().CreateWatchpoint(addr, size, watch_type).get(); 567 if (wp) 568 { 569 if (var_sp && var_sp->GetDeclaration().GetFile()) 570 { 571 StreamString ss; 572 // True to show fullpath for declaration file. 573 var_sp->GetDeclaration().DumpStopContext(&ss, true); 574 wp->SetDeclInfo(ss.GetString()); 575 } 576 StreamString ss; 577 output_stream.Printf("Watchpoint created: "); 578 wp->GetDescription(&output_stream, lldb::eDescriptionLevelFull); 579 output_stream.EOL(); 580 result.SetStatus(eReturnStatusSuccessFinishResult); 581 } 582 else 583 { 584 result.AppendErrorWithFormat("Watchpoint creation failed.\n"); 585 result.SetStatus(eReturnStatusFailed); 586 } 587 return (wp != NULL); 588 } 589 } 590 else 591 { 592 const char *error_cstr = error.AsCString(NULL); 593 if (error_cstr) 594 result.GetErrorStream().Printf("error: %s\n", error_cstr); 595 else 596 result.GetErrorStream().Printf ("error: unable to find any variable expression path that matches '%s'\n", name_cstr); 597 } 598 } 599 } 600 } 601 else // No command arg specified. Use variable_list, instead. 602 { 603 const uint32_t num_variables = variable_list->GetSize(); 604 if (num_variables > 0) 605 { 606 for (uint32_t i=0; i<num_variables; i++) 607 { 608 var_sp = variable_list->GetVariableAtIndex(i); 609 bool dump_variable = true; 610 switch (var_sp->GetScope()) 611 { 612 case eValueTypeVariableGlobal: 613 dump_variable = m_option_variable.show_globals; 614 if (dump_variable && m_option_variable.show_scope) 615 s.PutCString("GLOBAL: "); 616 break; 617 618 case eValueTypeVariableStatic: 619 dump_variable = m_option_variable.show_globals; 620 if (dump_variable && m_option_variable.show_scope) 621 s.PutCString("STATIC: "); 622 break; 623 624 case eValueTypeVariableArgument: 625 dump_variable = m_option_variable.show_args; 626 if (dump_variable && m_option_variable.show_scope) 627 s.PutCString(" ARG: "); 628 break; 629 630 case eValueTypeVariableLocal: 631 dump_variable = m_option_variable.show_locals; 632 if (dump_variable && m_option_variable.show_scope) 633 s.PutCString(" LOCAL: "); 634 break; 635 636 default: 637 break; 638 } 639 640 if (dump_variable) 641 { 642 // Use the variable object code to make sure we are 643 // using the same APIs as the the public API will be 644 // using... 645 valobj_sp = frame->GetValueObjectForFrameVariable (var_sp, 646 m_varobj_options.use_dynamic); 647 if (valobj_sp) 648 { 649 // if (format != eFormatDefault) 650 // valobj_sp->SetFormat (format); 651 652 // When dumping all variables, don't print any variables 653 // that are not in scope to avoid extra unneeded output 654 if (valobj_sp->IsInScope ()) 655 { 656 if (m_option_variable.show_decl && var_sp->GetDeclaration ().GetFile()) 657 { 658 var_sp->GetDeclaration ().DumpStopContext (&s, false); 659 s.PutCString (": "); 660 } 661 if (summary_format_sp) 662 valobj_sp->SetCustomSummaryFormat(summary_format_sp); 663 ValueObject::DumpValueObject (result.GetOutputStream(), 664 valobj_sp.get(), 665 name_cstr, 666 options, 667 format); 668 } 669 } 670 } 671 } 672 } 673 } 674 result.SetStatus (eReturnStatusSuccessFinishResult); 675 } 676 677 if (m_interpreter.TruncationWarningNecessary()) 678 { 679 result.GetOutputStream().Printf(m_interpreter.TruncationWarningText(), 680 m_cmd_name.c_str()); 681 m_interpreter.TruncationWarningGiven(); 682 } 683 684 return result.Succeeded(); 685 } 686 protected: 687 688 OptionGroupOptions m_option_group; 689 OptionGroupVariable m_option_variable; 690 OptionGroupFormat m_option_format; 691 OptionGroupWatchpoint m_option_watchpoint; 692 OptionGroupValueObjectDisplay m_varobj_options; 693 }; 694 695 696 #pragma mark CommandObjectMultiwordFrame 697 698 //------------------------------------------------------------------------- 699 // CommandObjectMultiwordFrame 700 //------------------------------------------------------------------------- 701 702 CommandObjectMultiwordFrame::CommandObjectMultiwordFrame (CommandInterpreter &interpreter) : 703 CommandObjectMultiword (interpreter, 704 "frame", 705 "A set of commands for operating on the current thread's frames.", 706 "frame <subcommand> [<subcommand-options>]") 707 { 708 LoadSubCommand ("info", CommandObjectSP (new CommandObjectFrameInfo (interpreter))); 709 LoadSubCommand ("select", CommandObjectSP (new CommandObjectFrameSelect (interpreter))); 710 LoadSubCommand ("variable", CommandObjectSP (new CommandObjectFrameVariable (interpreter))); 711 } 712 713 CommandObjectMultiwordFrame::~CommandObjectMultiwordFrame () 714 { 715 } 716 717