diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h --- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h +++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h @@ -25,62 +25,98 @@ public: class MethodName { public: - enum Type { eTypeUnspecified, eTypeClassMethod, eTypeInstanceMethod }; - - MethodName() : m_full(), m_class(), m_category(), m_selector() {} - - MethodName(const char *name, bool strict) - : m_full(), m_class(), m_category(), m_selector(), - m_type(eTypeUnspecified), m_category_is_valid(false) { - SetName(name, strict); - } - MethodName(llvm::StringRef name, bool strict) - : m_full(), m_class(), m_category(), m_selector(), - m_type(eTypeUnspecified), m_category_is_valid(false) { - SetName(name, strict); - } - - void Clear(); - - bool IsValid(bool strict) const { - // If "strict" is true, the name must have everything specified including - // the leading "+" or "-" on the method name - if (strict && m_type == eTypeUnspecified) - return false; - // Other than that, m_full will only be filled in if the objective C - // name is valid. - return (bool)m_full; - } - - bool HasCategory() { return !GetCategory().IsEmpty(); } - - Type GetType() const { return m_type; } - - ConstString GetFullName() const { return m_full; } + /// The static factory method for creating a MethodName. + /// + /// \param[in] name + /// The name of the method. + /// + /// \param[in] strict + /// Control whether or not the name parser is strict about +/- in the + /// front of the name. + /// + /// \return If the name failed to parse as a valid Objective-C method name, + /// returns std::nullopt. Otherwise returns a const MethodName. + static std::optional Create(llvm::StringRef name, + bool strict); + + /// Determines if this method is a class method + /// + /// \return Returns true if the method is a class method. False otherwise. + bool IsClassMethod() const { return m_type == eTypeClassMethod; } + + /// Determines if this method is an instance method + /// + /// \return Returns true if the method is an instance method. False + /// otherwise. + bool IsInstanceMethod() const { return m_type == eTypeInstanceMethod; } + + /// Returns the full name of the method. + /// + /// This includes the class name, the category name (if applicable), and the + /// selector name. + /// + /// \return The name of the method in the form of a const std::string + /// reference. + const std::string &GetFullName() const { return m_full; } + + /// Creates a variation of this method without the category. + /// If this method has no category, it returns an empty string. + /// + /// Example: + /// Full name: "+[NSString(my_additions) myStringWithCString:]" + /// becomes "+[NSString myStringWithCString:]" + /// + /// \return The method name without the category or an empty string if there + /// was no category to begin with. + std::string GetFullNameWithoutCategory() const; + + /// Returns a reference to the class name. + /// + /// Example: + /// Full name: "+[NSString(my_additions) myStringWithCString:]" + /// will give you "NSString" + /// + /// \return A StringRef to the class name of this method. + llvm::StringRef GetClassName() const; + + /// Returns a reference to the class name with the category. + /// + /// Example: + /// Full name: "+[NSString(my_additions) myStringWithCString:]" + /// will give you "NSString(my_additions)" + /// + /// Note: If your method has no category, this will give the same output as + /// `GetClassName`. + /// + /// \return A StringRef to the class name (including the category) of this + /// method. If there was no category, returns the same as `GetClassName`. + llvm::StringRef GetClassNameWithCategory() const; + + /// Returns a reference to the category name. + /// + /// Example: + /// Full name: "+[NSString(my_additions) myStringWithCString:]" + /// will give you "my_additions" + /// \return A StringRef to the category name of this method. If no category + /// is present, the StringRef is empty. + llvm::StringRef GetCategory() const; + + /// Returns a reference to the selector name. + /// + /// Example: + /// Full name: "+[NSString(my_additions) myStringWithCString:]" + /// will give you "myStringWithCString:" + /// \return A StringRef to the selector of this method. + llvm::StringRef GetSelector() const; - ConstString GetFullNameWithoutCategory(bool empty_if_no_category); - - bool SetName(const char *name, bool strict); - bool SetName(llvm::StringRef name, bool strict); - - ConstString GetClassName(); - - ConstString GetClassNameWithCategory(); - - ConstString GetCategory(); + protected: + enum Type { eTypeUnspecified, eTypeClassMethod, eTypeInstanceMethod }; - ConstString GetSelector(); + MethodName(llvm::StringRef name, Type type) + : m_full(name.str()), m_type(type) {} - protected: - ConstString - m_full; // Full name: "+[NSString(my_additions) myStringWithCString:]" - ConstString m_class; // Class name: "NSString" - ConstString - m_class_category; // Class with category: "NSString(my_additions)" - ConstString m_category; // Category: "my_additions" - ConstString m_selector; // Selector: "myStringWithCString:" - Type m_type = eTypeUnspecified; - bool m_category_is_valid = false; + const std::string m_full; + Type m_type; }; ObjCLanguage() = default; diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp --- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp +++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp @@ -60,201 +60,152 @@ } } -void ObjCLanguage::MethodName::Clear() { - m_full.Clear(); - m_class.Clear(); - m_category.Clear(); - m_selector.Clear(); - m_type = eTypeUnspecified; - m_category_is_valid = false; -} - -bool ObjCLanguage::MethodName::SetName(llvm::StringRef name, bool strict) { - Clear(); +std::optional +ObjCLanguage::MethodName::Create(llvm::StringRef name, bool strict) { if (name.empty()) - return IsValid(strict); - - // 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.size() > 1 && (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 = name.size(); - // 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.back() == ']') { - m_full.SetString(name); - } - } - return IsValid(strict); + return std::nullopt; + + // Objective-C method minimum requirements: + // - If `strict` is true, must start with '-' or '+' (1 char) + // - Must be followed by '[' (1 char) + // - Must have at least one character for class name (1 char) + // - Must have a space between class name and method name (1 char) + // - Must have at least one character for method name (1 char) + // - Must be end with ']' (1 char) + // This means that the minimum size is 5 characters (6 if `strict`) + // e.g. [a a] (-[a a] or +[a a] if `strict`) + + // We can check length and ending invariants first + if (name.size() < (5 + (strict ? 1 : 0)) || name.back() != ']') + return std::nullopt; + + // Figure out type + Type type = eTypeUnspecified; + if (name.startswith("+[")) + type = eTypeClassMethod; + else if (name.startswith("-[")) + type = eTypeInstanceMethod; + + // If there's no type and it's strict, this is invalid + if (strict && type == eTypeUnspecified) + return std::nullopt; + + // If not strict and type unspecified, make sure we start with '[' + if (type == eTypeUnspecified && name.front() != '[') + return std::nullopt; + + // If we've gotten here, we're confident that this looks enough like an + // Objective-C method to treat it like one. + ObjCLanguage::MethodName method_name(name, type); + return method_name; } -bool ObjCLanguage::MethodName::SetName(const char *name, bool strict) { - return SetName(llvm::StringRef(name), strict); +llvm::StringRef ObjCLanguage::MethodName::GetClassName() const { + llvm::StringRef full = m_full; + const size_t class_start_pos = (full.front() == '[' ? 1 : 2); + const size_t paren_pos = full.find('(', class_start_pos); + // If there's a category we want to stop there + if (paren_pos != llvm::StringRef::npos) + return full.substr(class_start_pos, paren_pos - class_start_pos); + + // Otherwise we find the space separating the class and method + const size_t space_pos = full.find(' ', class_start_pos); + return full.substr(class_start_pos, space_pos - class_start_pos); } -ConstString ObjCLanguage::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; +llvm::StringRef ObjCLanguage::MethodName::GetClassNameWithCategory() const { + llvm::StringRef full = m_full; + const size_t class_start_pos = (full.front() == '[' ? 1 : 2); + const size_t space_pos = full.find(' ', class_start_pos); + return full.substr(class_start_pos, space_pos - class_start_pos); } -ConstString ObjCLanguage::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(), '(') == nullptr) { - 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; +llvm::StringRef ObjCLanguage::MethodName::GetSelector() const { + llvm::StringRef full = m_full; + const size_t space_pos = full.find(' '); + if (space_pos == llvm::StringRef::npos) + return llvm::StringRef(); + const size_t closing_bracket = full.find(']', space_pos); + return full.substr(space_pos + 1, closing_bracket - space_pos - 1); } -ConstString ObjCLanguage::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; -} +llvm::StringRef ObjCLanguage::MethodName::GetCategory() const { + llvm::StringRef full = m_full; + const size_t open_paren_pos = full.find('('); + const size_t close_paren_pos = full.find(')'); -ConstString ObjCLanguage::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; -} + if (open_paren_pos == llvm::StringRef::npos || + close_paren_pos == llvm::StringRef::npos) + return llvm::StringRef(); -ConstString ObjCLanguage::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()); - } + return full.substr(open_paren_pos + 1, + close_paren_pos - (open_paren_pos + 1)); +} - if (!empty_if_no_category) { - // Just return the full name since it doesn't have a category - return GetFullName(); - } - } - return ConstString(); +std::string ObjCLanguage::MethodName::GetFullNameWithoutCategory() const { + llvm::StringRef full = m_full; + const size_t open_paren_pos = full.find('('); + const size_t close_paren_pos = full.find(')'); + if (open_paren_pos == llvm::StringRef::npos || + close_paren_pos == llvm::StringRef::npos) + return std::string(); + + llvm::StringRef class_name = GetClassName(); + llvm::StringRef selector_name = GetSelector(); + std::string name_sans_category; + + if (m_type == eTypeClassMethod) + name_sans_category += '+'; + else if (m_type == eTypeInstanceMethod) + name_sans_category += '-'; + + name_sans_category += '['; + name_sans_category.append(class_name.data(), class_name.size()); + name_sans_category += ' '; + name_sans_category.append(selector_name.data(), selector_name.size()); + name_sans_category += ']'; + + return name_sans_category; } std::vector ObjCLanguage::GetMethodNameVariants(ConstString method_name) const { std::vector variant_names; - ObjCLanguage::MethodName objc_method(method_name.GetCString(), false); - if (!objc_method.IsValid(false)) { + std::optional objc_method = + ObjCLanguage::MethodName::Create(method_name.GetStringRef(), false); + if (!objc_method) return variant_names; - } - variant_names.emplace_back(objc_method.GetSelector(), + variant_names.emplace_back(ConstString(objc_method->GetSelector()), lldb::eFunctionNameTypeSelector); - const bool is_class_method = - objc_method.GetType() == MethodName::eTypeClassMethod; - const bool is_instance_method = - objc_method.GetType() == MethodName::eTypeInstanceMethod; - ConstString name_sans_category = - objc_method.GetFullNameWithoutCategory(/*empty_if_no_category*/ true); + const std::string name_sans_category = + objc_method->GetFullNameWithoutCategory(); - if (is_class_method || is_instance_method) { - if (name_sans_category) - variant_names.emplace_back(name_sans_category, + if (objc_method->IsClassMethod() || objc_method->IsInstanceMethod()) { + if (!name_sans_category.empty()) + variant_names.emplace_back(ConstString(name_sans_category.c_str()), lldb::eFunctionNameTypeFull); } else { StreamString strm; - strm.Printf("+%s", objc_method.GetFullName().GetCString()); + strm.Printf("+%s", objc_method->GetFullName().c_str()); variant_names.emplace_back(ConstString(strm.GetString()), lldb::eFunctionNameTypeFull); strm.Clear(); - strm.Printf("-%s", objc_method.GetFullName().GetCString()); + strm.Printf("-%s", objc_method->GetFullName().c_str()); variant_names.emplace_back(ConstString(strm.GetString()), lldb::eFunctionNameTypeFull); strm.Clear(); - if (name_sans_category) { - strm.Printf("+%s", name_sans_category.GetCString()); + if (!name_sans_category.empty()) { + strm.Printf("+%s", name_sans_category.c_str()); variant_names.emplace_back(ConstString(strm.GetString()), lldb::eFunctionNameTypeFull); strm.Clear(); - strm.Printf("-%s", name_sans_category.GetCString()); + strm.Printf("-%s", name_sans_category.c_str()); variant_names.emplace_back(ConstString(strm.GetString()), lldb::eFunctionNameTypeFull); } @@ -1020,7 +971,7 @@ friend class lldb_private::ObjCLanguage; }; - + class ObjCDebugInfoScavenger : public Language::ImageListTypeScavenger { public: CompilerType AdjustForInclusion(CompilerType &candidate) override { diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -980,10 +980,11 @@ if (attrs.name) { bool type_handled = false; if (tag == DW_TAG_subprogram || tag == DW_TAG_inlined_subroutine) { - ObjCLanguage::MethodName objc_method(attrs.name.GetStringRef(), true); - if (objc_method.IsValid(true)) { + std::optional objc_method = + ObjCLanguage::MethodName::Create(attrs.name.GetStringRef(), true); + if (objc_method) { CompilerType class_opaque_type; - ConstString class_name(objc_method.GetClassName()); + ConstString class_name(objc_method->GetClassName()); if (class_name) { TypeSP complete_objc_class_type_sp( dwarf->FindCompleteObjCDefinitionTypeForDIE(DWARFDIE(), @@ -2617,13 +2618,19 @@ // Check if the property getter/setter were provided as full names. // We want basenames, so we extract them. if (prop_getter_name && prop_getter_name[0] == '-') { - ObjCLanguage::MethodName prop_getter_method(prop_getter_name, true); - prop_getter_name = prop_getter_method.GetSelector().GetCString(); + std::optional prop_getter_method = + ObjCLanguage::MethodName::Create(prop_getter_name, true); + if (prop_getter_method) + prop_getter_name = + ConstString(prop_getter_method->GetSelector()).GetCString(); } if (prop_setter_name && prop_setter_name[0] == '-') { - ObjCLanguage::MethodName prop_setter_method(prop_setter_name, true); - prop_setter_name = prop_setter_method.GetSelector().GetCString(); + std::optional prop_setter_method = + ObjCLanguage::MethodName::Create(prop_setter_name, true); + if (prop_setter_method) + prop_setter_name = + ConstString(prop_setter_method->GetSelector()).GetCString(); } // If the names haven't been provided, they need to be filled in. diff --git a/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp @@ -287,15 +287,16 @@ bool is_objc_method = false; if (cu_language == eLanguageTypeObjC || cu_language == eLanguageTypeObjC_plus_plus) { - ObjCLanguage::MethodName objc_method(name, true); - if (objc_method.IsValid(true)) { + std::optional objc_method = + ObjCLanguage::MethodName::Create(name, true); + if (objc_method) { is_objc_method = true; ConstString class_name_with_category( - objc_method.GetClassNameWithCategory()); - ConstString objc_selector_name(objc_method.GetSelector()); + objc_method->GetClassNameWithCategory()); + ConstString objc_selector_name(objc_method->GetSelector()); ConstString objc_fullname_no_category_name( - objc_method.GetFullNameWithoutCategory(true)); - ConstString class_name_no_category(objc_method.GetClassName()); + objc_method->GetFullNameWithoutCategory().c_str()); + ConstString class_name_no_category(objc_method->GetClassName()); set.function_fullnames.Insert(ConstString(name), ref); if (class_name_with_category) set.objc_class_selectors.Insert(class_name_with_category, ref); diff --git a/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp b/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp --- a/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp +++ b/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp @@ -42,53 +42,51 @@ // First, be strict for (const auto &test : strict_cases) { - ObjCLanguage::MethodName method(test.input, /*strict = */ true); - EXPECT_TRUE(method.IsValid(/*strict = */ true)); - EXPECT_EQ( - test.full_name_sans_category, - method.GetFullNameWithoutCategory(/*empty_if_no_category = */ true) - .GetStringRef()); - EXPECT_EQ(test.class_name, method.GetClassName().GetStringRef()); + std::optional method = + ObjCLanguage::MethodName::Create(test.input, /*strict = */ true); + EXPECT_TRUE(method.has_value()); + EXPECT_EQ(test.full_name_sans_category, + method->GetFullNameWithoutCategory()); + EXPECT_EQ(test.class_name, method->GetClassName()); EXPECT_EQ(test.class_name_with_category, - method.GetClassNameWithCategory().GetStringRef()); - EXPECT_EQ(test.category, method.GetCategory().GetStringRef()); - EXPECT_EQ(test.selector, method.GetSelector().GetStringRef()); + method->GetClassNameWithCategory()); + EXPECT_EQ(test.category, method->GetCategory()); + EXPECT_EQ(test.selector, method->GetSelector()); } // We should make sure strict parsing does not accept lax cases for (const auto &test : lax_cases) { - ObjCLanguage::MethodName method(test.input, /*strict = */ true); - EXPECT_FALSE(method.IsValid(/*strict = */ true)); + std::optional method = + ObjCLanguage::MethodName::Create(test.input, /*strict = */ true); + EXPECT_FALSE(method.has_value()); } // All strict cases should work when not lax for (const auto &test : strict_cases) { - ObjCLanguage::MethodName method(test.input, /*strict = */ false); - EXPECT_TRUE(method.IsValid(/*strict = */ false)); - EXPECT_EQ( - test.full_name_sans_category, - method.GetFullNameWithoutCategory(/*empty_if_no_category = */ true) - .GetStringRef()); - EXPECT_EQ(test.class_name, method.GetClassName().GetStringRef()); + std::optional method = + ObjCLanguage::MethodName::Create(test.input, /*strict = */ false); + EXPECT_TRUE(method.has_value()); + EXPECT_EQ(test.full_name_sans_category, + method->GetFullNameWithoutCategory()); + EXPECT_EQ(test.class_name, method->GetClassName()); EXPECT_EQ(test.class_name_with_category, - method.GetClassNameWithCategory().GetStringRef()); - EXPECT_EQ(test.category, method.GetCategory().GetStringRef()); - EXPECT_EQ(test.selector, method.GetSelector().GetStringRef()); + method->GetClassNameWithCategory()); + EXPECT_EQ(test.category, method->GetCategory()); + EXPECT_EQ(test.selector, method->GetSelector()); } // Make sure non-strict parsing works for (const auto &test : lax_cases) { - ObjCLanguage::MethodName method(test.input, /*strict = */ false); - EXPECT_TRUE(method.IsValid(/*strict = */ false)); - EXPECT_EQ( - test.full_name_sans_category, - method.GetFullNameWithoutCategory(/*empty_if_no_category = */ true) - .GetStringRef()); - EXPECT_EQ(test.class_name, method.GetClassName().GetStringRef()); + std::optional method = + ObjCLanguage::MethodName::Create(test.input, /*strict = */ false); + EXPECT_TRUE(method.has_value()); + EXPECT_EQ(test.full_name_sans_category, + method->GetFullNameWithoutCategory()); + EXPECT_EQ(test.class_name, method->GetClassName()); EXPECT_EQ(test.class_name_with_category, - method.GetClassNameWithCategory().GetStringRef()); - EXPECT_EQ(test.category, method.GetCategory().GetStringRef()); - EXPECT_EQ(test.selector, method.GetSelector().GetStringRef()); + method->GetClassNameWithCategory()); + EXPECT_EQ(test.category, method->GetCategory()); + EXPECT_EQ(test.selector, method->GetSelector()); } } @@ -105,10 +103,12 @@ "[]"}; for (const auto &name : test_cases) { - ObjCLanguage::MethodName strict_method(name, /*strict = */ true); - EXPECT_FALSE(strict_method.IsValid(true)); + std::optional strict_method = + ObjCLanguage::MethodName::Create(name, /*strict = */ false); + EXPECT_FALSE(strict_method.has_value()); - ObjCLanguage::MethodName lax_method(name, /*strict = */ false); - EXPECT_FALSE(lax_method.IsValid(true)); + std::optional lax_method = + ObjCLanguage::MethodName::Create(name, /*strict = */ false); + EXPECT_FALSE(lax_method.has_value()); } }