#include "Metadata.h" #include #include #include "SymbolLoader.h" #include "Helpers.h" namespace tns { using namespace std; void LogMetadataUnavailable(const char* identifierString, uint8_t majorVersion, uint8_t minorVersion, const char* baseName) { Log(@"** \"%s\" introduced in iOS SDK %d.%d is currently unavailable, attempting to load its base: \"%s\". **", identifierString, majorVersion, minorVersion, baseName ); } /** * \brief Gets the system version of the current device. */ static UInt8 getSystemVersion() { static UInt8 iosVersion; if (iosVersion != 0) { return iosVersion; } NSString* version = [[UIDevice currentDevice] systemVersion]; NSArray* versionTokens = [version componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]; UInt8 majorVersion = (UInt8)[versionTokens[0] intValue]; UInt8 minorVersion = (UInt8)[versionTokens[1] intValue]; return iosVersion = encodeVersion(majorVersion, minorVersion); } robin_hood::unordered_map getMetasByJSNames(MembersCollection members) { robin_hood::unordered_map result; for (auto member : members) { result[member->jsName()].insert(member); } return result; } // Meta bool Meta::isAvailable() const { UInt8 introducedIn = this->introducedIn(); UInt8 systemVersion = getSystemVersion(); return introducedIn == 0 || introducedIn <= systemVersion; } // MethodMeta class bool MethodMeta::isImplementedInClass(Class klass, bool isStatic) const { // class can be null for Protocol prototypes, treat all members in a protocol as implemented if (klass == nullptr) { return true; } // Some members are implemented by extension of classes defined in a different // module than the class, ensure they've been initialized SymbolLoader::instance().ensureModule(this->topLevelModule()); if (isStatic) { return [klass respondsToSelector:this->selector()] || ([klass resolveClassMethod:this->selector()]); } else { if ([klass instancesRespondToSelector:this->selector()] || [klass resolveInstanceMethod:this->selector()]) { return true; } // Last resort - allocate an object and ask it if it supports the selector. // There are two kinds of scenarios that need this additional check: // 1. The `alloc` method is overridden and returns an instance of another class // E.g. [NSAttributedString alloc] returns a NSConcreteAttributedString* // 2. The message is forwarded to another object. E.g. `UITextField` forwards // `autocapitalizationType` to an instance of `UITextInputTraits` static robin_hood::unordered_map sampleInstances; auto it = sampleInstances.find(klass); if (it == sampleInstances.end()) { @try { it = sampleInstances.emplace(klass, [klass alloc]).first; } @catch(id err) { return false; } } id sampleInstance = it->second; return [sampleInstance respondsToSelector:this->selector()]; } } // BaseClassMeta std::set BaseClassMeta::protocolsSet() const { std::set protocols; this->forEachProtocol([&protocols](const ProtocolMeta* protocolMeta) { protocols.insert(protocolMeta); }, /*additionalProtocols*/ nullptr); return protocols; } std::set BaseClassMeta::deepProtocolsSet() const { std::set protocols; this->deepProtocolsSet(protocols); return protocols; } void BaseClassMeta::deepProtocolsSet(std::set& protocols) const { if (this->type() == Interface) { auto interfaceMeta = static_cast(this); if (auto baseMeta = interfaceMeta->baseMeta()) { baseMeta->deepProtocolsSet(protocols); } } this->forEachProtocol([&protocols](const ProtocolMeta* protocolMeta) { protocolMeta->deepProtocolsSet(protocols); protocols.insert(protocolMeta); }, /*additionalProtocols*/ nullptr); } const MemberMeta* BaseClassMeta::member(const char* identifier, size_t length, MemberType type, bool includeProtocols, bool onlyIfAvailable, const ProtocolMetas& additionalProtocols) const { MembersCollection members = this->members(identifier, length, type, includeProtocols, onlyIfAvailable, additionalProtocols); // It's expected to receive only one occurence when member is used. If more than one results can // be found consider (1) using BaseClassMeta::members to process all of them; or (2) fixing metadata // generator to disambiguate and remove the redundant one(s); or (3) modify this method so that it doesn't arbitrary // choose one and drop the other(s) but deterministically decides which one has to be returned. ASSERT(members.size() <= 1); return members.size() > 0 ? *members.begin() : nullptr; } void collectInheritanceChainMembers(const char* identifier, size_t length, MemberType type, bool onlyIfAvailable, const BaseClassMeta* meta, std::function collectMember) { const ArrayOfPtrTo* members = nullptr; // Scan method overloads (methods with different selectors and number of arguments which have the same jsName) // in base classes. Properties cannot be overridden like that so there's no need to traverse the hierarchy. bool shouldScanForOverrides = true; switch (type) { case MemberType::InstanceMethod: members = &meta->instanceMethods->castTo>(); break; case MemberType::StaticMethod: members = &meta->staticMethods->castTo>(); break; case MemberType::InstanceProperty: shouldScanForOverrides = false; members = &meta->instanceProps->castTo>(); break; case MemberType::StaticProperty: shouldScanForOverrides = false; members = &meta->staticProps->castTo>(); break; } int resultIndex = -1; resultIndex = members->binarySearchLeftmost([&](const PtrTo& member) { return compareIdentifiers(member->jsName(), identifier, length); }); if (resultIndex >= 0) { for (const MemberMeta* m = (*members)[resultIndex].valuePtr(); resultIndex < members->count && (strncmp(m->jsName(), identifier, length) == 0 && strlen(m->jsName()) == length); m = (*members)[++resultIndex].valuePtr()) { if (m->isAvailable() || !onlyIfAvailable) { collectMember(m); } } if (shouldScanForOverrides && meta->type() == MetaType::Interface) { const BaseClassMeta* superClass = static_cast(meta)->baseMeta(); if (superClass) { collectInheritanceChainMembers(identifier, length, type, onlyIfAvailable, superClass, collectMember); } } } // Instance members of NSObject can be called as static as well (e.g. [NSString performSelector:@selector(alloc)] static const char* nsObject = "NSObject"; static const size_t nsObjectLen = strlen(nsObject); if (strncmp(meta->name(), nsObject, nsObjectLen + 1) == 0) { if (type == MemberType::StaticMethod) { collectInheritanceChainMembers(identifier, length, MemberType::InstanceMethod, onlyIfAvailable, meta, collectMember); } else if (type == MemberType::StaticProperty) { collectInheritanceChainMembers(identifier, length, MemberType::InstanceProperty, onlyIfAvailable, meta, collectMember); } } } const MembersCollection BaseClassMeta::members(const char* identifier, size_t length, MemberType type, bool includeProtocols, bool onlyIfAvailable, const ProtocolMetas& additionalProtocols) const { MembersCollection result; if (type == MemberType::InstanceMethod || type == MemberType::StaticMethod) { // We need to return base class members as well. Otherwise, // if an overloaded method is overriden by a derived class // the FunctionWrapper's *functionsContainer* will contain // overriden members metas only. std::map membersMap; collectInheritanceChainMembers(identifier, length, type, onlyIfAvailable, this, [&](const MemberMeta* member) { const MethodMeta* method = static_cast(member); ArrayCount count = method->encodings()->count; membersMap.emplace(count, member); }); for (std::map::iterator it = membersMap.begin(); it != membersMap.end(); ++it) { result.insert(it->second); } } else { // member is a property collectInheritanceChainMembers(identifier, length, type, onlyIfAvailable, this, [&](const MemberMeta* member) { result.insert(member); }); } if (result.size() > 0) { return result; } // search in protocols if (includeProtocols) { this->forEachProtocol([&result, identifier, length, type, includeProtocols, onlyIfAvailable](const ProtocolMeta* protocolMeta) { const MembersCollection members = protocolMeta->members(identifier, length, type, includeProtocols, onlyIfAvailable, ProtocolMetas()); result.insert(members.begin(), members.end()); }, &additionalProtocols); } return result; } std::vector BaseClassMeta::instancePropertiesWithProtocols(std::vector& container, KnownUnknownClassPair klasses, const ProtocolMetas& additionalProtocols) const { this->instanceProperties(container, klasses); this->forEachProtocol([&container, klasses](const ProtocolMeta* protocolMeta) { protocolMeta->instancePropertiesWithProtocols(container, klasses, ProtocolMetas()); }, &additionalProtocols); return container; } std::vector BaseClassMeta::staticPropertiesWithProtocols(std::vector& container, KnownUnknownClassPair klasses, const ProtocolMetas& additionalProtocols) const { this->staticProperties(container, klasses); this->forEachProtocol([&container, klasses](const ProtocolMeta* protocolMeta) { protocolMeta->staticPropertiesWithProtocols(container, klasses, ProtocolMetas()); }, &additionalProtocols); return container; } vector BaseClassMeta::initializers(vector& container, KnownUnknownClassPair klasses) const { // search in instance methods int16_t firstInitIndex = this->initializersStartIndex; if (firstInitIndex != -1) { for (int i = firstInitIndex; i < instanceMethods->count; i++) { const MethodMeta* method = instanceMethods.value()[i].valuePtr(); if (!method->isInitializer()) { break; } if (method->isAvailableInClasses(klasses, /*isStatic*/ false)) { container.push_back(method); } } } return container; } vector BaseClassMeta::initializersWithProtocols(vector& container, KnownUnknownClassPair klasses, const ProtocolMetas& additionalProtocols) const { this->initializers(container, klasses); for (Array::iterator it = this->protocols->begin(); it != this->protocols->end(); it++) { const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTableJs()->findProtocol((*it).valuePtr()); if (protocolMeta != nullptr) protocolMeta->initializersWithProtocols(container, klasses, ProtocolMetas()); } for (const ProtocolMeta* protocolMeta : additionalProtocols) { protocolMeta->initializersWithProtocols(container, klasses, ProtocolMetas()); } return container; } static MetaFile* metaFileInstance(nullptr); MetaFile* MetaFile::instance() { return metaFileInstance; } MetaFile* MetaFile::setInstance(void* metadataPtr) { metaFileInstance = reinterpret_cast(metadataPtr); return metaFileInstance; } }