//===-- ObjCLanguageRuntime.cpp ---------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "clang/AST/Type.h" #include "lldb/Core/Log.h" #include "lldb/Core/MappedHash.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/Timer.h" #include "lldb/Core/ValueObject.h" #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Symbol/Type.h" #include "lldb/Symbol/TypeList.h" #include "lldb/Target/ObjCLanguageRuntime.h" #include "lldb/Target/Target.h" #include "llvm/ADT/StringRef.h" using namespace lldb; using namespace lldb_private; //---------------------------------------------------------------------- // Destructor //---------------------------------------------------------------------- ObjCLanguageRuntime::~ObjCLanguageRuntime() { } ObjCLanguageRuntime::ObjCLanguageRuntime (Process *process) : LanguageRuntime (process), m_has_new_literals_and_indexing (eLazyBoolCalculate), m_isa_to_descriptor(), m_isa_to_descriptor_stop_id (UINT32_MAX) { } bool ObjCLanguageRuntime::AddClass (ObjCISA isa, const ClassDescriptorSP &descriptor_sp, const char *class_name) { if (isa != 0) { m_isa_to_descriptor[isa] = descriptor_sp; // class_name is assumed to be valid m_hash_to_isa_map.insert(std::make_pair(MappedHash::HashStringUsingDJB(class_name), isa)); return true; } return false; } void ObjCLanguageRuntime::AddToMethodCache (lldb::addr_t class_addr, lldb::addr_t selector, lldb::addr_t impl_addr) { Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); if (log) { log->Printf ("Caching: class 0x%" PRIx64 " selector 0x%" PRIx64 " implementation 0x%" PRIx64 ".", class_addr, selector, impl_addr); } m_impl_cache.insert (std::pair<ClassAndSel,lldb::addr_t> (ClassAndSel(class_addr, selector), impl_addr)); } lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache (lldb::addr_t class_addr, lldb::addr_t selector) { MsgImplMap::iterator pos, end = m_impl_cache.end(); pos = m_impl_cache.find (ClassAndSel(class_addr, selector)); if (pos != end) return (*pos).second; return LLDB_INVALID_ADDRESS; } lldb::TypeSP ObjCLanguageRuntime::LookupInCompleteClassCache (ConstString &name) { CompleteClassMap::iterator complete_class_iter = m_complete_class_cache.find(name); if (complete_class_iter != m_complete_class_cache.end()) { // Check the weak pointer to make sure the type hasn't been unloaded TypeSP complete_type_sp (complete_class_iter->second.lock()); if (complete_type_sp) return complete_type_sp; else m_complete_class_cache.erase(name); } if (m_negative_complete_class_cache.count(name) > 0) return TypeSP(); const ModuleList &modules = m_process->GetTarget().GetImages(); SymbolContextList sc_list; const size_t matching_symbols = modules.FindSymbolsWithNameAndType (name, eSymbolTypeObjCClass, sc_list); if (matching_symbols) { SymbolContext sc; sc_list.GetContextAtIndex(0, sc); ModuleSP module_sp(sc.module_sp); if (!module_sp) return TypeSP(); const SymbolContext null_sc; const bool exact_match = true; const uint32_t max_matches = UINT32_MAX; TypeList types; const uint32_t num_types = module_sp->FindTypes (null_sc, name, exact_match, max_matches, types); if (num_types) { uint32_t i; for (i = 0; i < num_types; ++i) { TypeSP type_sp (types.GetTypeAtIndex(i)); if (type_sp->GetClangForwardType().IsObjCObjectOrInterfaceType()) { if (type_sp->IsCompleteObjCClass()) { m_complete_class_cache[name] = type_sp; return type_sp; } } } } } m_negative_complete_class_cache.insert(name); return TypeSP(); } size_t ObjCLanguageRuntime::GetByteOffsetForIvar (ClangASTType &parent_qual_type, const char *ivar_name) { return LLDB_INVALID_IVAR_OFFSET; } void ObjCLanguageRuntime::MethodName::Clear() { m_full.Clear(); m_class.Clear(); m_category.Clear(); m_selector.Clear(); m_type = eTypeUnspecified; m_category_is_valid = false; } //bool //ObjCLanguageRuntime::MethodName::SetName (const char *name, bool strict) //{ // Clear(); // if (name && name[0]) // { // // If "strict" is true. then the method must be specified with a // // '+' or '-' at the beginning. If "strict" is false, then the '+' // // or '-' can be omitted // bool valid_prefix = false; // // if (name[0] == '+' || name[0] == '-') // { // valid_prefix = name[1] == '['; // } // else if (!strict) // { // // "strict" is false, the name just needs to start with '[' // valid_prefix = name[0] == '['; // } // // if (valid_prefix) // { // 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:]*)\\]$"); // llvm::StringRef matches[4]; // // Since we are using a global regular expression, we must use the threadsafe version of execute // if (g_regex.ExecuteThreadSafe(name, matches, 4)) // { // m_full.SetCString(name); // if (matches[0].empty()) // m_type = eTypeUnspecified; // else if (matches[0][0] == '+') // m_type = eTypeClassMethod; // else // m_type = eTypeInstanceMethod; // m_class.SetString(matches[1]); // m_selector.SetString(matches[3]); // if (!matches[2].empty()) // m_category.SetString(matches[2]); // } // } // } // return IsValid(strict); //} bool ObjCLanguageRuntime::MethodName::SetName (const char *name, bool strict) { Clear(); if (name && name[0]) { // If "strict" is true. then the method must be specified with a // '+' or '-' at the beginning. If "strict" is false, then the '+' // or '-' can be omitted bool valid_prefix = false; if (name[0] == '+' || name[0] == '-') { valid_prefix = name[1] == '['; if (name[0] == '+') m_type = eTypeClassMethod; else m_type = eTypeInstanceMethod; } else if (!strict) { // "strict" is false, the name just needs to start with '[' valid_prefix = name[0] == '['; } if (valid_prefix) { int name_len = strlen (name); // Objective C methods must have at least: // "-[" or "+[" prefix // One character for a class name // One character for the space between the class name // One character for the method name // "]" suffix if (name_len >= (5 + (strict ? 1 : 0)) && name[name_len - 1] == ']') { m_full.SetCStringWithLength(name, name_len); } } } return IsValid(strict); } const ConstString & ObjCLanguageRuntime::MethodName::GetClassName () { if (!m_class) { if (IsValid(false)) { const char *full = m_full.GetCString(); const char *class_start = (full[0] == '[' ? full + 1 : full + 2); const char *paren_pos = strchr (class_start, '('); if (paren_pos) { m_class.SetCStringWithLength (class_start, paren_pos - class_start); } else { // No '(' was found in the full name, we can definitively say // that our category was valid (and empty). m_category_is_valid = true; const char *space_pos = strchr (full, ' '); if (space_pos) { m_class.SetCStringWithLength (class_start, space_pos - class_start); if (!m_class_category) { // No category in name, so we can also fill in the m_class_category m_class_category = m_class; } } } } } return m_class; } const ConstString & ObjCLanguageRuntime::MethodName::GetClassNameWithCategory () { if (!m_class_category) { if (IsValid(false)) { const char *full = m_full.GetCString(); const char *class_start = (full[0] == '[' ? full + 1 : full + 2); const char *space_pos = strchr (full, ' '); if (space_pos) { m_class_category.SetCStringWithLength (class_start, space_pos - class_start); // If m_class hasn't been filled in and the class with category doesn't // contain a '(', then we can also fill in the m_class if (!m_class && strchr (m_class_category.GetCString(), '(') == NULL) { m_class = m_class_category; // No '(' was found in the full name, we can definitively say // that our category was valid (and empty). m_category_is_valid = true; } } } } return m_class_category; } const ConstString & ObjCLanguageRuntime::MethodName::GetSelector () { if (!m_selector) { if (IsValid(false)) { const char *full = m_full.GetCString(); const char *space_pos = strchr (full, ' '); if (space_pos) { ++space_pos; // skip the space m_selector.SetCStringWithLength (space_pos, m_full.GetLength() - (space_pos - full) - 1); } } } return m_selector; } const ConstString & ObjCLanguageRuntime::MethodName::GetCategory () { if (!m_category_is_valid && !m_category) { if (IsValid(false)) { m_category_is_valid = true; const char *full = m_full.GetCString(); const char *class_start = (full[0] == '[' ? full + 1 : full + 2); const char *open_paren_pos = strchr (class_start, '('); if (open_paren_pos) { ++open_paren_pos; // Skip the open paren const char *close_paren_pos = strchr (open_paren_pos, ')'); if (close_paren_pos) m_category.SetCStringWithLength (open_paren_pos, close_paren_pos - open_paren_pos); } } } return m_category; } ConstString ObjCLanguageRuntime::MethodName::GetFullNameWithoutCategory (bool empty_if_no_category) { if (IsValid(false)) { if (HasCategory()) { StreamString strm; if (m_type == eTypeClassMethod) strm.PutChar('+'); else if (m_type == eTypeInstanceMethod) strm.PutChar('-'); strm.Printf("[%s %s]", GetClassName().GetCString(), GetSelector().GetCString()); return ConstString(strm.GetString().c_str()); } if (!empty_if_no_category) { // Just return the full name since it doesn't have a category return GetFullName(); } } return ConstString(); } size_t ObjCLanguageRuntime::MethodName::GetFullNames (std::vector<ConstString> &names, bool append) { if (!append) names.clear(); if (IsValid(false)) { StreamString strm; const bool is_class_method = m_type == eTypeClassMethod; const bool is_instance_method = m_type == eTypeInstanceMethod; const ConstString &category = GetCategory(); if (is_class_method || is_instance_method) { names.push_back (m_full); if (category) { strm.Printf("%c[%s %s]", is_class_method ? '+' : '-', GetClassName().GetCString(), GetSelector().GetCString()); names.push_back(ConstString(strm.GetString().c_str())); } } else { const ConstString &class_name = GetClassName(); const ConstString &selector = GetSelector(); strm.Printf("+[%s %s]", class_name.GetCString(), selector.GetCString()); names.push_back(ConstString(strm.GetString().c_str())); strm.Clear(); strm.Printf("-[%s %s]", class_name.GetCString(), selector.GetCString()); names.push_back(ConstString(strm.GetString().c_str())); strm.Clear(); if (category) { strm.Printf("+[%s(%s) %s]", class_name.GetCString(), category.GetCString(), selector.GetCString()); names.push_back(ConstString(strm.GetString().c_str())); strm.Clear(); strm.Printf("-[%s(%s) %s]", class_name.GetCString(), category.GetCString(), selector.GetCString()); names.push_back(ConstString(strm.GetString().c_str())); } } } return names.size(); } bool ObjCLanguageRuntime::ClassDescriptor::IsPointerValid (lldb::addr_t value, uint32_t ptr_size, bool allow_NULLs, bool allow_tagged, bool check_version_specific) const { if (!value) return allow_NULLs; if ( (value % 2) == 1 && allow_tagged) return true; if ((value % ptr_size) == 0) return (check_version_specific ? CheckPointer(value,ptr_size) : true); else return false; } ObjCLanguageRuntime::ObjCISA ObjCLanguageRuntime::GetISA(const ConstString &name) { ISAToDescriptorIterator pos = GetDescriptorIterator (name); if (pos != m_isa_to_descriptor.end()) return pos->first; return 0; } ObjCLanguageRuntime::ISAToDescriptorIterator ObjCLanguageRuntime::GetDescriptorIterator (const ConstString &name) { ISAToDescriptorIterator end = m_isa_to_descriptor.end(); if (name) { UpdateISAToDescriptorMap(); if (m_hash_to_isa_map.empty()) { // No name hashes were provided, we need to just linearly power through the // names and find a match for (ISAToDescriptorIterator pos = m_isa_to_descriptor.begin(); pos != end; ++pos) { if (pos->second->GetClassName() == name) return pos; } } else { // Name hashes were provided, so use them to efficiently lookup name to isa/descriptor const uint32_t name_hash = MappedHash::HashStringUsingDJB (name.GetCString()); std::pair <HashToISAIterator, HashToISAIterator> range = m_hash_to_isa_map.equal_range(name_hash); for (HashToISAIterator range_pos = range.first; range_pos != range.second; ++range_pos) { ISAToDescriptorIterator pos = m_isa_to_descriptor.find (range_pos->second); if (pos != m_isa_to_descriptor.end()) { if (pos->second->GetClassName() == name) return pos; } } } } return end; } ObjCLanguageRuntime::ObjCISA ObjCLanguageRuntime::GetParentClass(ObjCLanguageRuntime::ObjCISA isa) { ClassDescriptorSP objc_class_sp (GetClassDescriptorFromISA(isa)); if (objc_class_sp) { ClassDescriptorSP objc_super_class_sp (objc_class_sp->GetSuperclass()); if (objc_super_class_sp) return objc_super_class_sp->GetISA(); } return 0; } ConstString ObjCLanguageRuntime::GetActualTypeName(ObjCLanguageRuntime::ObjCISA isa) { ClassDescriptorSP objc_class_sp (GetNonKVOClassDescriptor(isa)); if (objc_class_sp) return objc_class_sp->GetClassName(); return ConstString(); } ObjCLanguageRuntime::ClassDescriptorSP ObjCLanguageRuntime::GetClassDescriptorFromClassName (const ConstString &class_name) { ISAToDescriptorIterator pos = GetDescriptorIterator (class_name); if (pos != m_isa_to_descriptor.end()) return pos->second; return ClassDescriptorSP(); } ObjCLanguageRuntime::ClassDescriptorSP ObjCLanguageRuntime::GetClassDescriptor (ValueObject& valobj) { ClassDescriptorSP objc_class_sp; // if we get an invalid VO (which might still happen when playing around // with pointers returned by the expression parser, don't consider this // a valid ObjC object) if (valobj.GetClangType().IsValid()) { addr_t isa_pointer = valobj.GetPointerValue(); if (isa_pointer != LLDB_INVALID_ADDRESS) { ExecutionContext exe_ctx (valobj.GetExecutionContextRef()); Process *process = exe_ctx.GetProcessPtr(); if (process) { Error error; ObjCISA isa = process->ReadPointerFromMemory(isa_pointer, error); if (isa != LLDB_INVALID_ADDRESS) objc_class_sp = GetClassDescriptorFromISA (isa); } } } return objc_class_sp; } ObjCLanguageRuntime::ClassDescriptorSP ObjCLanguageRuntime::GetNonKVOClassDescriptor (ValueObject& valobj) { ObjCLanguageRuntime::ClassDescriptorSP objc_class_sp (GetClassDescriptor (valobj)); if (objc_class_sp) { if (!objc_class_sp->IsKVO()) return objc_class_sp; ClassDescriptorSP non_kvo_objc_class_sp(objc_class_sp->GetSuperclass()); if (non_kvo_objc_class_sp && non_kvo_objc_class_sp->IsValid()) return non_kvo_objc_class_sp; } return ClassDescriptorSP(); } ObjCLanguageRuntime::ClassDescriptorSP ObjCLanguageRuntime::GetClassDescriptorFromISA (ObjCISA isa) { if (isa) { UpdateISAToDescriptorMap(); ObjCLanguageRuntime::ISAToDescriptorIterator pos = m_isa_to_descriptor.find(isa); if (pos != m_isa_to_descriptor.end()) return pos->second; } return ClassDescriptorSP(); } ObjCLanguageRuntime::ClassDescriptorSP ObjCLanguageRuntime::GetNonKVOClassDescriptor (ObjCISA isa) { if (isa) { ClassDescriptorSP objc_class_sp = GetClassDescriptorFromISA (isa); if (objc_class_sp && objc_class_sp->IsValid()) { if (!objc_class_sp->IsKVO()) return objc_class_sp; ClassDescriptorSP non_kvo_objc_class_sp(objc_class_sp->GetSuperclass()); if (non_kvo_objc_class_sp && non_kvo_objc_class_sp->IsValid()) return non_kvo_objc_class_sp; } } return ClassDescriptorSP(); }