From 850e5e45794db79c5d3a35fe0af7c8542fc8d528 Mon Sep 17 00:00:00 2001 From: Martin Bektchiev Date: Thu, 28 Feb 2019 11:24:52 +0200 Subject: [PATCH 1/7] fix(runtime): Filter members which are unavailable in the current SDK * Add `isImplementedInClass` and `isAvailableInClass` methods in `PropertyMeta` and `MethodMeta` which additionally check whether a Class instance supports a given optional method * Use `isAvailableInClass` instead of `isAvailable` in `getOwnPropertyNames` of `ObjCPrototype`, `ObjCConstructorBase`, `ObjCConstructorNative` and in `materializeProperties` of `ObjCConstructorNative` * Filter via `isAvailableInClass` in `BaseClassMeta`'s various method and property getters * Change `MembersCollection` to be a `HashSet` to avoid adding the same member more than once when collecting them from the inheritance chain * Remove unused functions from `BaseClassMeta` * Add `findInterfaceMeta` function with fallback to the base interface if the desired one is unavailable in the current SDK version (instead of the hardcoded `NSObject` so far) * Remove hacky patch from `ObjCMethodWrapper::preInvocation` which fixed the symptoms of #978 but not its root cause. Namely, that unimplemented optional methods were returned as available properties in `getOwnPropertyNames`. * Modify fixtures and add test cases for methods availability in `InheritanceTests.js` * Add test case for unavailable base class * Add test case for property with custom selector in ApiTests. The getter of `secureTextEntry`(`isSecureText`) overrides the default `secureTextEntry` * Add `encodeVersion` `getMajorVersion` and `getMinorVersion` helpers refs #1085 --- src/NativeScript/GlobalObject.mm | 2 +- src/NativeScript/Metadata/Metadata.h | 150 ++++++++++++------ src/NativeScript/Metadata/Metadata.mm | 96 ++++++----- .../ObjC/Constructor/ObjCConstructorBase.mm | 12 +- .../ObjC/Constructor/ObjCConstructorNative.mm | 55 +++---- .../ObjC/Inheritance/ObjCClassBuilder.h | 2 + .../ObjC/Inheritance/ObjCClassBuilder.mm | 32 ++-- src/NativeScript/ObjC/ObjCMethodCall.h | 12 +- src/NativeScript/ObjC/ObjCMethodCall.mm | 11 +- src/NativeScript/ObjC/ObjCMethodCallback.mm | 2 +- src/NativeScript/ObjC/ObjCProtocolWrapper.mm | 4 +- src/NativeScript/ObjC/ObjCPrototype.h | 17 +- src/NativeScript/ObjC/ObjCPrototype.mm | 73 ++++----- src/NativeScript/TypeFactory.mm | 4 +- tests/TestFixtures/Api/TNSVersions.h | 95 ++++++----- tests/TestFixtures/Api/TNSVersions.m | 9 ++ .../TestFixtures/Interfaces/TNSInheritance.h | 23 +++ .../TestFixtures/Interfaces/TNSInheritance.m | 38 ++++- tests/TestRunner/app/ApiTests.js | 26 +++ .../app/Inheritance/InheritanceTests.js | 72 +++++++-- tests/TestRunner/app/VersionDiffTests.js | 6 + 21 files changed, 488 insertions(+), 253 deletions(-) diff --git a/src/NativeScript/GlobalObject.mm b/src/NativeScript/GlobalObject.mm index 4ef251e27..4b1c593bd 100644 --- a/src/NativeScript/GlobalObject.mm +++ b/src/NativeScript/GlobalObject.mm @@ -89,7 +89,7 @@ static Strong createProtocolWrapper(GlobalObject* globalObject, const ProtocolMeta* protocolMeta, Protocol* aProtocol) { Structure* prototypeStructure = ObjCPrototype::createStructure(globalObject->vm(), globalObject, globalObject->objectPrototype()); - auto prototype = ObjCPrototype::create(globalObject->vm(), globalObject, prototypeStructure, protocolMeta); + auto prototype = ObjCPrototype::create(globalObject->vm(), globalObject, prototypeStructure, protocolMeta, /*klass*/ nullptr); Structure* protocolWrapperStructure = ObjCProtocolWrapper::createStructure(globalObject->vm(), globalObject, globalObject->objectPrototype()); auto protocolWrapper = ObjCProtocolWrapper::create(globalObject->vm(), protocolWrapperStructure, prototype.get(), protocolMeta, aProtocol); prototype->materializeProperties(globalObject->vm(), globalObject); diff --git a/src/NativeScript/Metadata/Metadata.h b/src/NativeScript/Metadata/Metadata.h index 3c10f9064..15507455b 100644 --- a/src/NativeScript/Metadata/Metadata.h +++ b/src/NativeScript/Metadata/Metadata.h @@ -46,6 +46,18 @@ static const V& getProperFunctionFromContainer(const std::vector& container, return *callee; } +inline UInt8 encodeVersion(UInt8 majorVersion, UInt8 minorVersion) { + return (majorVersion << 3) | minorVersion; +} + +inline UInt8 getMajorVersion(UInt8 encodedVersion) { + return encodedVersion >> 3; +} + +inline UInt8 getMinorVersion(UInt8 encodedVersion) { + return encodedVersion & 0b111; +} + // Bit indices in flags section enum MetaFlags { HasName = 7, @@ -126,6 +138,7 @@ enum BinaryTypeEncodingType : Byte { template struct PtrTo; struct Meta; +struct InterfaceMeta; struct ProtocolMeta; struct ModuleMeta; struct LibraryMeta; @@ -273,6 +286,12 @@ struct GlobalTable { ArrayOfPtrTo> buckets; + const InterfaceMeta* findInterfaceMeta(WTF::StringImpl* identifier) const; + + const InterfaceMeta* findInterfaceMeta(const char* identifierString) const; + + const InterfaceMeta* findInterfaceMeta(const char* identifierString, size_t length, unsigned hash) const; + const Meta* findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable = true) const; const Meta* findMeta(const char* identifierString, bool onlyIfAvailable = true) const; @@ -667,9 +686,24 @@ struct MethodMeta : MemberMeta { const char* constructorTokens() const { return this->_constructorTokens.valuePtr(); } + + bool 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; + } + + return nullptr != (isStatic ? class_getClassMethod(klass, this->selector()) : class_getInstanceMethod(klass, this->selector())); + } + + bool isAvailableInClass(Class klass, bool isStatic) const { + return this->isAvailable() && this->isImplementedInClass(klass, isStatic); + } }; -std::unordered_map> getMetasByJSNames(std::vector methods); +typedef HashSet MembersCollection; + +std::unordered_map getMetasByJSNames(MembersCollection methods); struct PropertyMeta : MemberMeta { PtrTo method1; @@ -691,6 +725,16 @@ struct PropertyMeta : MemberMeta { const MethodMeta* setter() const { return (this->hasSetter()) ? (this->hasGetter() ? method2.valuePtr() : method1.valuePtr()) : nullptr; } + + bool isImplementedInClass(Class klass, bool isStatic) const { + bool getterAvailable = this->hasGetter() && this->getter()->isImplementedInClass(klass, isStatic); + bool setterAvailable = this->hasSetter() && this->setter()->isImplementedInClass(klass, isStatic); + return getterAvailable || setterAvailable; + } + + bool isAvailableInClass(Class klass, bool isStatic) const { + return this->isAvailable() && this->isImplementedInClass(klass, isStatic); + } }; struct BaseClassMeta : Meta { @@ -706,7 +750,7 @@ struct BaseClassMeta : Meta { const MethodMeta* member(const char* identifier, size_t length, MemberType type, size_t paramsCount, bool includeProtocols = true, bool onlyIfAvailable = true) const; - const std::vector members(const char* identifier, size_t length, MemberType type, bool includeProtocols = true, bool onlyIfAvailable = true) const; + const MembersCollection members(const char* identifier, size_t length, MemberType type, bool includeProtocols = true, bool onlyIfAvailable = true) const; const MemberMeta* member(StringImpl* identifier, MemberType type, bool includeProtocols = true) const { const char* identif = reinterpret_cast(identifier->characters8()); @@ -720,7 +764,7 @@ struct BaseClassMeta : Meta { return this->member(identif, length, type, paramsCount, includeProtocols); } - const std::vector members(StringImpl* identifier, MemberType type, bool includeProtocols = true) const { + const MembersCollection members(StringImpl* identifier, MemberType type, bool includeProtocols = true) const { const char* identif = reinterpret_cast(identifier->characters8()); size_t length = (size_t)identifier->length(); return this->members(identif, length, type, includeProtocols); @@ -731,101 +775,110 @@ struct BaseClassMeta : Meta { } /// instance methods - const MethodMeta* instanceMethod(const char* identifier, size_t paramsCount, bool includeProtocols = true) const { - return this->member(identifier, strlen(identifier), MemberType::InstanceMethod, paramsCount, includeProtocols); - } - const MethodMeta* instanceMethod(StringImpl* identifier, size_t paramsCount, bool includeProtocols = true) const { - return this->member(identifier, MemberType::InstanceMethod, paramsCount, includeProtocols); + // Remove all optional methods/properties which are not implemented in the class + template + static void filterUnavailableMembers(MembersCollection& members, Class klass, bool isStatic) { + members.removeIf([klass, isStatic](const MemberMeta* memberMeta) { + return !static_cast(memberMeta)->isAvailableInClass(klass, isStatic); + }); } - const std::vector getInstanceMethods(StringImpl* identifier, bool includeProtocols = true) const { - return this->members(identifier, MemberType::InstanceMethod, includeProtocols); + const MembersCollection getInstanceMethods(StringImpl* identifier, Class klass, bool includeProtocols = true) const { + MembersCollection methods = this->members(identifier, MemberType::InstanceMethod, includeProtocols); + + filterUnavailableMembers(methods, klass, false); + + return methods; } /// static methods - const MethodMeta* staticMethod(const char* identifier, size_t paramsCount, bool includeProtocols = true) const { - return this->member(identifier, strlen(identifier), MemberType::StaticMethod, paramsCount, includeProtocols); - } + const MembersCollection getStaticMethods(StringImpl* identifier, Class klass, bool includeProtocols = true) const { + MembersCollection methods = this->members(identifier, MemberType::StaticMethod, includeProtocols); - const std::vector getStaticMethods(StringImpl* identifier, bool includeProtocols = true) const { - return this->members(identifier, MemberType::StaticMethod, includeProtocols); - } + filterUnavailableMembers(methods, klass, true); - const MethodMeta* staticMethod(StringImpl* identifier, size_t paramsCount, bool includeProtocols = true) const { - return this->member(identifier, MemberType::StaticMethod, paramsCount, includeProtocols); + return methods; } /// instance properties - const PropertyMeta* instanceProperty(const char* identifier, bool includeProtocols = true) const { - return reinterpret_cast(this->member(identifier, MemberType::InstanceProperty, includeProtocols)); + const PropertyMeta* instanceProperty(const char* identifier, Class klass, bool includeProtocols = true) const { + auto propMeta = static_cast(this->member(identifier, MemberType::InstanceProperty, includeProtocols)); + return propMeta && propMeta->isAvailableInClass(klass, /*isStatic*/ false) ? propMeta : nullptr; } - const PropertyMeta* instanceProperty(StringImpl* identifier, bool includeProtocols = true) const { - return reinterpret_cast(this->member(identifier, MemberType::InstanceProperty, includeProtocols)); + const PropertyMeta* instanceProperty(StringImpl* identifier, Class klass, bool includeProtocols = true) const { + auto propMeta = static_cast(this->member(identifier, MemberType::InstanceProperty, includeProtocols)); + return propMeta && propMeta->isAvailableInClass(klass, /*isStatic*/ false) ? propMeta : nullptr; } /// static properties - const PropertyMeta* staticProperty(const char* identifier, bool includeProtocols = true) const { - return reinterpret_cast(this->member(identifier, MemberType::StaticProperty, includeProtocols)); + const PropertyMeta* staticProperty(const char* identifier, Class klass, bool includeProtocols = true) const { + auto propMeta = static_cast(this->member(identifier, MemberType::StaticProperty, includeProtocols)); + return propMeta && propMeta->isAvailableInClass(klass, /*isStatic*/ true) ? propMeta : nullptr; } - const PropertyMeta* staticProperty(StringImpl* identifier, bool includeProtocols = true) const { - return reinterpret_cast(this->member(identifier, MemberType::StaticProperty, includeProtocols)); + const PropertyMeta* staticProperty(StringImpl* identifier, Class klass, bool includeProtocols = true) const { + auto propMeta = static_cast(this->member(identifier, MemberType::StaticProperty, includeProtocols)); + return propMeta && propMeta->isAvailableInClass(klass, /*isStatic*/ true) ? propMeta : nullptr; } /// vectors - std::vector instanceProperties() const { + std::vector instanceProperties(Class klass) const { std::vector properties; - return this->instanceProperties(properties); + return this->instanceProperties(properties, klass); } - std::vector instancePropertiesWithProtocols() const { + std::vector instancePropertiesWithProtocols(Class klass) const { std::vector properties; - return this->instancePropertiesWithProtocols(properties); + return this->instancePropertiesWithProtocols(properties, klass); } - std::vector instanceProperties(std::vector& container) const { + std::vector instanceProperties(std::vector& container, Class klass) const { for (Array>::iterator it = this->instanceProps->begin(); it != this->instanceProps->end(); it++) { - container.push_back((*it).valuePtr()); + if ((*it)->isAvailableInClass(klass, /*isStatic*/ false)) { + container.push_back((*it).valuePtr()); + } } return container; } - std::vector instancePropertiesWithProtocols(std::vector& container) const; + std::vector instancePropertiesWithProtocols(std::vector& container, Class klass) const; - std::vector staticProperties() const { + std::vector staticProperties(Class klass) const { std::vector properties; - return this->staticProperties(properties); + return this->staticProperties(properties, klass); } - std::vector staticPropertiesWithProtocols() const { + std::vector staticPropertiesWithProtocols(Class klass) const { std::vector properties; - return this->staticPropertiesWithProtocols(properties); + return this->staticPropertiesWithProtocols(properties, klass); } - std::vector staticProperties(std::vector& container) const { + std::vector staticProperties(std::vector& container, Class klass) const { for (Array>::iterator it = this->staticProps->begin(); it != this->staticProps->end(); it++) { - container.push_back((*it).valuePtr()); + if ((*it)->isAvailableInClass(klass, /*isStatic*/ true)) { + container.push_back((*it).valuePtr()); + } } return container; } - std::vector staticPropertiesWithProtocols(std::vector& container) const; + std::vector staticPropertiesWithProtocols(std::vector& container, Class klass) const; - std::vector initializers() const { + std::vector initializers(Class klass) const { std::vector initializers; - return this->initializers(initializers); + return this->initializers(initializers, klass); } - std::vector initializersWithProtcols() const { + std::vector initializersWithProtocols(Class klass) const { std::vector initializers; - return this->initializersWithProtcols(initializers); + return this->initializersWithProtocols(initializers, klass); } - std::vector initializers(std::vector& container) const; + std::vector initializers(std::vector& container, Class klass) const; - std::vector initializersWithProtcols(std::vector& container) const; + std::vector initializersWithProtocols(std::vector& container, Class klass) const; }; struct ProtocolMeta : BaseClassMeta { @@ -843,9 +896,10 @@ struct InterfaceMeta : BaseClassMeta { const InterfaceMeta* baseMeta() const { if (this->baseName() != nullptr) { - const Meta* baseMeta = MetaFile::instance()->globalTable()->findMeta(this->baseName()); - return baseMeta->type() == MetaType::Interface ? reinterpret_cast(baseMeta) : nullptr; + const InterfaceMeta* baseMeta = MetaFile::instance()->globalTable()->findInterfaceMeta(this->baseName()); + return baseMeta; } + return nullptr; } }; diff --git a/src/NativeScript/Metadata/Metadata.mm b/src/NativeScript/Metadata/Metadata.mm index 8d1e7d72e..62476b954 100644 --- a/src/NativeScript/Metadata/Metadata.mm +++ b/src/NativeScript/Metadata/Metadata.mm @@ -28,14 +28,12 @@ static UInt8 getSystemVersion() { UInt8 majorVersion = (UInt8)[versionTokens[0] intValue]; UInt8 minorVersion = (UInt8)[versionTokens[1] intValue]; - iosVersion = (majorVersion << 3) | minorVersion; - - return iosVersion; + return encodeVersion(majorVersion, minorVersion); } -std::unordered_map> getMetasByJSNames(std::vector members) { - std::unordered_map> result; +std::unordered_map getMetasByJSNames(MembersCollection members) { + std::unordered_map result; for (auto member : members) { - result[member->jsName()].push_back(member); + result[member->jsName()].add(member); } return result; } @@ -45,6 +43,42 @@ static int compareIdentifiers(const char* nullTerminated, const char* notNullTer return (result == 0) ? strlen(nullTerminated) - length : result; } +const InterfaceMeta* GlobalTable::findInterfaceMeta(WTF::StringImpl* identifier) const { + return this->findInterfaceMeta(reinterpret_cast(identifier->characters8()), identifier->length(), identifier->hash()); +} + +const InterfaceMeta* GlobalTable::findInterfaceMeta(const char* identifierString) const { + unsigned hash = WTF::StringHasher::computeHashAndMaskTop8Bits(reinterpret_cast(identifierString)); + return this->findInterfaceMeta(identifierString, strlen(identifierString), hash); +} + +const InterfaceMeta* GlobalTable::findInterfaceMeta(const char* identifierString, size_t length, unsigned hash) const { + const Meta* meta = MetaFile::instance()->globalTable()->findMeta(identifierString, length, hash, /*onlyIfAvailable*/ false); + if (meta == nullptr) { + return nullptr; + } + + assert(meta->type() == MetaType::Interface); + if (meta->type() != MetaType::Interface) { + return nullptr; + } + + const InterfaceMeta* interfaceMeta = static_cast(meta); + if (interfaceMeta->isAvailable()) { + return interfaceMeta; + } else { + const char* baseName = static_cast(interfaceMeta)->baseName(); + + NSLog(@"** \"%s\" introduced in iOS SDK %d.%d is currently unavailable, attempting to load its base: \"%s\". **", + std::string(identifierString, length).c_str(), + getMajorVersion(interfaceMeta->introducedIn()), + getMinorVersion(interfaceMeta->introducedIn()), + baseName); + + return this->findInterfaceMeta(baseName); + } +} + const Meta* GlobalTable::findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable) const { return this->findMeta(reinterpret_cast(identifier->characters8()), identifier->length(), identifier->hash(), onlyIfAvailable); } @@ -79,21 +113,9 @@ static int compareIdentifiers(const char* nullTerminated, const char* notNullTer // BaseClassMeta const MemberMeta* BaseClassMeta::member(const char* identifier, size_t length, MemberType type, bool includeProtocols, bool onlyIfAvailable) const { - std::vector members = this->members(identifier, length, type, includeProtocols, onlyIfAvailable); + MembersCollection members = this->members(identifier, length, type, includeProtocols, onlyIfAvailable); ASSERT(members.size() <= 1); - return members.size() == 1 ? members[0] : nullptr; -} -const MethodMeta* BaseClassMeta::member(const char* identifier, size_t length, MemberType type, size_t paramsCount, bool includeProtocols, bool onlyIfAvailable) const { - const std::vector metas = this->members(identifier, length, type, includeProtocols, onlyIfAvailable); - - if (metas.size() == 0) { - return nullptr; - } - const MemberMeta* result = Metadata::getProperFunctionFromContainer(metas, paramsCount, [&](const MemberMeta* const& meta) { - return ((MethodMeta*)meta)->encodings()->count - 1; - }); - - return (MethodMeta*)result; + return members.size() == 1 ? *members.begin() : nullptr; } void collectInheritanceChainMembers(const char* identifier, size_t length, MemberType type, bool onlyIfAvailable, const BaseClassMeta* derivedClass, std::function collectMember) { @@ -135,9 +157,9 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe } } -const std::vector BaseClassMeta::members(const char* identifier, size_t length, MemberType type, bool includeProtocols, bool onlyIfAvailable) const { +const MembersCollection BaseClassMeta::members(const char* identifier, size_t length, MemberType type, bool includeProtocols, bool onlyIfAvailable) const { - std::vector result; + MembersCollection result; if (type == MemberType::InstanceMethod || type == MemberType::StaticMethod) { @@ -151,12 +173,12 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe membersMap.emplace(method->encodings()->count, member); }); for (std::map::iterator it = membersMap.begin(); it != membersMap.end(); ++it) { - result.push_back(it->second); + result.add(it->second); } } else { // member is a property collectInheritanceChainMembers(identifier, length, type, onlyIfAvailable, this, [&](const MemberMeta* member) { - result.push_back(member); + result.add(member); }); } @@ -169,9 +191,9 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe for (Array::iterator it = protocols->begin(); it != protocols->end(); ++it) { const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr())); if (protocolMeta != nullptr) { - const std::vector members = protocolMeta->members(identifier, length, type, onlyIfAvailable); + const MembersCollection members = protocolMeta->members(identifier, length, type, onlyIfAvailable); if (members.size() > 0) { - result.insert(result.end(), members.begin(), members.end()); + result.add(members.begin(), members.end()); } } } @@ -180,33 +202,33 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe return result; } -std::vector BaseClassMeta::instancePropertiesWithProtocols(std::vector& container) const { - this->instanceProperties(container); +std::vector BaseClassMeta::instancePropertiesWithProtocols(std::vector& container, Class klass) const { + this->instanceProperties(container, klass); for (Array::iterator it = protocols->begin(); it != protocols->end(); ++it) { const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false)); if (protocolMeta != nullptr) - protocolMeta->instancePropertiesWithProtocols(container); + protocolMeta->instancePropertiesWithProtocols(container, klass); } return container; } -std::vector BaseClassMeta::staticPropertiesWithProtocols(std::vector& container) const { - this->staticProperties(container); +std::vector BaseClassMeta::staticPropertiesWithProtocols(std::vector& container, Class klass) const { + this->staticProperties(container, klass); for (Array::iterator it = protocols->begin(); it != protocols->end(); ++it) { const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false)); if (protocolMeta != nullptr) - protocolMeta->staticPropertiesWithProtocols(container); + protocolMeta->staticPropertiesWithProtocols(container, klass); } return container; } -vector BaseClassMeta::initializers(vector& container) const { +vector BaseClassMeta::initializers(vector& container, Class klass) 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()) { + if (method->isInitializer() && method->isAvailableInClass(klass, /*isStatic*/ false)) { container.push_back(method); } else { break; @@ -216,12 +238,12 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe return container; } -vector BaseClassMeta::initializersWithProtcols(vector& container) const { - this->initializers(container); +vector BaseClassMeta::initializersWithProtocols(vector& container, Class klass) const { + this->initializers(container, klass); for (Array::iterator it = this->protocols->begin(); it != this->protocols->end(); it++) { const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false)); if (protocolMeta != nullptr) - protocolMeta->initializersWithProtcols(container); + protocolMeta->initializersWithProtocols(container, klass); } return container; } diff --git a/src/NativeScript/ObjC/Constructor/ObjCConstructorBase.mm b/src/NativeScript/ObjC/Constructor/ObjCConstructorBase.mm index 916e8f97d..bd0a43dc4 100644 --- a/src/NativeScript/ObjC/Constructor/ObjCConstructorBase.mm +++ b/src/NativeScript/ObjC/Constructor/ObjCConstructorBase.mm @@ -169,12 +169,10 @@ static void writeAdapter(ExecState* execState, const JSValue& value, void* buffe const Metadata::InterfaceMeta* metadata = this->metadata(); do { - std::vector initializers = metadata->initializersWithProtcols(); + std::vector initializers = metadata->initializersWithProtocols(this->klass()); for (const Metadata::MethodMeta* method : initializers) { - if (method->isAvailable()) { - auto constructorWrapper = ObjCConstructorWrapper::create(vm, globalObject, globalObject->objCConstructorWrapperStructure(), this->_klass, method); - this->_initializers.append(WriteBarrier(vm, this, constructorWrapper.get())); - } + auto constructorWrapper = ObjCConstructorWrapper::create(vm, globalObject, globalObject->objCConstructorWrapperStructure(), this->_klass, method); + this->_initializers.append(WriteBarrier(vm, this, constructorWrapper.get())); } metadata = metadata->baseMeta(); @@ -216,9 +214,9 @@ static JSValue getInitializerForSwiftStyleConstruction(ExecState* execState, Obj std::vector initializers; initializers.reserve(16); do { - interface->initializersWithProtcols(initializers); + interface->initializersWithProtocols(initializers, constructor->klass()); for (const Metadata::MethodMeta* method : initializers) { - if (method->isAvailable() && strcmp(method->constructorTokens(), ctorMetadata.data()) == 0) { + if (strcmp(method->constructorTokens(), ctorMetadata.data()) == 0) { result = method; break; } diff --git a/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm b/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm index d05d493a9..b7c41533f 100644 --- a/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm +++ b/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm @@ -38,16 +38,16 @@ ObjCConstructorNative* constructor = jsCast(object); - std::vector methods = constructor->_metadata->getStaticMethods(propertyName.publicName()); + MembersCollection methods = constructor->_metadata->getStaticMethods(propertyName.publicName(), constructor->klass()); if (methods.size() > 0) { - std::unordered_map> metasByJsName = Metadata::getMetasByJSNames(methods); + std::unordered_map metasByJsName = Metadata::getMetasByJSNames(methods); for (auto& methodNameAndMetas : metasByJsName) { - std::vector& metas = methodNameAndMetas.second; + MembersCollection& metas = methodNameAndMetas.second; ASSERT(metas.size() > 0); - SymbolLoader::instance().ensureModule(metas[0]->topLevelModule()); + SymbolLoader::instance().ensureModule((*metas.begin())->topLevelModule()); GlobalObject* globalObject = jsCast(execState->lexicalGlobalObject()); auto wrapper = ObjCMethodWrapper::create(execState->vm(), globalObject, globalObject->objCMethodWrapperStructure(), metas); @@ -93,19 +93,22 @@ for (ArrayOfPtrTo::iterator it = baseClassMeta->staticMethods->begin(); it != baseClassMeta->staticMethods->end(); it++) { const MethodMeta* meta = (*it).valuePtr(); - if (meta->isAvailable()) + if (meta->isAvailableInClass(constructor->klass(), /*isStatic*/ true)) { propertyNames.add(Identifier::fromString(execState, meta->jsName())); + } } for (Metadata::ArrayOfPtrTo::iterator it = baseClassMeta->staticProps->begin(); it != baseClassMeta->staticProps->end(); it++) { - if ((*it)->isAvailable()) + if ((*it)->isAvailableInClass(constructor->klass(), /*isStatic*/ true)) { propertyNames.add(Identifier::fromString(execState, (*it)->jsName())); + } } for (Array::iterator it = baseClassMeta->protocols->begin(); it != baseClassMeta->protocols->end(); it++) { const ProtocolMeta* protocolMeta = (const ProtocolMeta*)MetaFile::instance()->globalTable()->findMeta((*it).valuePtr()); - if (protocolMeta != nullptr) + if (protocolMeta != nullptr) { baseClassMetaStack.push_back(protocolMeta); + } } } @@ -113,33 +116,31 @@ } void ObjCConstructorNative::materializeProperties(VM& vm, GlobalObject* globalObject) { - std::vector properties = this->metadata()->staticPropertiesWithProtocols(); + std::vector properties = this->metadata()->staticPropertiesWithProtocols(this->klass()); for (const PropertyMeta* propertyMeta : properties) { - if (propertyMeta->isAvailable()) { - SymbolLoader::instance().ensureModule(propertyMeta->topLevelModule()); - - const MethodMeta* getterMeta = (propertyMeta->getter() != nullptr && propertyMeta->getter()->isAvailable()) ? propertyMeta->getter() : nullptr; - const MethodMeta* setterMeta = (propertyMeta->setter() != nullptr && propertyMeta->setter()->isAvailable()) ? propertyMeta->setter() : nullptr; + SymbolLoader::instance().ensureModule(propertyMeta->topLevelModule()); - PropertyDescriptor descriptor; - Strong getter; - Strong setter; + const MethodMeta* getterMeta = (propertyMeta->getter() != nullptr && propertyMeta->getter()->isAvailableInClass(this->klass(), /*isStatic*/ true)) ? propertyMeta->getter() : nullptr; + const MethodMeta* setterMeta = (propertyMeta->setter() != nullptr && propertyMeta->setter()->isAvailableInClass(this->klass(), /*isStatic*/ true)) ? propertyMeta->setter() : nullptr; - if (getterMeta) { - std::vector getters(1, getterMeta); - getter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), getters); - descriptor.setGetter(getter.get()); - } + PropertyDescriptor descriptor; + Strong getter; + Strong setter; - if (setterMeta) { - std::vector setters(1, setterMeta); - setter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), setters); - descriptor.setSetter(setter.get()); - } + if (getterMeta) { + MembersCollection getters = { getterMeta }; + getter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), getters); + descriptor.setGetter(getter.get()); + } - Base::defineOwnProperty(this, globalObject->globalExec(), Identifier::fromString(globalObject->globalExec(), propertyMeta->jsName()), descriptor, false); + if (setterMeta) { + MembersCollection setters = { setterMeta }; + setter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), setters); + descriptor.setSetter(setter.get()); } + + Base::defineOwnProperty(this, globalObject->globalExec(), Identifier::fromString(globalObject->globalExec(), propertyMeta->jsName()), descriptor, false); } } diff --git a/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.h b/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.h index dcae958a7..bf7840e89 100644 --- a/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.h +++ b/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.h @@ -47,6 +47,8 @@ class ObjCClassBuilder { ObjCConstructorDerived* build(JSC::ExecState*); + Class klass(); + private: JSC::Strong _constructor; diff --git a/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.mm b/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.mm index 65d722477..ee8cf4703 100644 --- a/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.mm +++ b/src/NativeScript/ObjC/Inheritance/ObjCClassBuilder.mm @@ -230,7 +230,7 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, this->_protocols.push_back(protocolWrapperObject->metadata()); if (Protocol* aProtocol = protocolWrapperObject->protocol()) { - Class klass = this->_constructor.get()->klass(); + Class klass = this->klass(); if ([klass conformsToProtocol:aProtocol]) { WTF::String errorMessage = WTF::String::format("Class \"%s\" already implements the \"%s\" protocol.", class_getName(klass), protocol_getName(aProtocol)); warn(execState, errorMessage); @@ -266,21 +266,19 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, } void ObjCClassBuilder::addInstanceMethod(ExecState* execState, const Identifier& jsName, JSCell* method) { - Class klass = this->_constructor.get()->klass(); overrideObjcMethodCalls(execState, this->_constructor.get(), jsName, method, this->_constructor->metadata(), MemberType::InstanceMethod, - klass, + this->klass(), &this->_protocols); } void ObjCClassBuilder::addInstanceMethod(ExecState* execState, const Identifier& jsName, JSCell* method, JSC::JSValue& typeEncoding) { - Class klass = this->_constructor.get()->klass(); SEL methodName = sel_registerName(jsName.utf8().data()); - addMethodToClass(execState, klass, method, methodName, typeEncoding); + addMethodToClass(execState, this->klass(), method, methodName, typeEncoding); } void ObjCClassBuilder::addProperty(ExecState* execState, const Identifier& name, const PropertyDescriptor& propertyDescriptor) { @@ -292,20 +290,22 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, const InterfaceMeta* currentClass = this->_baseConstructor->metadata(); const PropertyMeta* propertyMeta = nullptr; do { - propertyMeta = currentClass->instanceProperty(propertyName); + // Do not pass a `Class` instance, we need to override the property and must not check whether it's implemented or not + propertyMeta = currentClass->instanceProperty(propertyName, nullptr); currentClass = currentClass->baseMeta(); } while (!propertyMeta && currentClass); if (!propertyMeta && !this->_protocols.empty()) { for (const ProtocolMeta* aProtocol : this->_protocols) { - if ((propertyMeta = aProtocol->instanceProperty(propertyName))) { + // Do not pass a `Class` instance, we need to override the property and must not check whether it's implemented or not + if ((propertyMeta = aProtocol->instanceProperty(propertyName, nullptr))) { break; } } } if (propertyMeta) { - Class klass = this->_constructor.get()->klass(); + Class klass = this->klass(); GlobalObject* globalObject = jsCast(execState->lexicalGlobalObject()); VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -407,8 +407,8 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, } if (instanceMethods->hasOwnProperty(execState, execState->vm().propertyNames->iteratorSymbol)) { - class_addProtocol(this->_constructor->klass(), @protocol(NSFastEnumeration)); - class_addProtocol(object_getClass(this->_constructor->klass()), @protocol(NSFastEnumeration)); + class_addProtocol(this->klass(), @protocol(NSFastEnumeration)); + class_addProtocol(object_getClass(this->klass()), @protocol(NSFastEnumeration)); GlobalObject* globalObject = jsCast(execState->lexicalGlobalObject()); IMP imp = imp_implementationWithBlock(^NSUInteger(id self, NSFastEnumerationState* state, id buffer[], NSUInteger length) { @@ -416,13 +416,13 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, }); struct objc_method_description fastEnumerationMethodDescription = protocol_getMethodDescription(@protocol(NSFastEnumeration), @selector(countByEnumeratingWithState:objects:count:), YES, YES); - class_addMethod(this->_constructor->klass(), @selector(countByEnumeratingWithState:objects:count:), imp, fastEnumerationMethodDescription.types); + class_addMethod(this->klass(), @selector(countByEnumeratingWithState:objects:count:), imp, fastEnumerationMethodDescription.types); } } void ObjCClassBuilder::addStaticMethod(ExecState* execState, const Identifier& jsName, JSCell* method) { - Class klass = this->_constructor.get()->klass(); + Class klass = this->klass(); overrideObjcMethodCalls(execState, this->_constructor.get(), jsName, @@ -434,7 +434,7 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, } void ObjCClassBuilder::addStaticMethod(ExecState* execState, const Identifier& jsName, JSCell* method, JSC::JSValue& typeEncoding) { - Class klass = object_getClass(this->_constructor.get()->klass()); + Class klass = object_getClass(this->klass()); SEL methodName = sel_registerName(jsName.utf8().data()); addMethodToClass(execState, klass, method, methodName, typeEncoding); } @@ -469,7 +469,7 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, } ObjCConstructorDerived* ObjCClassBuilder::build(ExecState* execState) { - Class klass = this->_constructor.get()->klass(); + Class klass = this->klass(); GlobalObject* globalObject = jsCast(execState->lexicalGlobalObject()); @@ -479,4 +479,8 @@ static void addMethodToClass(ExecState* execState, Class klass, JSCell* method, return this->_constructor.get(); } +Class ObjCClassBuilder::klass() { + return this->_constructor->klass(); +} + } //namespace NativeScript diff --git a/src/NativeScript/ObjC/ObjCMethodCall.h b/src/NativeScript/ObjC/ObjCMethodCall.h index 33f629a3b..194224761 100644 --- a/src/NativeScript/ObjC/ObjCMethodCall.h +++ b/src/NativeScript/ObjC/ObjCMethodCall.h @@ -12,24 +12,20 @@ #include "FFICall.h" #include "FunctionWrapper.h" #include "JavaScriptCore/IsoSubspace.h" - -namespace Metadata { -struct MemberMeta; -struct MethodMeta; -} // namespace Metadata +#include "Metadata.h" namespace NativeScript { class ObjCMethodWrapper : public FunctionWrapper { public: typedef FunctionWrapper Base; - static JSC::Strong create(JSC::VM& vm, GlobalObject* globalObject, JSC::Structure* structure, std::vector metadata) { + static JSC::Strong create(JSC::VM& vm, GlobalObject* globalObject, JSC::Structure* structure, Metadata::MembersCollection metadata) { JSC::Strong cell(vm, new (NotNull, JSC::allocateCell(vm.heap)) ObjCMethodWrapper(vm, structure)); cell->finishCreation(vm, globalObject, metadata); return cell; } - static Strong create(JSC::ExecState* execState, std::vector metadata) { + static Strong create(JSC::ExecState* execState, Metadata::MembersCollection metadata) { GlobalObject* globalObject = jsCast(execState->lexicalGlobalObject()); return create(execState->vm(), globalObject, globalObject->objCMethodWrapperStructure(), metadata); } @@ -50,7 +46,7 @@ class ObjCMethodWrapper : public FunctionWrapper { : Base(vm, structure) { } - void finishCreation(JSC::VM&, GlobalObject*, std::vector methods); + void finishCreation(JSC::VM&, GlobalObject*, Metadata::MembersCollection methods); static void preInvocation(FFICall*, JSC::ExecState*, FFICall::Invocation&); static void postInvocation(FFICall*, JSC::ExecState*, FFICall::Invocation&); diff --git a/src/NativeScript/ObjC/ObjCMethodCall.mm b/src/NativeScript/ObjC/ObjCMethodCall.mm index 61c4abca2..9c4dc31d6 100644 --- a/src/NativeScript/ObjC/ObjCMethodCall.mm +++ b/src/NativeScript/ObjC/ObjCMethodCall.mm @@ -25,9 +25,9 @@ const ClassInfo ObjCMethodWrapper::s_info = { "ObjCMethodWrapper", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ObjCMethodWrapper) }; -void ObjCMethodWrapper::finishCreation(VM& vm, GlobalObject* globalObject, std::vector methods) { +void ObjCMethodWrapper::finishCreation(VM& vm, GlobalObject* globalObject, MembersCollection methods) { ASSERT(methods.size() > 0); - Base::finishCreation(vm, methods.front()->jsName()); + Base::finishCreation(vm, (*methods.begin())->jsName()); size_t maxParamsCount = 0; for (auto m : methods) { @@ -146,12 +146,7 @@ static bool isJavaScriptDerived(JSC::JSValue value) { bool isInstance = !class_isMetaClass(object_getClass(target)); NSLog(@"> %@[%@ %@]", isInstance ? @"-" : @"+", NSStringFromClass(object_getClass(target)), NSStringFromSelector(call->_selector)); #endif - if (call->_isOptional && ![target respondsToSelector:call->_selector]) { - // Unimplemented optional method: Silently perform a dummy call to nil object - invocation.setArgument(0, nil); - } else { - invocation.setArgument(0, target); - } + invocation.setArgument(0, target); invocation.setArgument(1, call->_selector); invocation.function = call->_msgSend; } diff --git a/src/NativeScript/ObjC/ObjCMethodCallback.mm b/src/NativeScript/ObjC/ObjCMethodCallback.mm index 5e89a373e..372f56bfc 100644 --- a/src/NativeScript/ObjC/ObjCMethodCallback.mm +++ b/src/NativeScript/ObjC/ObjCMethodCallback.mm @@ -56,7 +56,7 @@ void overrideObjcMethodCalls(ExecState* execState, JSObject* object, PropertyNam ObjCMethodWrapper* wrapper = jsDynamicCast(execState->vm(), object->get(execState, propertyName)); Strong strongWrapper; if (!wrapper) { - std::vector methodMetas; + MembersCollection methodMetas; auto currentClass = meta; do { diff --git a/src/NativeScript/ObjC/ObjCProtocolWrapper.mm b/src/NativeScript/ObjC/ObjCProtocolWrapper.mm index d4318a888..4e9e2ebb8 100644 --- a/src/NativeScript/ObjC/ObjCProtocolWrapper.mm +++ b/src/NativeScript/ObjC/ObjCProtocolWrapper.mm @@ -42,9 +42,9 @@ ObjCProtocolWrapper* protocol = jsCast(object); - std::vector metas = protocol->_metadata->getStaticMethods(propertyName.publicName()); + MembersCollection metas = protocol->_metadata->getStaticMethods(propertyName.publicName(), nullptr); if (metas.size() > 0) { - SymbolLoader::instance().ensureModule(metas[0]->topLevelModule()); + SymbolLoader::instance().ensureModule((*metas.begin())->topLevelModule()); GlobalObject* globalObject = jsCast(execState->lexicalGlobalObject()); diff --git a/src/NativeScript/ObjC/ObjCPrototype.h b/src/NativeScript/ObjC/ObjCPrototype.h index d5eb292f8..ff1ca103f 100644 --- a/src/NativeScript/ObjC/ObjCPrototype.h +++ b/src/NativeScript/ObjC/ObjCPrototype.h @@ -23,8 +23,8 @@ class ObjCPrototype : public JSC::JSDestructibleObject { static const unsigned StructureFlags; - static JSC::Strong create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, const Metadata::BaseClassMeta* metadata) { - JSC::Strong prototype(vm, new (NotNull, JSC::allocateCell(globalObject->vm().heap)) ObjCPrototype(globalObject->vm(), structure)); + static JSC::Strong create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, const Metadata::BaseClassMeta* metadata, Class klass) { + JSC::Strong prototype(vm, new (NotNull, JSC::allocateCell(globalObject->vm().heap)) ObjCPrototype(globalObject->vm(), structure, klass)); prototype->finishCreation(vm, globalObject, metadata); return prototype; } @@ -39,13 +39,18 @@ class ObjCPrototype : public JSC::JSDestructibleObject { void materializeProperties(JSC::VM& vm, GlobalObject* globalObject); - const Metadata::BaseClassMeta* metadata() { + const Metadata::BaseClassMeta* metadata() const { return this->_metadata; } + Class klass() const { + return this->_klass; + } + private: - ObjCPrototype(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) { + ObjCPrototype(JSC::VM& vm, JSC::Structure* structure, Class klass) + : Base(vm, structure) + , _klass(klass) { } void finishCreation(JSC::VM&, JSC::JSGlobalObject*, const Metadata::BaseClassMeta*); @@ -59,6 +64,8 @@ class ObjCPrototype : public JSC::JSDestructibleObject { static void getOwnPropertyNames(JSC::JSObject*, JSC::ExecState*, JSC::PropertyNameArray&, JSC::EnumerationMode); const Metadata::BaseClassMeta* _metadata; + + Class _klass; }; } // namespace NativeScript diff --git a/src/NativeScript/ObjC/ObjCPrototype.mm b/src/NativeScript/ObjC/ObjCPrototype.mm index 86ca1dc0b..64108ed1f 100644 --- a/src/NativeScript/ObjC/ObjCPrototype.mm +++ b/src/NativeScript/ObjC/ObjCPrototype.mm @@ -61,23 +61,15 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) { ObjCPrototype* prototype = jsCast(object); - std::vector methods = prototype->_metadata->getInstanceMethods(propertyName.publicName()); + MembersCollection methods = prototype->_metadata->getInstanceMethods(propertyName.publicName(), prototype->klass()); if (methods.size() > 0) { + SymbolLoader::instance().ensureModule((*methods.begin())->topLevelModule()); - std::unordered_map> metasByJsName = Metadata::getMetasByJSNames(methods); - - for (auto& methodNameAndMetas : metasByJsName) { - std::vector& metas = methodNameAndMetas.second; - - ASSERT(metas.size() > 0); - SymbolLoader::instance().ensureModule(metas[0]->topLevelModule()); - - GlobalObject* globalObject = jsCast(prototype->globalObject()); - auto method = ObjCMethodWrapper::create(globalObject->vm(), globalObject, globalObject->objCMethodWrapperStructure(), metas); - object->putDirect(execState->vm(), propertyName, method.get()); - propertySlot.setValue(object, static_cast(PropertyAttribute::None), method.get()); - } + GlobalObject* globalObject = jsCast(prototype->globalObject()); + auto method = ObjCMethodWrapper::create(globalObject->vm(), globalObject, globalObject->objCMethodWrapperStructure(), methods); + object->putDirect(execState->vm(), propertyName, method.get()); + propertySlot.setValue(object, static_cast(PropertyAttribute::None), method.get()); return true; } @@ -108,13 +100,13 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) { bool ObjCPrototype::defineOwnProperty(JSObject* object, ExecState* execState, PropertyName propertyName, const PropertyDescriptor& propertyDescriptor, bool shouldThrow) { ObjCPrototype* prototype = jsCast(object); VM& vm = execState->vm(); + Class klass = prototype->klass(); - if (const PropertyMeta* propertyMeta = prototype->_metadata->instanceProperty(propertyName.publicName())) { + if (const PropertyMeta* propertyMeta = prototype->_metadata->instanceProperty(propertyName.publicName(), klass)) { if (!propertyDescriptor.isAccessorDescriptor()) { WTFCrash(); } - Class klass = jsCast(prototype->get(execState, execState->vm().propertyNames->constructor))->klass(); PropertyDescriptor nativeProperty; prototype->getOwnPropertyDescriptor(execState, propertyName, nativeProperty); @@ -150,6 +142,7 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) { void ObjCPrototype::getOwnPropertyNames(JSObject* object, ExecState* execState, PropertyNameArray& propertyNames, EnumerationMode enumerationMode) { ObjCPrototype* prototype = jsCast(object); + Class klass = prototype->klass(); std::vector baseClassMetaStack; baseClassMetaStack.push_back(prototype->_metadata); @@ -159,12 +152,12 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) { baseClassMetaStack.pop_back(); for (Metadata::ArrayOfPtrTo::iterator it = baseClassMeta->instanceMethods->begin(); it != baseClassMeta->instanceMethods->end(); it++) { - if ((*it)->isAvailable()) + if ((*it)->isAvailableInClass(klass, /*isStatic*/ false)) propertyNames.add(Identifier::fromString(execState, (*it)->jsName())); } for (Metadata::ArrayOfPtrTo::iterator it = baseClassMeta->instanceProps->begin(); it != baseClassMeta->instanceProps->end(); it++) { - if ((*it)->isAvailable()) + if ((*it)->isAvailableInClass(klass, /*isStatic*/ false)) propertyNames.add(Identifier::fromString(execState, (*it)->jsName())); } @@ -179,33 +172,31 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) { } void ObjCPrototype::materializeProperties(VM& vm, GlobalObject* globalObject) { - std::vector properties = this->_metadata->instancePropertiesWithProtocols(); + std::vector properties = this->_metadata->instancePropertiesWithProtocols(this->klass()); for (const PropertyMeta* propertyMeta : properties) { - if (propertyMeta->isAvailable()) { - SymbolLoader::instance().ensureModule(propertyMeta->topLevelModule()); - - const MethodMeta* getter = (propertyMeta->getter() != nullptr && propertyMeta->getter()->isAvailable()) ? propertyMeta->getter() : nullptr; - const MethodMeta* setter = (propertyMeta->setter() != nullptr && propertyMeta->setter()->isAvailable()) ? propertyMeta->setter() : nullptr; - - PropertyDescriptor descriptor; - descriptor.setConfigurable(true); - Strong strongGetter; - Strong strongSetter; - if (getter) { - std::vector getters(1, getter); - strongGetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), getters); - descriptor.setGetter(strongGetter.get()); - } - - if (setter) { - std::vector setters(1, setter); - strongSetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), setters); - descriptor.setSetter(strongSetter.get()); - } + SymbolLoader::instance().ensureModule(propertyMeta->topLevelModule()); + + const MethodMeta* getter = (propertyMeta->getter() != nullptr && propertyMeta->getter()->isAvailable()) ? propertyMeta->getter() : nullptr; + const MethodMeta* setter = (propertyMeta->setter() != nullptr && propertyMeta->setter()->isAvailable()) ? propertyMeta->setter() : nullptr; + + PropertyDescriptor descriptor; + descriptor.setConfigurable(true); + Strong strongGetter; + Strong strongSetter; + if (getter) { + MembersCollection getters = { getter }; + strongGetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), getters); + descriptor.setGetter(strongGetter.get()); + } - Base::defineOwnProperty(this, globalObject->globalExec(), Identifier::fromString(globalObject->globalExec(), propertyMeta->jsName()), descriptor, false); + if (setter) { + MembersCollection setters = { setter }; + strongSetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), setters); + descriptor.setSetter(strongSetter.get()); } + + Base::defineOwnProperty(this, globalObject->globalExec(), Identifier::fromString(globalObject->globalExec(), propertyMeta->jsName()), descriptor, false); } } } diff --git a/src/NativeScript/TypeFactory.mm b/src/NativeScript/TypeFactory.mm index 66669d5b3..d6ec1348e 100644 --- a/src/NativeScript/TypeFactory.mm +++ b/src/NativeScript/TypeFactory.mm @@ -247,7 +247,7 @@ VM& vm = globalObject->vm(); - const InterfaceMeta* metadata = static_cast(MetaFile::instance()->globalTable()->findMeta(klassName.impl())); + const InterfaceMeta* metadata = MetaFile::instance()->globalTable()->findInterfaceMeta(klassName.impl()); Class klass = Nil; if (metadata) { @@ -289,7 +289,7 @@ } Structure* prototypeStructure = ObjCPrototype::createStructure(vm, globalObject, parentPrototype); - auto prototype = ObjCPrototype::create(vm, globalObject, prototypeStructure, metadata); + auto prototype = ObjCPrototype::create(vm, globalObject, prototypeStructure, metadata, klass); Structure* constructorStructure = ObjCConstructorNative::createStructure(vm, globalObject, parentConstructor); auto constructor = ObjCConstructorNative::create(vm, globalObject, constructorStructure, prototype.get(), klass); diff --git a/tests/TestFixtures/Api/TNSVersions.h b/tests/TestFixtures/Api/TNSVersions.h index 3c86a3e06..eb805e8b4 100644 --- a/tests/TestFixtures/Api/TNSVersions.h +++ b/tests/TestFixtures/Api/TNSVersions.h @@ -6,50 +6,53 @@ // Copyright (c) 2015 Jason Zhekov. All rights reserved. // -#define generateVersionDeclarations(V1, V2) \ - __attribute__((availability(ios, introduced = V1))) \ - @interface TNSInterface##V2##Plus : NSObject \ - @end \ - \ - @interface TNSInterfaceMembers##V2 : NSObject \ - @property int property \ - __attribute__((availability(ios, introduced = V1))); \ - \ - +(void)staticMethod \ - __attribute__((availability(ios, introduced = V1))); \ - \ - -(void)instanceMethod \ - __attribute__((availability(ios, introduced = V1))); \ - @end \ - \ - __attribute__((availability(ios, introduced = V1))) void TNSFunction##V2##Plus(); \ - \ - __attribute__((availability(ios, introduced = V1))) extern const int TNSConstant##V2##Plus; \ - \ - enum TNSEnum##V2##Plus { \ - TNSEnum##V2##Member \ - } \ +#define generateVersionDeclarations(V1, V2) \ + __attribute__((availability(ios, introduced = V1))) \ + @interface TNSInterface \ + ##V2##Plus : NSObject \ + @end \ + \ + @interface TNSInterfaceMembers \ + ##V2 : NSObject \ + @property int property \ + __attribute__((availability(ios, introduced = V1))); \ + \ + +(void)staticMethod \ + __attribute__((availability(ios, introduced = V1))); \ + \ + -(void)instanceMethod \ + __attribute__((availability(ios, introduced = V1))); \ + @end \ + \ + __attribute__((availability(ios, introduced = V1))) void TNSFunction##V2##Plus(); \ + \ + __attribute__((availability(ios, introduced = V1))) extern const int TNSConstant##V2##Plus; \ + \ + enum TNSEnum##V2##Plus { \ + TNSEnum##V2##Member \ + } \ __attribute__((availability(ios, introduced = V1))) #ifndef generateVersionImpl #define generateVersion(V1, V2) \ generateVersionDeclarations(V1, V2) #else -#define generateVersion(V1, V2) \ - generateVersionDeclarations(V1, V2); \ - \ - @implementation TNSInterface \ - ##V2##Plus \ - @end \ - \ - @implementation TNSInterfaceMembers##V2 \ - + (void)staticMethod{} \ - \ - - (void)instanceMethod {} \ - @end \ - \ - void TNSFunction##V2##Plus() {} \ - \ +#define generateVersion(V1, V2) \ + generateVersionDeclarations(V1, V2); \ + \ + @implementation TNSInterface \ + ##V2##Plus \ + @end \ + \ + @implementation TNSInterfaceMembers \ + ##V2 \ + + (void)staticMethod{} \ + \ + - (void)instanceMethod {} \ + @end \ + \ + void TNSFunction##V2##Plus() {} \ + \ const int TNSConstant##V2##Plus = 0 #endif @@ -68,3 +71,19 @@ generateMinors(12); generateMinors(13); generateMinors(14); generateMinors(15); + +// max availability version that can be currently represented in the binary metadata is 31.7 (major << 3 | minor) -> uint8_t +#define MAX_AVAILABILITY 31.7 + +@interface TNSInterfaceAlwaysAvailable : NSObject +@end + +__attribute__((availability(ios, introduced = MAX_AVAILABILITY))) +@interface TNSInterfaceNeverAvailable : TNSInterfaceAlwaysAvailable +@end + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" +@interface TNSInterfaceNeverAvailableDescendant : TNSInterfaceNeverAvailable +@end +#pragma clang diagnostic pop diff --git a/tests/TestFixtures/Api/TNSVersions.m b/tests/TestFixtures/Api/TNSVersions.m index a03c9c991..d2687301c 100644 --- a/tests/TestFixtures/Api/TNSVersions.m +++ b/tests/TestFixtures/Api/TNSVersions.m @@ -9,3 +9,12 @@ #define generateVersionImpl #import "TNSVersions.h" #undef generateVersionImpl + +@implementation TNSInterfaceAlwaysAvailable +@end + +@implementation TNSInterfaceNeverAvailable : TNSInterfaceAlwaysAvailable +@end + +@implementation TNSInterfaceNeverAvailableDescendant : TNSInterfaceNeverAvailable +@end diff --git a/tests/TestFixtures/Interfaces/TNSInheritance.h b/tests/TestFixtures/Interfaces/TNSInheritance.h index 78e734ef9..992278c58 100644 --- a/tests/TestFixtures/Interfaces/TNSInheritance.h +++ b/tests/TestFixtures/Interfaces/TNSInheritance.h @@ -20,6 +20,16 @@ @protocol TNSIBaseProtocol @optional +@property(class) int staticBaseImplementedOptionalProperty; +@property(class) int staticBaseNotImplementedOptionalProperty; + ++ (void)staticBaseImplementedOptionalMethod; ++ (void)staticBaseNotImplementedOptionalMethod; ++ (void)staticBaseNotImplementedOptionalMethodImplementedInJavaScript; + +@property int baseImplementedOptionalProperty; +@property int baseNotImplementedOptionalProperty; + - (void)baseImplementedOptionalMethod; - (void)baseNotImplementedOptionalMethod; - (void)baseNotImplementedOptionalMethodImplementedInJavaScript; @@ -29,6 +39,9 @@ @end @interface TNSIBaseInterface (TNSIBaseCategory) ++ (void)staticBaseImplementedCategoryMethod; ++ (void)staticBaseNotImplementedCategoryMethod; + - (void)baseImplementedCategoryMethod; - (void)baseNotImplementedCategoryMethod; - (void)baseNotImplementedNativeCategoryMethodOverridenInJavaScript; @@ -37,15 +50,25 @@ @protocol TNSIDerivedProtocol @optional ++ (void)staticDerivedImplementedOptionalMethod; ++ (void)staticDerivedNotImplementedOptionalMethod; +@property(class) int staticDerivedImplementedOptionalProperty; +@property(class) int staticDerivedNotImplementedOptionalProperty; + - (void)derivedImplementedOptionalMethod; - (void)derivedNotImplementedOptionalMethod; - (void)derivedNotImplementedOptionalMethodImplementedInJavaScript; +@property int derivedImplementedOptionalProperty; +@property int derivedNotImplementedOptionalProperty; @end @interface TNSIDerivedInterface : TNSIBaseInterface @end @interface TNSIDerivedInterface (TNSIDerivedCategory) ++ (void)staticDerivedImplementedCategoryMethod; ++ (void)staticDerivedNotImplementedCategoryMethod; + - (void)derivedImplementedCategoryMethod; - (void)derivedNotImplementedCategoryMethod; - (void)derivedNotImplementedNativeCategoryMethodOverridenInJavaScript; diff --git a/tests/TestFixtures/Interfaces/TNSInheritance.m b/tests/TestFixtures/Interfaces/TNSInheritance.m index b41e0da96..893fa9ed4 100644 --- a/tests/TestFixtures/Interfaces/TNSInheritance.m +++ b/tests/TestFixtures/Interfaces/TNSInheritance.m @@ -28,6 +28,20 @@ - (NSString*)description { @end @implementation TNSIBaseInterface + ++ (int)staticBaseImplementedOptionalProperty { + return -1; +} + ++ (void)setStaticBaseImplementedOptionalProperty:(int)x { +} + ++ (void)staticBaseImplementedOptionalMethod { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + +@synthesize baseImplementedOptionalProperty; + - (void)baseImplementedOptionalMethod { TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); } @@ -36,18 +50,40 @@ - (void)baseImplementedOptionalMethod { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wincomplete-implementation" @implementation TNSIBaseInterface (TNSIBaseCategory) ++ (void)staticBaseImplementedCategoryMethod { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + - (void)baseImplementedCategoryMethod { TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); } @end @implementation TNSIDerivedInterface ++ (int)staticDerivedImplementedOptionalProperty { + return -1; +} + ++ (void)setStaticDerivedImplementedOptionalProperty:(int)x { +} + +@synthesize derivedImplementedOptionalProperty; + ++ (void)staticDerivedImplementedOptionalMethod { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + - (void)derivedImplementedOptionalMethod { TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); } + @end @implementation TNSIDerivedInterface (TNSIDerivedCategory) ++ (void)staticDerivedImplementedCategoryMethod { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + - (void)derivedImplementedCategoryMethod { TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); } @@ -62,4 +98,4 @@ + (void)consumeIterable:(id)iterable { } } -@end \ No newline at end of file +@end diff --git a/tests/TestRunner/app/ApiTests.js b/tests/TestRunner/app/ApiTests.js index 5fdaeb655..54e5d1e39 100644 --- a/tests/TestRunner/app/ApiTests.js +++ b/tests/TestRunner/app/ApiTests.js @@ -197,6 +197,32 @@ describe(module.id, function () { expect(field.secureTextEntry).toBe(true); }); + it("SpecialCaseProperty_When_CustomSelector_ImplementedInJS", function () { + var field = new (UITextField.extend({ + get secureTextEntry() { + TNSLog("getter"); + return this._secureTextEntry; + }, + set secureTextEntry(val) { + this._secureTextEntry = val; + TNSLog("setter:" + val); + } + }))(); + var expectedOutput = ""; + + expect(field.secureTextEntry).toBeUndefined(); expectedOutput+="getter"; + + field.secureTextEntry = true; expectedOutput+="setter:true"; + + expect(field.secureTextEntry).toBe(true); expectedOutput+="getter"; + + field.secureTextEntry = false; expectedOutput+="setter:false"; + + expect(field.secureTextEntry).toBe(false); expectedOutput+="getter"; + + expect(TNSGetOutput()).toBe(expectedOutput); + }); + it("TypedefPointerClass", function () { expect(TNSApi.alloc().init().strokeColor).toBeNull(); }); diff --git a/tests/TestRunner/app/Inheritance/InheritanceTests.js b/tests/TestRunner/app/Inheritance/InheritanceTests.js index adc7511d6..2192344bc 100644 --- a/tests/TestRunner/app/Inheritance/InheritanceTests.js +++ b/tests/TestRunner/app/Inheritance/InheritanceTests.js @@ -184,11 +184,11 @@ describe(module.id, function () { 'instance methodWith:param: called with 2 params' ); }); - + it("Explicitly called base class method", function () { var object = TNSDerivedInterface.alloc().init(); TNSBaseInterface.baseMethod.call(object); - + var actual = TNSGetOutput(); expect(actual).toBe('instance baseMethod called'); }); @@ -1760,7 +1760,7 @@ describe(module.id, function () { it('ExposeWithoutImplementation', function () { NSObject.extend({}, { exposedMethods: { - 'nonExistingSelector': {returns: interop.types.void, params: [interop.types.selector]} + 'nonExistingSelector': { returns: interop.types.void, params: [interop.types.selector] } } }); }); @@ -1784,7 +1784,7 @@ describe(module.id, function () { }); it('OptionalProtocolMethodsAndCategories', function () { - var object = TNSIDerivedInterface.extend({ + const TNSIDerivedInterfaceImpl = TNSIDerivedInterface.extend({ baseImplementedOptionalMethod: function () { TNSLog('js baseImplementedOptionalMethod called'); TNSIDerivedInterface.prototype.baseImplementedOptionalMethod.apply(this, arguments); @@ -1813,7 +1813,8 @@ describe(module.id, function () { derivedNotImplementedNativeCategoryMethodOverridenInJavaScript: function () { TNSLog('js derivedNotImplementedNativeCategoryMethodOverridenInJavaScript called'); } - }).alloc().init(); + }); + var object = TNSIDerivedInterfaceImpl.alloc().init(); object.baseImplementedOptionalMethod(); object.baseNotImplementedOptionalMethodImplementedInJavaScript(); @@ -1848,6 +1849,51 @@ describe(module.id, function () { actual = TNSGetOutput(); expect(actual).toBe(expected); TNSClearOutput(); + + const checkMethodsAvailability = (obj, isStatic) => { + let keys = []; + for (key in obj) { + keys.push(key); + } + + const jsImplementedClassMembersContainer = isStatic + ? TNSIDerivedInterfaceImpl /* static properties are provided by the constructor function (and it's prototype chain)*/ + : TNSIDerivedInterfaceImpl.prototype /* instance properties are provided by the object's prototype and it's chain */; + const firstBase = Object.getPrototypeOf(jsImplementedClassMembersContainer); + const secondBase = Object.getPrototypeOf(firstBase); + const scopes = [firstBase, secondBase]; + const prefixes = isStatic ? ["staticDerived", "staticBase"] : ["derived", "base"]; + + for (i in prefixes) { + const prefix = prefixes[i]; + const scope = scopes[i]; + const checkFuncOrProp = (scopeObj, suffix, desc, defined) => { + const propName = `${prefix}${suffix}`; + + if (defined) { + expect(keys).toContain(propName, `${desc} should be listed (${propName})`); + expect(Object.getOwnPropertyDescriptor(scopeObj, propName)).toBeDefined(`${desc} should be directly retrievable (${propName})`); + } else { + expect(keys).not.toContain(propName, `${desc} should not be listed (${propName})`); + expect(Object.getOwnPropertyDescriptor(scopeObj, propName)).toBeUndefined(`${desc} should not be directly retrievable (${propName})`); + } + }; + checkFuncOrProp(scope, `ImplementedOptionalProperty`, "Implemented optional properties", true); + checkFuncOrProp(scope, `NotImplementedOptionalProperty`, "Unimplemented optional properties", false); + checkFuncOrProp(scope, `ImplementedOptionalMethod`, "Implemented optional methods", true); + checkFuncOrProp(scope, `NotImplementedOptionalMethod`, "Unimplemented optional methods", false); + // static methods cannot be defined in JavaScript (yet) + if (!isStatic) { + checkFuncOrProp(jsImplementedClassMembersContainer, `NotImplementedOptionalMethodImplementedInJavaScript`, "Optional methods implemented in JS", true); + } + + checkFuncOrProp(scope, `ImplementedCategoryMethod`, "Implemented category methods", true); + checkFuncOrProp(scope, `NotImplementedCategoryMethod`, "Unimplemented category methods", false); + } + }; + + checkMethodsAvailability(object, false); + checkMethodsAvailability(TNSIDerivedInterfaceImpl, true); }); it('AddedNewProperty', function () { @@ -1872,7 +1918,7 @@ describe(module.id, function () { expect(NSString.stringWithCString(encoding).toString()).toBe("v@:"); }); - it("should project Symbol.iterable as NSFastEnumeration", function() { + it("should project Symbol.iterable as NSFastEnumeration", function () { var start = 1; var end = 10; var lastStep = 0; @@ -1881,16 +1927,16 @@ describe(module.id, function () { return { step: start, - next() { + next() { if (this.step <= end) { - return { value : this.step++, done : false }; + return { value: this.step++, done: false }; } else { - return { done : true }; + return { done: true }; } } , - return () { + return() { lastStep = this.step; return {}; } @@ -1909,7 +1955,7 @@ describe(module.id, function () { TNSClearOutput(); }); - it("Method and property with the same name", function() { + it("Method and property with the same name", function () { var JSObject = NSObject.extend({ get conflict() { return true; } }, { protocols : [ TNSPropertyMethodConflictProtocol ] }); @@ -1918,8 +1964,8 @@ describe(module.id, function () { expect(result).toBe(true); }); - - it("Should not have base class property slot", function() { + + it("Should not have base class property slot", function () { // baseMethod: is declared in the base class (TNSBaseInterface) expect(TNSDerivedInterface.prototype.hasOwnProperty('baseMethod')).toBe(false); }); diff --git a/tests/TestRunner/app/VersionDiffTests.js b/tests/TestRunner/app/VersionDiffTests.js index ad0d885a2..489f120f1 100644 --- a/tests/TestRunner/app/VersionDiffTests.js +++ b/tests/TestRunner/app/VersionDiffTests.js @@ -72,4 +72,10 @@ describe(module.id, function() { check(value, major, minor); }); }); + + it("Base class which is unavailable should be skipped", function() { + // Test case inspired from MTLArrayType(8.0) : MTLType(11.0) : NSObject + // TNSInterfaceNeverAvailableDescendant : TNSInterfaceNeverAvailable(API31.7 - skipped) : TNSInterfaceAlwaysAvailable + expect(Object.getPrototypeOf(TNSInterfaceNeverAvailableDescendant).toString()).toBe(TNSInterfaceAlwaysAvailable.toString(), "TNSInterfaceNeverAvailable base class should be skipped as it is unavailable"); + }); }); From 5bfffabaf5437ff165cae230b9e3500da80251e3 Mon Sep 17 00:00:00 2001 From: Martin Bektchiev Date: Fri, 22 Feb 2019 18:49:41 +0200 Subject: [PATCH 2/7] fix(runtime): Never check for availability of protocols Apple regularly create new protocols and move existing interface members there. E.g. iOS 12.0 introduced the UIFocusItemScrollableContainer protocol in UIKit which contained members that have existed in UIScrollView since iOS 2.0. * Create `findProtocol` function in `GlobalTable` * Use it instead of `findMeta` everwhere where a protocol is looked for * Add tests refs #1084 --- src/NativeScript/Metadata/Metadata.h | 6 +++ src/NativeScript/Metadata/Metadata.mm | 29 +++++++++++--- .../ObjC/Constructor/ObjCConstructorNative.mm | 2 +- src/NativeScript/ObjC/ObjCPrototype.mm | 2 +- tests/TestFixtures/Api/TNSVersions.h | 32 ++++++++++++++- tests/TestFixtures/Api/TNSVersions.m | 39 +++++++++++++++++++ tests/TestRunner/app/VersionDiffTests.js | 33 ++++++++++++++++ 7 files changed, 135 insertions(+), 8 deletions(-) diff --git a/src/NativeScript/Metadata/Metadata.h b/src/NativeScript/Metadata/Metadata.h index 15507455b..a1255f194 100644 --- a/src/NativeScript/Metadata/Metadata.h +++ b/src/NativeScript/Metadata/Metadata.h @@ -292,6 +292,12 @@ struct GlobalTable { const InterfaceMeta* findInterfaceMeta(const char* identifierString, size_t length, unsigned hash) const; + const ProtocolMeta* findProtocol(WTF::StringImpl* identifier) const; + + const ProtocolMeta* findProtocol(const char* identifierString) const; + + const ProtocolMeta* findProtocol(const char* identifierString, size_t length, unsigned hash) const; + const Meta* findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable = true) const; const Meta* findMeta(const char* identifierString, bool onlyIfAvailable = true) const; diff --git a/src/NativeScript/Metadata/Metadata.mm b/src/NativeScript/Metadata/Metadata.mm index 62476b954..ad716b9e4 100644 --- a/src/NativeScript/Metadata/Metadata.mm +++ b/src/NativeScript/Metadata/Metadata.mm @@ -79,6 +79,25 @@ static int compareIdentifiers(const char* nullTerminated, const char* notNullTer } } +const ProtocolMeta* GlobalTable::findProtocol(WTF::StringImpl* identifier) const { + return this->findProtocol(reinterpret_cast(identifier->characters8()), identifier->length(), identifier->hash()); +} + +const ProtocolMeta* GlobalTable::findProtocol(const char* identifierString) const { + unsigned hash = WTF::StringHasher::computeHashAndMaskTop8Bits(reinterpret_cast(identifierString)); + return this->findProtocol(identifierString, strlen(identifierString), hash); +} + +const ProtocolMeta* GlobalTable::findProtocol(const char* identifierString, size_t length, unsigned hash) const { + // Do not check for availability when returning a protocol. Apple regularly create new protocols and move + // existing interface members there (e.g. iOS 12.0 introduced the UIFocusItemScrollableContainer protocol + // in UIKit which contained members that have existed in UIScrollView since iOS 2.0) + + auto meta = this->findMeta(identifierString, length, hash, /*onlyIfAvailable*/ false); + ASSERT(!meta || meta->type() == ProtocolType); + return static_cast(meta); +} + const Meta* GlobalTable::findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable) const { return this->findMeta(reinterpret_cast(identifier->characters8()), identifier->length(), identifier->hash(), onlyIfAvailable); } @@ -186,10 +205,10 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe return result; } - // search in protcols + // search in protocols if (includeProtocols) { for (Array::iterator it = protocols->begin(); it != protocols->end(); ++it) { - const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr())); + const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr()); if (protocolMeta != nullptr) { const MembersCollection members = protocolMeta->members(identifier, length, type, onlyIfAvailable); if (members.size() > 0) { @@ -205,7 +224,7 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe std::vector BaseClassMeta::instancePropertiesWithProtocols(std::vector& container, Class klass) const { this->instanceProperties(container, klass); for (Array::iterator it = protocols->begin(); it != protocols->end(); ++it) { - const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false)); + const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr()); if (protocolMeta != nullptr) protocolMeta->instancePropertiesWithProtocols(container, klass); } @@ -215,7 +234,7 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe std::vector BaseClassMeta::staticPropertiesWithProtocols(std::vector& container, Class klass) const { this->staticProperties(container, klass); for (Array::iterator it = protocols->begin(); it != protocols->end(); ++it) { - const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false)); + const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr()); if (protocolMeta != nullptr) protocolMeta->staticPropertiesWithProtocols(container, klass); } @@ -241,7 +260,7 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe vector BaseClassMeta::initializersWithProtocols(vector& container, Class klass) const { this->initializers(container, klass); for (Array::iterator it = this->protocols->begin(); it != this->protocols->end(); it++) { - const ProtocolMeta* protocolMeta = static_cast(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false)); + const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr()); if (protocolMeta != nullptr) protocolMeta->initializersWithProtocols(container, klass); } diff --git a/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm b/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm index b7c41533f..29bf79df3 100644 --- a/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm +++ b/src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm @@ -105,7 +105,7 @@ } for (Array::iterator it = baseClassMeta->protocols->begin(); it != baseClassMeta->protocols->end(); it++) { - const ProtocolMeta* protocolMeta = (const ProtocolMeta*)MetaFile::instance()->globalTable()->findMeta((*it).valuePtr()); + const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr()); if (protocolMeta != nullptr) { baseClassMetaStack.push_back(protocolMeta); } diff --git a/src/NativeScript/ObjC/ObjCPrototype.mm b/src/NativeScript/ObjC/ObjCPrototype.mm index 64108ed1f..be8601c0b 100644 --- a/src/NativeScript/ObjC/ObjCPrototype.mm +++ b/src/NativeScript/ObjC/ObjCPrototype.mm @@ -162,7 +162,7 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) { } for (Metadata::Array::iterator it = baseClassMeta->protocols->begin(); it != baseClassMeta->protocols->end(); it++) { - const ProtocolMeta* protocolMeta = (const ProtocolMeta*)MetaFile::instance()->globalTable()->findMeta((*it).valuePtr()); + const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr()); if (protocolMeta != nullptr) baseClassMetaStack.push_back(protocolMeta); } diff --git a/tests/TestFixtures/Api/TNSVersions.h b/tests/TestFixtures/Api/TNSVersions.h index eb805e8b4..dfe66cb70 100644 --- a/tests/TestFixtures/Api/TNSVersions.h +++ b/tests/TestFixtures/Api/TNSVersions.h @@ -75,7 +75,37 @@ generateMinors(15); // max availability version that can be currently represented in the binary metadata is 31.7 (major << 3 | minor) -> uint8_t #define MAX_AVAILABILITY 31.7 -@interface TNSInterfaceAlwaysAvailable : NSObject +__attribute__((availability(ios, introduced = MAX_AVAILABILITY))) +@protocol TNSProtocolNeverAvailable + +@property(class, readonly) int staticPropertyFromProtocolNeverAvailable; +@property(class, readonly) int staticPropertyFromProtocolNeverAvailableNotImplemented; + ++ (void)staticMethodFromProtocolNeverAvailable; ++ (void)staticMethodFromProtocolNeverAvailableNotImplemented; + +@property(readonly) int propertyFromProtocolNeverAvailable; +@property(readonly) int propertyFromProtocolNeverAvailableNotImplemented; + +- (void)methodFromProtocolNeverAvailable; +- (void)methodFromProtocolNeverAvailableNotImplemented; + +@end + +__attribute__((availability(ios, introduced = 1.0))) +@protocol TNSProtocolAlwaysAvailable + +@property(class, readonly) int staticPropertyFromProtocolAlwaysAvailable; + ++ (void)staticMethodFromProtocolAlwaysAvailable; + +@property(readonly) int propertyFromProtocolAlwaysAvailable; + +- (void)methodFromProtocolAlwaysAvailable; + +@end + +@interface TNSInterfaceAlwaysAvailable : NSObject @end __attribute__((availability(ios, introduced = MAX_AVAILABILITY))) diff --git a/tests/TestFixtures/Api/TNSVersions.m b/tests/TestFixtures/Api/TNSVersions.m index d2687301c..23d99aa91 100644 --- a/tests/TestFixtures/Api/TNSVersions.m +++ b/tests/TestFixtures/Api/TNSVersions.m @@ -10,7 +10,44 @@ #import "TNSVersions.h" #undef generateVersionImpl +#pragma clang diagnostic push +// Ignore warnings about not implemented required protocol members. +// Those coming from TNSProtocolNeverAvailable are intentionally left unimplemented +#pragma clang diagnostic ignored "-Wobjc-protocol-property-synthesis" +#pragma clang diagnostic ignored "-Wobjc-property-implementation" +#pragma clang diagnostic ignored "-Wprotocol" + @implementation TNSInterfaceAlwaysAvailable + ++ (int)staticPropertyFromProtocolAlwaysAvailable { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); + return 12; +} + ++ (int)staticPropertyFromProtocolNeverAvailable { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); + return 23; +} + ++ (void)staticMethodFromProtocolAlwaysAvailable { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + ++ (void)staticMethodFromProtocolNeverAvailable { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + +@synthesize propertyFromProtocolAlwaysAvailable; +@synthesize propertyFromProtocolNeverAvailable; + +- (void)methodFromProtocolAlwaysAvailable { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + +- (void)methodFromProtocolNeverAvailable { + TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]); +} + @end @implementation TNSInterfaceNeverAvailable : TNSInterfaceAlwaysAvailable @@ -18,3 +55,5 @@ @implementation TNSInterfaceNeverAvailable : TNSInterfaceAlwaysAvailable @implementation TNSInterfaceNeverAvailableDescendant : TNSInterfaceNeverAvailable @end + +#pragma clang diagnostic pop diff --git a/tests/TestRunner/app/VersionDiffTests.js b/tests/TestRunner/app/VersionDiffTests.js index 489f120f1..0ce7ecfc5 100644 --- a/tests/TestRunner/app/VersionDiffTests.js +++ b/tests/TestRunner/app/VersionDiffTests.js @@ -78,4 +78,37 @@ describe(module.id, function() { // TNSInterfaceNeverAvailableDescendant : TNSInterfaceNeverAvailable(API31.7 - skipped) : TNSInterfaceAlwaysAvailable expect(Object.getPrototypeOf(TNSInterfaceNeverAvailableDescendant).toString()).toBe(TNSInterfaceAlwaysAvailable.toString(), "TNSInterfaceNeverAvailable base class should be skipped as it is unavailable"); }); + + it("Members of a protocol which is unavailable should be skipped only when not implemented by class", function() { + expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable)).toContain("staticPropertyFromProtocolNeverAvailable", "TNSProtocolNeverAvailable static properties that are implemented should be present although the protocol is unavailable"); + expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable)).not.toContain("staticPropertyFromProtocolNeverAvailableNotImplemented", "TNSProtocolNeverAvailable unimplemented static properties should be skipped"); + expect(TNSInterfaceAlwaysAvailable.staticMethodFromProtocolNeverAvailable).toBeDefined("TNSProtocolNeverAvailable static methods that are implemented should be present although the protocol is unavailable"); + expect(TNSInterfaceAlwaysAvailable.staticMethodFromProtocolNeverAvailableNotImplemented).toBeUndefined("TNSProtocolNeverAvailable unimplemented static methods should be skipped"); + expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable.prototype)).toContain("propertyFromProtocolNeverAvailable", "TNSProtocolNeverAvailable properties that are implemented should be present although the protocol is unavailable"); + expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable.prototype)).not.toContain("propertyFromProtocolNeverAvailableNotImplemented", "TNSProtocolNeverAvailable unimplemented properties should be skipped"); + expect(new TNSInterfaceAlwaysAvailable().methodFromProtocolNeverAvailable).toBeDefined("TNSProtocolNeverAvailable methods that are implemented should be present although the protocol is unavailable"); + expect(new TNSInterfaceAlwaysAvailable().methodFromProtocolNeverAvailableNotImplemented).toBeUndefined("TNSProtocolNeverAvailable unimplemented methods should be skipped"); + }); + + it("Members of a protocol which is available should be present", function() { + const obj = new TNSInterfaceAlwaysAvailable(); + let expectedOutput = ""; + expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable.prototype)).toContain("propertyFromProtocolAlwaysAvailable", "TNSProtocolAlwaysAvailable properties should be present as it is available"); + expect(obj.propertyFromProtocolAlwaysAvailable).toBe(0); + + expect(obj.methodFromProtocolAlwaysAvailable).toBeDefined("TNSProtocolAlwaysAvailable methods should be present as it is available"); + obj.methodFromProtocolAlwaysAvailable(); expectedOutput += "methodFromProtocolAlwaysAvailable called"; + expect(TNSGetOutput()).toBe(expectedOutput); + + TNSClearOutput(); + + expectedOutput = ""; + expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable)).toContain("staticPropertyFromProtocolAlwaysAvailable", "TNSProtocolAlwaysAvailable static properties should be present as it is available"); + TNSInterfaceAlwaysAvailable.staticPropertyFromProtocolAlwaysAvailable; expectedOutput += "staticPropertyFromProtocolAlwaysAvailable called"; + + expect(TNSInterfaceAlwaysAvailable.staticMethodFromProtocolAlwaysAvailable).toBeDefined("TNSProtocolAlwaysAvailable static methods should be present as it is available"); + TNSInterfaceAlwaysAvailable.staticMethodFromProtocolAlwaysAvailable(); expectedOutput += "staticMethodFromProtocolAlwaysAvailable called"; + + expect(TNSGetOutput()).toBe(expectedOutput); + }); }); From aa42a5ac0f1753f3e6ccbe883ae635f2eb48ab90 Mon Sep 17 00:00:00 2001 From: Martin Bektchiev Date: Fri, 1 Mar 2019 11:41:11 +0200 Subject: [PATCH 3/7] fix(runtime): Improve MethodMeta's isImplementedInClass * Ensure that the module providing the method is loaded before checking * Add check for dynamic selectors * As a last resort `alloc` an instance and send `respondsToSelector` message * Cache each class' instances to minimize the number of allocations and deallocations performed * Improve diagnostics messages in the `ApiIterator` test --- src/NativeScript/Metadata/Metadata.h | 10 +------- src/NativeScript/Metadata/Metadata.mm | 36 +++++++++++++++++++++++++++ tests/TestRunner/app/ApiTests.js | 16 ++++++------ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/NativeScript/Metadata/Metadata.h b/src/NativeScript/Metadata/Metadata.h index a1255f194..07e7bb8cf 100644 --- a/src/NativeScript/Metadata/Metadata.h +++ b/src/NativeScript/Metadata/Metadata.h @@ -693,15 +693,7 @@ struct MethodMeta : MemberMeta { return this->_constructorTokens.valuePtr(); } - bool 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; - } - - return nullptr != (isStatic ? class_getClassMethod(klass, this->selector()) : class_getInstanceMethod(klass, this->selector())); - } - + bool isImplementedInClass(Class klass, bool isStatic) const; bool isAvailableInClass(Class klass, bool isStatic) const { return this->isAvailable() && this->isImplementedInClass(klass, isStatic); } diff --git a/src/NativeScript/Metadata/Metadata.mm b/src/NativeScript/Metadata/Metadata.mm index ad716b9e4..f75dbc973 100644 --- a/src/NativeScript/Metadata/Metadata.mm +++ b/src/NativeScript/Metadata/Metadata.mm @@ -7,6 +7,7 @@ // #include "Metadata.h" +#include "SymbolLoader.h" #include #include @@ -129,6 +130,41 @@ static int compareIdentifiers(const char* nullTerminated, const char* notNullTer 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 + NativeScript::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 std::unordered_map sampleInstances; + + auto it = sampleInstances.find(klass); + if (it == sampleInstances.end()) { + it = sampleInstances.insert(std::make_pair(klass, [klass alloc])).first; + } + id sampleInstance = it->second; + return [sampleInstance respondsToSelector:this->selector()]; + } +} + // BaseClassMeta const MemberMeta* BaseClassMeta::member(const char* identifier, size_t length, MemberType type, bool includeProtocols, bool onlyIfAvailable) const { diff --git a/tests/TestRunner/app/ApiTests.js b/tests/TestRunner/app/ApiTests.js index 54e5d1e39..8e3ffff0f 100644 --- a/tests/TestRunner/app/ApiTests.js +++ b/tests/TestRunner/app/ApiTests.js @@ -222,7 +222,7 @@ describe(module.id, function () { expect(TNSGetOutput()).toBe(expectedOutput); }); - + it("TypedefPointerClass", function () { expect(TNSApi.alloc().init().strokeColor).toBeNull(); }); @@ -692,7 +692,7 @@ describe(module.id, function () { var counter = 0; Object.getOwnPropertyNames(global).forEach(function (name) { - // console.debug(name); +// console.debug(`Symbol global.${name}`); // according to SDK headers kCFAllocatorUseContext is of type id, but in fact it is not if (name == "kCFAllocatorUseContext" || @@ -715,13 +715,13 @@ describe(module.id, function () { if (NSObject.isPrototypeOf(symbol) || symbol === NSObject) { var klass = symbol; - expect(klass).toBeDefined(); + expect(klass).toBeDefined(`Class ${name} should be defined.`); - // console.debug(klass); +// console.debug(`Entering class ${klass}`); Object.getOwnPropertyNames(klass).forEach(function (y) { if (klass.respondsToSelector(y)) { - // console.debug(name, y); +// console.debug(`Checking class member ${name} . ${y}`); // supportedVideoFormats is a property and it's getter is being called the value is read below. // We skip it because it will throw "Supported video formats should be called on individual configuration class." @@ -729,7 +729,7 @@ describe(module.id, function () { return; } var method = klass[y]; - expect(method).toBeDefined(); + expect(method).toBeDefined(`Static method ${name} . ${y} should be defined.`); counter++; } @@ -737,13 +737,13 @@ describe(module.id, function () { Object.getOwnPropertyNames(klass.prototype).forEach(function (y) { if (klass.instancesRespondToSelector(y)) { - // console.debug(name, "proto", y); +// console.debug(`Checking instance member ${name} . ${y}`); var property = Object.getOwnPropertyDescriptor(klass.prototype, y); if (!property) { var method = klass.prototype[y]; - expect(method).toBeDefined(); + expect(method).toBeDefined(`Instance method -[${name} ${y}] should be defined.`); } counter++; From 2d99e2b66cc9b1faa83daa091024aacbe4a9fb21 Mon Sep 17 00:00:00 2001 From: Martin Bektchiev Date: Sat, 2 Mar 2019 08:04:30 +0200 Subject: [PATCH 4/7] fix(metadata): Gather initializers loop Do not break the loop at the first encountered unavailable initializer --- src/NativeScript/Metadata/Metadata.mm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/NativeScript/Metadata/Metadata.mm b/src/NativeScript/Metadata/Metadata.mm index f75dbc973..d027cce9c 100644 --- a/src/NativeScript/Metadata/Metadata.mm +++ b/src/NativeScript/Metadata/Metadata.mm @@ -283,11 +283,13 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe if (firstInitIndex != -1) { for (int i = firstInitIndex; i < instanceMethods->count; i++) { const MethodMeta* method = instanceMethods.value()[i].valuePtr(); - if (method->isInitializer() && method->isAvailableInClass(klass, /*isStatic*/ false)) { - container.push_back(method); - } else { + if (!method->isInitializer()) { break; } + + if (method->isAvailableInClass(klass, /*isStatic*/ false)) { + container.push_back(method); + } } } return container; From 1e9dff201cd3c2e81e06b336244c90616aef0ff5 Mon Sep 17 00:00:00 2001 From: Martin Bektchiev Date: Tue, 5 Mar 2019 09:49:39 +0200 Subject: [PATCH 5/7] feat(tests): Add test cases for initializers from protocols * Add not implemented initializers * Check that implemented initializers will be called * Check that not implemented initializers will not be used --- .../Interfaces/TNSConstructorResolution.h | 16 +++++++++++++- .../Interfaces/TNSConstructorResolution.m | 11 ++++++++++ tests/TestRunner/app/ObjCConstructors.js | 21 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/TestFixtures/Interfaces/TNSConstructorResolution.h b/tests/TestFixtures/Interfaces/TNSConstructorResolution.h index 3130dbb73..46e7832e8 100644 --- a/tests/TestFixtures/Interfaces/TNSConstructorResolution.h +++ b/tests/TestFixtures/Interfaces/TNSConstructorResolution.h @@ -11,7 +11,21 @@ typedef struct TNSCStructure { int y; } TNSCStructure; -@interface TNSCInterface : NSObject +@protocol TNSCProtocol +// Ensure we have unimplemented methods before and after +// the implemented ones (in alphabetical order) +@optional +- (id)initAWithIntNotImplemented:(int)x andInt:(int)y andInt:(int)z; + +@required +- (id)initWithInt:(int)x andInt:(int)y; + +@optional +- (id)initWithStringOptional:(NSString*)x andString:(NSString*)y; +- (id)initZWithIntNotImplemented:(int)x andInt:(int)y andInt:(int)z; +@end + +@interface TNSCInterface : NSObject - (id)initWithEmpty; - (id)initWithPrimitive:(int)x; - (id)initWithStructure:(TNSCStructure)x; diff --git a/tests/TestFixtures/Interfaces/TNSConstructorResolution.m b/tests/TestFixtures/Interfaces/TNSConstructorResolution.m index 3da2e002c..6aa985e62 100644 --- a/tests/TestFixtures/Interfaces/TNSConstructorResolution.m +++ b/tests/TestFixtures/Interfaces/TNSConstructorResolution.m @@ -38,4 +38,15 @@ - (id)initWithParameter1:(NSString*)x parameter2:(NSString*)y error:(NSError**)e } return [super init]; } + +- (id)initWithInt:(int)x andInt:(int)y { + TNSLog([NSString stringWithFormat:@"%@ %d %d called", NSStringFromSelector(_cmd), x, y]); + return [super init]; +} + +- (id)initWithStringOptional:(NSString*)x andString:(NSString*)y { + TNSLog([NSString stringWithFormat:@"%@ %@ %@ called", NSStringFromSelector(_cmd), x, y]); + return [super init]; +} + @end diff --git a/tests/TestRunner/app/ObjCConstructors.js b/tests/TestRunner/app/ObjCConstructors.js index 353246ab1..10a0c288a 100644 --- a/tests/TestRunner/app/ObjCConstructors.js +++ b/tests/TestRunner/app/ObjCConstructors.js @@ -46,6 +46,27 @@ describe("Constructing Objective-C classes with new operator", function () { expect(actual).toBe("initWithString:str calledinitWithString:str called"); }); + it("WithInt:andInt from protocol", function () { + var instance1 = new TNSCInterface(5, 10); + var instance2 = new (TNSCInterface.extend({}))(100, 500); + + var actual = TNSGetOutput(); + expect(actual).toBe("initWithInt:andInt: 5 10 calledinitWithInt:andInt: 100 500 called"); + }); + + it("WithStringOptional:andString from protocol", function () { + var instance1 = new TNSCInterface("s1", "s2"); + var instance2 = new (TNSCInterface.extend({}))("s3", "s4"); + + var actual = TNSGetOutput(); + expect(actual).toBe("initWithStringOptional:andString: s1 s2 calledinitWithStringOptional:andString: s3 s4 called"); + }); + + it("initAWithIntNotImplemented:andInt:andInt and initZWithIntNotImplemented:andInt:andInt from protocol should be missing", function () { + expect(() => new TNSCInterface(1, 2, 3)).toThrowError("No initializer found that matches constructor invocation."); + expect(() => new (TNSCInterface.extend({}))(1, 2, 3)).toThrowError("No initializer found that matches constructor invocation."); + }); + it("NSArray with JS array constructor", function () { var nsarray = new NSArray([1, 2, 3]); expect(nsarray.class()).toBe(NSArray); From fde167df1bc798309d04de79c800f89f51e07201 Mon Sep 17 00:00:00 2001 From: Martin Bektchiev Date: Tue, 5 Mar 2019 11:02:05 +0200 Subject: [PATCH 6/7] fix(runtime): Throw an Objective-C exception if NSObject is unavailable If we don't have this special treatment we enter an endless recursion. --- src/NativeScript/TypeFactory.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/NativeScript/TypeFactory.mm b/src/NativeScript/TypeFactory.mm index d6ec1348e..6816a5190 100644 --- a/src/NativeScript/TypeFactory.mm +++ b/src/NativeScript/TypeFactory.mm @@ -259,6 +259,9 @@ } if (!metadata || !klass) { + if (klassName == "NSObject") { + @throw [NSException exceptionWithName:NSGenericException reason:@"fatal error: NativeScript cannot create constructor for NSObject." userInfo:nil]; + } #ifdef DEBUG NSLog(@"** Can not create constructor for \"%@\". Casting it to \"NSObject\". **", klassName.createCFString().autorelease()); #endif From 13c04575ca45ad7a3ec192dc8ab283b36188f88c Mon Sep 17 00:00:00 2001 From: Martin Bektchiev Date: Tue, 5 Mar 2019 12:02:25 +0200 Subject: [PATCH 7/7] Address PR comments * Remove redundant `static_cast` --- src/NativeScript/Metadata/Metadata.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NativeScript/Metadata/Metadata.mm b/src/NativeScript/Metadata/Metadata.mm index d027cce9c..ba2c6574d 100644 --- a/src/NativeScript/Metadata/Metadata.mm +++ b/src/NativeScript/Metadata/Metadata.mm @@ -68,7 +68,7 @@ static int compareIdentifiers(const char* nullTerminated, const char* notNullTer if (interfaceMeta->isAvailable()) { return interfaceMeta; } else { - const char* baseName = static_cast(interfaceMeta)->baseName(); + const char* baseName = interfaceMeta->baseName(); NSLog(@"** \"%s\" introduced in iOS SDK %d.%d is currently unavailable, attempting to load its base: \"%s\". **", std::string(identifierString, length).c_str(), @@ -286,7 +286,7 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe if (!method->isInitializer()) { break; } - + if (method->isAvailableInClass(klass, /*isStatic*/ false)) { container.push_back(method); }