1 //===-- ObjCLanguageRuntime.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 #include "clang/AST/Type.h" 10 11 #include "lldb/Core/Log.h" 12 #include "lldb/Core/MappedHash.h" 13 #include "lldb/Core/Module.h" 14 #include "lldb/Core/PluginManager.h" 15 #include "lldb/Core/Timer.h" 16 #include "lldb/Core/ValueObject.h" 17 #include "lldb/Symbol/ClangASTContext.h" 18 #include "lldb/Symbol/SymbolContext.h" 19 #include "lldb/Symbol/Type.h" 20 #include "lldb/Symbol/TypeList.h" 21 #include "lldb/Target/ObjCLanguageRuntime.h" 22 #include "lldb/Target/Target.h" 23 24 #include "llvm/ADT/StringRef.h" 25 26 using namespace lldb; 27 using namespace lldb_private; 28 29 //---------------------------------------------------------------------- 30 // Destructor 31 //---------------------------------------------------------------------- 32 ObjCLanguageRuntime::~ObjCLanguageRuntime() 33 { 34 } 35 36 ObjCLanguageRuntime::ObjCLanguageRuntime (Process *process) : 37 LanguageRuntime (process), 38 m_impl_cache(), 39 m_has_new_literals_and_indexing (eLazyBoolCalculate), 40 m_isa_to_descriptor(), 41 m_hash_to_isa_map(), 42 m_type_size_cache(), 43 m_isa_to_descriptor_stop_id (UINT32_MAX), 44 m_complete_class_cache(), 45 m_negative_complete_class_cache() 46 { 47 } 48 49 bool 50 ObjCLanguageRuntime::AddClass (ObjCISA isa, const ClassDescriptorSP &descriptor_sp, const char *class_name) 51 { 52 if (isa != 0) 53 { 54 m_isa_to_descriptor[isa] = descriptor_sp; 55 // class_name is assumed to be valid 56 m_hash_to_isa_map.insert(std::make_pair(MappedHash::HashStringUsingDJB(class_name), isa)); 57 return true; 58 } 59 return false; 60 } 61 62 void 63 ObjCLanguageRuntime::AddToMethodCache (lldb::addr_t class_addr, lldb::addr_t selector, lldb::addr_t impl_addr) 64 { 65 Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); 66 if (log) 67 { 68 log->Printf ("Caching: class 0x%" PRIx64 " selector 0x%" PRIx64 " implementation 0x%" PRIx64 ".", class_addr, selector, impl_addr); 69 } 70 m_impl_cache.insert (std::pair<ClassAndSel,lldb::addr_t> (ClassAndSel(class_addr, selector), impl_addr)); 71 } 72 73 lldb::addr_t 74 ObjCLanguageRuntime::LookupInMethodCache (lldb::addr_t class_addr, lldb::addr_t selector) 75 { 76 MsgImplMap::iterator pos, end = m_impl_cache.end(); 77 pos = m_impl_cache.find (ClassAndSel(class_addr, selector)); 78 if (pos != end) 79 return (*pos).second; 80 return LLDB_INVALID_ADDRESS; 81 } 82 83 84 lldb::TypeSP 85 ObjCLanguageRuntime::LookupInCompleteClassCache (ConstString &name) 86 { 87 CompleteClassMap::iterator complete_class_iter = m_complete_class_cache.find(name); 88 89 if (complete_class_iter != m_complete_class_cache.end()) 90 { 91 // Check the weak pointer to make sure the type hasn't been unloaded 92 TypeSP complete_type_sp (complete_class_iter->second.lock()); 93 94 if (complete_type_sp) 95 return complete_type_sp; 96 else 97 m_complete_class_cache.erase(name); 98 } 99 100 if (m_negative_complete_class_cache.count(name) > 0) 101 return TypeSP(); 102 103 const ModuleList &modules = m_process->GetTarget().GetImages(); 104 105 SymbolContextList sc_list; 106 const size_t matching_symbols = modules.FindSymbolsWithNameAndType (name, 107 eSymbolTypeObjCClass, 108 sc_list); 109 110 if (matching_symbols) 111 { 112 SymbolContext sc; 113 114 sc_list.GetContextAtIndex(0, sc); 115 116 ModuleSP module_sp(sc.module_sp); 117 118 if (!module_sp) 119 return TypeSP(); 120 121 const SymbolContext null_sc; 122 const bool exact_match = true; 123 const uint32_t max_matches = UINT32_MAX; 124 TypeList types; 125 126 const uint32_t num_types = module_sp->FindTypes (null_sc, 127 name, 128 exact_match, 129 max_matches, 130 types); 131 132 if (num_types) 133 { 134 uint32_t i; 135 for (i = 0; i < num_types; ++i) 136 { 137 TypeSP type_sp (types.GetTypeAtIndex(i)); 138 139 if (type_sp->GetClangForwardType().IsObjCObjectOrInterfaceType()) 140 { 141 if (type_sp->IsCompleteObjCClass()) 142 { 143 m_complete_class_cache[name] = type_sp; 144 return type_sp; 145 } 146 } 147 } 148 } 149 } 150 m_negative_complete_class_cache.insert(name); 151 return TypeSP(); 152 } 153 154 size_t 155 ObjCLanguageRuntime::GetByteOffsetForIvar (ClangASTType &parent_qual_type, const char *ivar_name) 156 { 157 return LLDB_INVALID_IVAR_OFFSET; 158 } 159 160 void 161 ObjCLanguageRuntime::MethodName::Clear() 162 { 163 m_full.Clear(); 164 m_class.Clear(); 165 m_category.Clear(); 166 m_selector.Clear(); 167 m_type = eTypeUnspecified; 168 m_category_is_valid = false; 169 } 170 171 //bool 172 //ObjCLanguageRuntime::MethodName::SetName (const char *name, bool strict) 173 //{ 174 // Clear(); 175 // if (name && name[0]) 176 // { 177 // // If "strict" is true. then the method must be specified with a 178 // // '+' or '-' at the beginning. If "strict" is false, then the '+' 179 // // or '-' can be omitted 180 // bool valid_prefix = false; 181 // 182 // if (name[0] == '+' || name[0] == '-') 183 // { 184 // valid_prefix = name[1] == '['; 185 // } 186 // else if (!strict) 187 // { 188 // // "strict" is false, the name just needs to start with '[' 189 // valid_prefix = name[0] == '['; 190 // } 191 // 192 // if (valid_prefix) 193 // { 194 // static RegularExpression g_regex("^([-+]?)\\[([A-Za-z_][A-Za-z_0-9]*)(\\([A-Za-z_][A-Za-z_0-9]*\\))? ([A-Za-z_][A-Za-z_0-9:]*)\\]$"); 195 // llvm::StringRef matches[4]; 196 // // Since we are using a global regular expression, we must use the threadsafe version of execute 197 // if (g_regex.ExecuteThreadSafe(name, matches, 4)) 198 // { 199 // m_full.SetCString(name); 200 // if (matches[0].empty()) 201 // m_type = eTypeUnspecified; 202 // else if (matches[0][0] == '+') 203 // m_type = eTypeClassMethod; 204 // else 205 // m_type = eTypeInstanceMethod; 206 // m_class.SetString(matches[1]); 207 // m_selector.SetString(matches[3]); 208 // if (!matches[2].empty()) 209 // m_category.SetString(matches[2]); 210 // } 211 // } 212 // } 213 // return IsValid(strict); 214 //} 215 216 bool 217 ObjCLanguageRuntime::MethodName::SetName (const char *name, bool strict) 218 { 219 Clear(); 220 if (name && name[0]) 221 { 222 // If "strict" is true. then the method must be specified with a 223 // '+' or '-' at the beginning. If "strict" is false, then the '+' 224 // or '-' can be omitted 225 bool valid_prefix = false; 226 227 if (name[0] == '+' || name[0] == '-') 228 { 229 valid_prefix = name[1] == '['; 230 if (name[0] == '+') 231 m_type = eTypeClassMethod; 232 else 233 m_type = eTypeInstanceMethod; 234 } 235 else if (!strict) 236 { 237 // "strict" is false, the name just needs to start with '[' 238 valid_prefix = name[0] == '['; 239 } 240 241 if (valid_prefix) 242 { 243 int name_len = strlen (name); 244 // Objective C methods must have at least: 245 // "-[" or "+[" prefix 246 // One character for a class name 247 // One character for the space between the class name 248 // One character for the method name 249 // "]" suffix 250 if (name_len >= (5 + (strict ? 1 : 0)) && name[name_len - 1] == ']') 251 { 252 m_full.SetCStringWithLength(name, name_len); 253 } 254 } 255 } 256 return IsValid(strict); 257 } 258 259 const ConstString & 260 ObjCLanguageRuntime::MethodName::GetClassName () 261 { 262 if (!m_class) 263 { 264 if (IsValid(false)) 265 { 266 const char *full = m_full.GetCString(); 267 const char *class_start = (full[0] == '[' ? full + 1 : full + 2); 268 const char *paren_pos = strchr (class_start, '('); 269 if (paren_pos) 270 { 271 m_class.SetCStringWithLength (class_start, paren_pos - class_start); 272 } 273 else 274 { 275 // No '(' was found in the full name, we can definitively say 276 // that our category was valid (and empty). 277 m_category_is_valid = true; 278 const char *space_pos = strchr (full, ' '); 279 if (space_pos) 280 { 281 m_class.SetCStringWithLength (class_start, space_pos - class_start); 282 if (!m_class_category) 283 { 284 // No category in name, so we can also fill in the m_class_category 285 m_class_category = m_class; 286 } 287 } 288 } 289 } 290 } 291 return m_class; 292 } 293 294 const ConstString & 295 ObjCLanguageRuntime::MethodName::GetClassNameWithCategory () 296 { 297 if (!m_class_category) 298 { 299 if (IsValid(false)) 300 { 301 const char *full = m_full.GetCString(); 302 const char *class_start = (full[0] == '[' ? full + 1 : full + 2); 303 const char *space_pos = strchr (full, ' '); 304 if (space_pos) 305 { 306 m_class_category.SetCStringWithLength (class_start, space_pos - class_start); 307 // If m_class hasn't been filled in and the class with category doesn't 308 // contain a '(', then we can also fill in the m_class 309 if (!m_class && strchr (m_class_category.GetCString(), '(') == NULL) 310 { 311 m_class = m_class_category; 312 // No '(' was found in the full name, we can definitively say 313 // that our category was valid (and empty). 314 m_category_is_valid = true; 315 316 } 317 } 318 } 319 } 320 return m_class_category; 321 } 322 323 const ConstString & 324 ObjCLanguageRuntime::MethodName::GetSelector () 325 { 326 if (!m_selector) 327 { 328 if (IsValid(false)) 329 { 330 const char *full = m_full.GetCString(); 331 const char *space_pos = strchr (full, ' '); 332 if (space_pos) 333 { 334 ++space_pos; // skip the space 335 m_selector.SetCStringWithLength (space_pos, m_full.GetLength() - (space_pos - full) - 1); 336 } 337 } 338 } 339 return m_selector; 340 } 341 342 const ConstString & 343 ObjCLanguageRuntime::MethodName::GetCategory () 344 { 345 if (!m_category_is_valid && !m_category) 346 { 347 if (IsValid(false)) 348 { 349 m_category_is_valid = true; 350 const char *full = m_full.GetCString(); 351 const char *class_start = (full[0] == '[' ? full + 1 : full + 2); 352 const char *open_paren_pos = strchr (class_start, '('); 353 if (open_paren_pos) 354 { 355 ++open_paren_pos; // Skip the open paren 356 const char *close_paren_pos = strchr (open_paren_pos, ')'); 357 if (close_paren_pos) 358 m_category.SetCStringWithLength (open_paren_pos, close_paren_pos - open_paren_pos); 359 } 360 } 361 } 362 return m_category; 363 } 364 365 ConstString 366 ObjCLanguageRuntime::MethodName::GetFullNameWithoutCategory (bool empty_if_no_category) 367 { 368 if (IsValid(false)) 369 { 370 if (HasCategory()) 371 { 372 StreamString strm; 373 if (m_type == eTypeClassMethod) 374 strm.PutChar('+'); 375 else if (m_type == eTypeInstanceMethod) 376 strm.PutChar('-'); 377 strm.Printf("[%s %s]", GetClassName().GetCString(), GetSelector().GetCString()); 378 return ConstString(strm.GetString().c_str()); 379 } 380 381 if (!empty_if_no_category) 382 { 383 // Just return the full name since it doesn't have a category 384 return GetFullName(); 385 } 386 } 387 return ConstString(); 388 } 389 390 size_t 391 ObjCLanguageRuntime::MethodName::GetFullNames (std::vector<ConstString> &names, bool append) 392 { 393 if (!append) 394 names.clear(); 395 if (IsValid(false)) 396 { 397 StreamString strm; 398 const bool is_class_method = m_type == eTypeClassMethod; 399 const bool is_instance_method = m_type == eTypeInstanceMethod; 400 const ConstString &category = GetCategory(); 401 if (is_class_method || is_instance_method) 402 { 403 names.push_back (m_full); 404 if (category) 405 { 406 strm.Printf("%c[%s %s]", 407 is_class_method ? '+' : '-', 408 GetClassName().GetCString(), 409 GetSelector().GetCString()); 410 names.push_back(ConstString(strm.GetString().c_str())); 411 } 412 } 413 else 414 { 415 const ConstString &class_name = GetClassName(); 416 const ConstString &selector = GetSelector(); 417 strm.Printf("+[%s %s]", class_name.GetCString(), selector.GetCString()); 418 names.push_back(ConstString(strm.GetString().c_str())); 419 strm.Clear(); 420 strm.Printf("-[%s %s]", class_name.GetCString(), selector.GetCString()); 421 names.push_back(ConstString(strm.GetString().c_str())); 422 strm.Clear(); 423 if (category) 424 { 425 strm.Printf("+[%s(%s) %s]", class_name.GetCString(), category.GetCString(), selector.GetCString()); 426 names.push_back(ConstString(strm.GetString().c_str())); 427 strm.Clear(); 428 strm.Printf("-[%s(%s) %s]", class_name.GetCString(), category.GetCString(), selector.GetCString()); 429 names.push_back(ConstString(strm.GetString().c_str())); 430 } 431 } 432 } 433 return names.size(); 434 } 435 436 437 bool 438 ObjCLanguageRuntime::ClassDescriptor::IsPointerValid (lldb::addr_t value, 439 uint32_t ptr_size, 440 bool allow_NULLs, 441 bool allow_tagged, 442 bool check_version_specific) const 443 { 444 if (!value) 445 return allow_NULLs; 446 if ( (value % 2) == 1 && allow_tagged) 447 return true; 448 if ((value % ptr_size) == 0) 449 return (check_version_specific ? CheckPointer(value,ptr_size) : true); 450 else 451 return false; 452 } 453 454 ObjCLanguageRuntime::ObjCISA 455 ObjCLanguageRuntime::GetISA(const ConstString &name) 456 { 457 ISAToDescriptorIterator pos = GetDescriptorIterator (name); 458 if (pos != m_isa_to_descriptor.end()) 459 return pos->first; 460 return 0; 461 } 462 463 ObjCLanguageRuntime::ISAToDescriptorIterator 464 ObjCLanguageRuntime::GetDescriptorIterator (const ConstString &name) 465 { 466 ISAToDescriptorIterator end = m_isa_to_descriptor.end(); 467 468 if (name) 469 { 470 UpdateISAToDescriptorMap(); 471 if (m_hash_to_isa_map.empty()) 472 { 473 // No name hashes were provided, we need to just linearly power through the 474 // names and find a match 475 for (ISAToDescriptorIterator pos = m_isa_to_descriptor.begin(); pos != end; ++pos) 476 { 477 if (pos->second->GetClassName() == name) 478 return pos; 479 } 480 } 481 else 482 { 483 // Name hashes were provided, so use them to efficiently lookup name to isa/descriptor 484 const uint32_t name_hash = MappedHash::HashStringUsingDJB (name.GetCString()); 485 std::pair <HashToISAIterator, HashToISAIterator> range = m_hash_to_isa_map.equal_range(name_hash); 486 for (HashToISAIterator range_pos = range.first; range_pos != range.second; ++range_pos) 487 { 488 ISAToDescriptorIterator pos = m_isa_to_descriptor.find (range_pos->second); 489 if (pos != m_isa_to_descriptor.end()) 490 { 491 if (pos->second->GetClassName() == name) 492 return pos; 493 } 494 } 495 } 496 } 497 return end; 498 } 499 500 std::pair<ObjCLanguageRuntime::ISAToDescriptorIterator,ObjCLanguageRuntime::ISAToDescriptorIterator> 501 ObjCLanguageRuntime::GetDescriptorIteratorPair (bool update_if_needed) 502 { 503 if (update_if_needed) 504 UpdateISAToDescriptorMapIfNeeded(); 505 506 return std::pair<ObjCLanguageRuntime::ISAToDescriptorIterator, 507 ObjCLanguageRuntime::ISAToDescriptorIterator>( 508 m_isa_to_descriptor.begin(), 509 m_isa_to_descriptor.end()); 510 } 511 512 513 ObjCLanguageRuntime::ObjCISA 514 ObjCLanguageRuntime::GetParentClass(ObjCLanguageRuntime::ObjCISA isa) 515 { 516 ClassDescriptorSP objc_class_sp (GetClassDescriptorFromISA(isa)); 517 if (objc_class_sp) 518 { 519 ClassDescriptorSP objc_super_class_sp (objc_class_sp->GetSuperclass()); 520 if (objc_super_class_sp) 521 return objc_super_class_sp->GetISA(); 522 } 523 return 0; 524 } 525 526 ConstString 527 ObjCLanguageRuntime::GetActualTypeName(ObjCLanguageRuntime::ObjCISA isa) 528 { 529 ClassDescriptorSP objc_class_sp (GetNonKVOClassDescriptor(isa)); 530 if (objc_class_sp) 531 return objc_class_sp->GetClassName(); 532 return ConstString(); 533 } 534 535 ObjCLanguageRuntime::ClassDescriptorSP 536 ObjCLanguageRuntime::GetClassDescriptorFromClassName (const ConstString &class_name) 537 { 538 ISAToDescriptorIterator pos = GetDescriptorIterator (class_name); 539 if (pos != m_isa_to_descriptor.end()) 540 return pos->second; 541 return ClassDescriptorSP(); 542 543 } 544 545 ObjCLanguageRuntime::ClassDescriptorSP 546 ObjCLanguageRuntime::GetClassDescriptor (ValueObject& valobj) 547 { 548 ClassDescriptorSP objc_class_sp; 549 // if we get an invalid VO (which might still happen when playing around 550 // with pointers returned by the expression parser, don't consider this 551 // a valid ObjC object) 552 if (valobj.GetClangType().IsValid()) 553 { 554 addr_t isa_pointer = valobj.GetPointerValue(); 555 if (isa_pointer != LLDB_INVALID_ADDRESS) 556 { 557 ExecutionContext exe_ctx (valobj.GetExecutionContextRef()); 558 559 Process *process = exe_ctx.GetProcessPtr(); 560 if (process) 561 { 562 Error error; 563 ObjCISA isa = process->ReadPointerFromMemory(isa_pointer, error); 564 if (isa != LLDB_INVALID_ADDRESS) 565 objc_class_sp = GetClassDescriptorFromISA (isa); 566 } 567 } 568 } 569 return objc_class_sp; 570 } 571 572 ObjCLanguageRuntime::ClassDescriptorSP 573 ObjCLanguageRuntime::GetNonKVOClassDescriptor (ValueObject& valobj) 574 { 575 ObjCLanguageRuntime::ClassDescriptorSP objc_class_sp (GetClassDescriptor (valobj)); 576 if (objc_class_sp) 577 { 578 if (!objc_class_sp->IsKVO()) 579 return objc_class_sp; 580 581 ClassDescriptorSP non_kvo_objc_class_sp(objc_class_sp->GetSuperclass()); 582 if (non_kvo_objc_class_sp && non_kvo_objc_class_sp->IsValid()) 583 return non_kvo_objc_class_sp; 584 } 585 return ClassDescriptorSP(); 586 } 587 588 589 ObjCLanguageRuntime::ClassDescriptorSP 590 ObjCLanguageRuntime::GetClassDescriptorFromISA (ObjCISA isa) 591 { 592 if (isa) 593 { 594 UpdateISAToDescriptorMap(); 595 ObjCLanguageRuntime::ISAToDescriptorIterator pos = m_isa_to_descriptor.find(isa); 596 if (pos != m_isa_to_descriptor.end()) 597 return pos->second; 598 } 599 return ClassDescriptorSP(); 600 } 601 602 ObjCLanguageRuntime::ClassDescriptorSP 603 ObjCLanguageRuntime::GetNonKVOClassDescriptor (ObjCISA isa) 604 { 605 if (isa) 606 { 607 ClassDescriptorSP objc_class_sp = GetClassDescriptorFromISA (isa); 608 if (objc_class_sp && objc_class_sp->IsValid()) 609 { 610 if (!objc_class_sp->IsKVO()) 611 return objc_class_sp; 612 613 ClassDescriptorSP non_kvo_objc_class_sp(objc_class_sp->GetSuperclass()); 614 if (non_kvo_objc_class_sp && non_kvo_objc_class_sp->IsValid()) 615 return non_kvo_objc_class_sp; 616 } 617 } 618 return ClassDescriptorSP(); 619 } 620 621 622 ClangASTType 623 ObjCLanguageRuntime::EncodingToType::RealizeType (const char* name, bool for_expression) 624 { 625 if (m_scratch_ast_ctx_ap) 626 return RealizeType(*m_scratch_ast_ctx_ap, name, for_expression); 627 return ClangASTType(); 628 } 629 630 ClangASTType 631 ObjCLanguageRuntime::EncodingToType::RealizeType (ClangASTContext& ast_ctx, const char* name, bool for_expression) 632 { 633 clang::ASTContext *clang_ast = ast_ctx.getASTContext(); 634 if (!clang_ast) 635 return ClangASTType(); 636 return RealizeType(*clang_ast, name, for_expression); 637 } 638 639 ObjCLanguageRuntime::EncodingToType::~EncodingToType() {} 640 641 ObjCLanguageRuntime::EncodingToTypeSP 642 ObjCLanguageRuntime::GetEncodingToType () 643 { 644 return nullptr; 645 } 646 647 bool 648 ObjCLanguageRuntime::GetTypeBitSize (const ClangASTType& clang_type, 649 uint64_t &size) 650 { 651 void *opaque_ptr = clang_type.GetQualType().getAsOpaquePtr(); 652 size = m_type_size_cache.Lookup(opaque_ptr); 653 // an ObjC object will at least have an ISA, so 0 is definitely not OK 654 if (size > 0) 655 return true; 656 657 ClassDescriptorSP class_descriptor_sp = GetClassDescriptorFromClassName(clang_type.GetTypeName()); 658 if (!class_descriptor_sp) 659 return false; 660 661 int32_t max_offset = INT32_MIN; 662 uint64_t sizeof_max = 0; 663 bool found = false; 664 665 for (size_t idx = 0; 666 idx < class_descriptor_sp->GetNumIVars(); 667 idx++) 668 { 669 const auto& ivar = class_descriptor_sp->GetIVarAtIndex(idx); 670 int32_t cur_offset = ivar.m_offset; 671 if (cur_offset > max_offset) 672 { 673 max_offset = cur_offset; 674 sizeof_max = ivar.m_size; 675 found = true; 676 } 677 } 678 679 size = 8 * (max_offset + sizeof_max); 680 if (found) 681 m_type_size_cache.Insert(opaque_ptr, size); 682 683 return found; 684 } 685 686 //------------------------------------------------------------------ 687 // Exception breakpoint Precondition class for ObjC: 688 //------------------------------------------------------------------ 689 void 690 ObjCLanguageRuntime::ObjCExceptionPrecondition::AddClassName(const char *class_name) 691 { 692 m_class_names.insert(class_name); 693 } 694 695 ObjCLanguageRuntime::ObjCExceptionPrecondition::ObjCExceptionPrecondition() 696 { 697 } 698 699 bool 700 ObjCLanguageRuntime::ObjCExceptionPrecondition::EvaluatePrecondition(StoppointCallbackContext &context) 701 { 702 return true; 703 } 704 705 void 706 ObjCLanguageRuntime::ObjCExceptionPrecondition::DescribePrecondition(Stream &stream, lldb::DescriptionLevel level) 707 { 708 } 709 710 Error 711 ObjCLanguageRuntime::ObjCExceptionPrecondition::ConfigurePrecondition(Args &args) 712 { 713 Error error; 714 if (args.GetArgumentCount() > 0) 715 error.SetErrorString("The ObjC Exception breakpoint doesn't support extra options."); 716 return error; 717 } 718