diff --git a/NativeScript/runtime/Metadata.mm b/NativeScript/runtime/Metadata.mm index 69442877..7f75c459 100644 --- a/NativeScript/runtime/Metadata.mm +++ b/NativeScript/runtime/Metadata.mm @@ -1,304 +1,331 @@ #include "Metadata.h" #include #include -#include "SymbolLoader.h" #include "Helpers.h" +#include "SymbolLoader.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 - ); +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); } -#if !TARGET_OS_VISION /** - * \brief Gets the system version of the current device. + * \brief Gets the system version of the current device, expressed on the iOS + * version scale used by metadata introducedIn values. */ 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]; + 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]; + +#if TARGET_OS_VISION + // Metadata availability is expressed in iOS versions. visionOS majors + // before the unified 26 numbering trail iOS by 16 (visionOS 1.x ~ iOS + // 17.x, visionOS 2.x ~ iOS 18.x); from 26 onward the numbering aligns. + if (majorVersion < 26) { + majorVersion += 16; + } +#endif - return iosVersion = encodeVersion(majorVersion, minorVersion); + return iosVersion = encodeVersion(majorVersion, minorVersion); } -#endif -robin_hood::unordered_map getMetasByJSNames(MembersCollection members) { - robin_hood::unordered_map result; - for (auto member : members) { - result[member->jsName()].insert(member); - } - return result; +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 { - #if TARGET_OS_VISION - return true; - #else - UInt8 introducedIn = this->introducedIn(); - UInt8 systemVersion = getSystemVersion(); - return introducedIn == 0 || introducedIn <= systemVersion; - #endif + 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; + // 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; } - // 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()]; + // 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); + std::set protocols; + this->forEachProtocol( + [&protocols](const ProtocolMeta* protocolMeta) { protocols.insert(protocolMeta); }, + /*additionalProtocols*/ nullptr); - return protocols; + return protocols; } std::set BaseClassMeta::deepProtocolsSet() const { - std::set protocols; - this->deepProtocolsSet(protocols); - return protocols; + 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); - } + if (this->type() == Interface) { + auto interfaceMeta = static_cast(this); + if (auto baseMeta = interfaceMeta->baseMeta()) { + baseMeta->deepProtocolsSet(protocols); } + } - this->forEachProtocol([&protocols](const ProtocolMeta* protocolMeta) { + this->forEachProtocol( + [&protocols](const ProtocolMeta* protocolMeta) { protocolMeta->deepProtocolsSet(protocols); protocols.insert(protocolMeta); - }, - /*additionalProtocols*/ nullptr); + }, + /*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); - 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); - // 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; + 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) { +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; + members = &meta->instanceMethods->castTo>(); + break; case MemberType::StaticMethod: - members = &meta->staticMethods->castTo>(); - break; + members = &meta->staticMethods->castTo>(); + break; case MemberType::InstanceProperty: - shouldScanForOverrides = false; - members = &meta->instanceProps->castTo>(); - break; + shouldScanForOverrides = false; + members = &meta->instanceProps->castTo>(); + break; case MemberType::StaticProperty: - shouldScanForOverrides = false; - members = &meta->staticProps->castTo>(); - break; + 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); + } } - 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); - } - } + 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); - } + } + + // 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 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); + 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); } - if (result.size() > 0) { - return result; - } + } else { // member is a property + collectInheritanceChainMembers(identifier, length, type, onlyIfAvailable, this, + [&](const MemberMeta* member) { result.insert(member); }); + } - // 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()); + 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); - } + &additionalProtocols); + } - return result; + 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) { +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; + }, + &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) { +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; + }, + &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); - } - } +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; + } + 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; +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::instance() { return metaFileInstance; } MetaFile* MetaFile::setInstance(void* metadataPtr) { - metaFileInstance = reinterpret_cast(metadataPtr); - return metaFileInstance; -} + metaFileInstance = reinterpret_cast(metadataPtr); + return metaFileInstance; } +} // namespace tns diff --git a/TestFixtures/Api/TNSVersions.h b/TestFixtures/Api/TNSVersions.h index fba2328d..e66af3fa 100644 --- a/TestFixtures/Api/TNSVersions.h +++ b/TestFixtures/Api/TNSVersions.h @@ -1,5 +1,6 @@ #define generateVersionDeclarations(V1, V2) \ - __attribute__((availability(ios, introduced = V1))) @interface TNSInterface \ + __attribute__((availability(ios, introduced = V1))) \ + @interface TNSInterface \ ##V2##Plus : NSObject @end \ \ @interface TNSInterfaceMembers \ @@ -58,6 +59,7 @@ generateMinors(15); #define MAX_AVAILABILITY 31.7 __attribute__((availability(ios, introduced = MAX_AVAILABILITY))) +__attribute__((availability(visionos, introduced = MAX_AVAILABILITY))) @protocol TNSProtocolNeverAvailable @property(class, readonly) int staticPropertyFromProtocolNeverAvailable; @@ -74,7 +76,8 @@ __attribute__((availability(ios, introduced = MAX_AVAILABILITY))) @end -__attribute__((availability(ios, introduced = 1.0))) @protocol TNSProtocolAlwaysAvailable +__attribute__((availability(ios, introduced = 1.0))) +@protocol TNSProtocolAlwaysAvailable @property(class, readonly) int staticPropertyFromProtocolAlwaysAvailable; @@ -91,6 +94,7 @@ __attribute__((availability(ios, introduced = 1.0))) @protocol TNSProtocolAlways @end __attribute__((availability(ios, introduced = MAX_AVAILABILITY))) +__attribute__((availability(visionos, introduced = MAX_AVAILABILITY))) @interface TNSInterfaceNeverAvailable : TNSInterfaceAlwaysAvailable @end diff --git a/v8ios.xcodeproj/project.pbxproj b/v8ios.xcodeproj/project.pbxproj index 19ca5ef8..4046a032 100644 --- a/v8ios.xcodeproj/project.pbxproj +++ b/v8ios.xcodeproj/project.pbxproj @@ -2339,7 +2339,10 @@ }; C293752B229FC4740075CB16 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; + platformFilters = ( + ios, + xros, + ); target = C29374E1229FC0F60075CB16 /* TestFixtures */; targetProxy = C293752A229FC4740075CB16 /* PBXContainerItemProxy */; }; @@ -2433,6 +2436,7 @@ C23992CE236C2D6E00D2F720 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_MODULES = NO; CODE_SIGN_STYLE = Automatic; @@ -2459,6 +2463,7 @@ C23992CF236C2D6E00D2F720 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_MODULES = NO; CODE_SIGN_STYLE = Automatic; @@ -2682,6 +2687,7 @@ C29374E9229FC0F60075CB16 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; CLANG_ENABLE_MODULES = NO; CLANG_WARN_STRICT_PROTOTYPES = NO; DEFINES_MODULE = YES; @@ -2699,6 +2705,7 @@ C29374EA229FC0F60075CB16 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; CLANG_ENABLE_MODULES = NO; CLANG_WARN_STRICT_PROTOTYPES = NO; DEFINES_MODULE = YES;