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