diff --git a/objectivec/objc.cpp b/objectivec/objc.cpp index 9bdd61c3c..6ca18e622 100644 --- a/objectivec/objc.cpp +++ b/objectivec/objc.cpp @@ -3,6 +3,41 @@ using namespace BinaryNinja; +namespace { + +// Given a selector component such as `initWithPath' and a prefix of `initWith`, returns `path`. +std::optional SelectorComponentWithoutPrefix(std::string_view prefix, std::string_view component) +{ + if (component.size() <= prefix.size() || component.rfind(prefix.data(), 0) != 0 + || !isupper(component[prefix.size()])) + { + return std::nullopt; + } + + std::string result(component.substr(prefix.size())); + + // Lowercase the first character if the second character is not also uppercase. + // This ensures we leave initialisms such as `URL` alone. + if (result.size() > 1 && islower(result[1])) + result[0] = tolower(result[0]); + + return result; +} + +std::string ArgumentNameFromSelectorComponent(std::string component) +{ + // TODO: Handle other common patterns such as With: and For: + for (const auto& prefix : {"initWith", "with", "and", "using", "set", "read", "to", "for"}) + { + if (auto argumentName = SelectorComponentWithoutPrefix(prefix, component); argumentName.has_value()) + return std::move(*argumentName); + } + + return component; +} + +} // namespace + Ref ObjCProcessor::SerializeMethod(uint64_t loc, const Method& method) { std::map> methodMeta; @@ -1099,10 +1134,13 @@ bool ObjCProcessor::ApplyMethodType(Class& cls, Method& method, bool isInstanceM for (size_t i = 3; i < typeTokens.size(); i++) { - std::string suffix; + std::string name; + if (selectorTokens.size() > i - 3) + name = ArgumentNameFromSelectorComponent(selectorTokens[i - 3]); + else + name = "arg"; - params.push_back({selectorTokens.size() > i - 3 ? selectorTokens[i - 3] : "arg", - typeForQualifiedNameOrType(typeTokens[i]), true, BinaryNinja::Variable()}); + params.push_back({std::move(name), typeForQualifiedNameOrType(typeTokens[i]), true, BinaryNinja::Variable()}); } auto funcType = BinaryNinja::Type::FunctionType(retType, cc, params);