Skip to content

Commit 5bfffab

Browse files
committed
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
1 parent 850e5e4 commit 5bfffab

File tree

7 files changed

+135
-8
lines changed

7 files changed

+135
-8
lines changed

src/NativeScript/Metadata/Metadata.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ struct GlobalTable {
292292

293293
const InterfaceMeta* findInterfaceMeta(const char* identifierString, size_t length, unsigned hash) const;
294294

295+
const ProtocolMeta* findProtocol(WTF::StringImpl* identifier) const;
296+
297+
const ProtocolMeta* findProtocol(const char* identifierString) const;
298+
299+
const ProtocolMeta* findProtocol(const char* identifierString, size_t length, unsigned hash) const;
300+
295301
const Meta* findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable = true) const;
296302

297303
const Meta* findMeta(const char* identifierString, bool onlyIfAvailable = true) const;

src/NativeScript/Metadata/Metadata.mm

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,25 @@ static int compareIdentifiers(const char* nullTerminated, const char* notNullTer
7979
}
8080
}
8181

82+
const ProtocolMeta* GlobalTable::findProtocol(WTF::StringImpl* identifier) const {
83+
return this->findProtocol(reinterpret_cast<const char*>(identifier->characters8()), identifier->length(), identifier->hash());
84+
}
85+
86+
const ProtocolMeta* GlobalTable::findProtocol(const char* identifierString) const {
87+
unsigned hash = WTF::StringHasher::computeHashAndMaskTop8Bits<LChar>(reinterpret_cast<const LChar*>(identifierString));
88+
return this->findProtocol(identifierString, strlen(identifierString), hash);
89+
}
90+
91+
const ProtocolMeta* GlobalTable::findProtocol(const char* identifierString, size_t length, unsigned hash) const {
92+
// Do not check for availability when returning a protocol. Apple regularly create new protocols and move
93+
// existing interface members there (e.g. iOS 12.0 introduced the UIFocusItemScrollableContainer protocol
94+
// in UIKit which contained members that have existed in UIScrollView since iOS 2.0)
95+
96+
auto meta = this->findMeta(identifierString, length, hash, /*onlyIfAvailable*/ false);
97+
ASSERT(!meta || meta->type() == ProtocolType);
98+
return static_cast<const ProtocolMeta*>(meta);
99+
}
100+
82101
const Meta* GlobalTable::findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable) const {
83102
return this->findMeta(reinterpret_cast<const char*>(identifier->characters8()), identifier->length(), identifier->hash(), onlyIfAvailable);
84103
}
@@ -186,10 +205,10 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe
186205
return result;
187206
}
188207

189-
// search in protcols
208+
// search in protocols
190209
if (includeProtocols) {
191210
for (Array<String>::iterator it = protocols->begin(); it != protocols->end(); ++it) {
192-
const ProtocolMeta* protocolMeta = static_cast<const ProtocolMeta*>(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr()));
211+
const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr());
193212
if (protocolMeta != nullptr) {
194213
const MembersCollection members = protocolMeta->members(identifier, length, type, onlyIfAvailable);
195214
if (members.size() > 0) {
@@ -205,7 +224,7 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe
205224
std::vector<const PropertyMeta*> BaseClassMeta::instancePropertiesWithProtocols(std::vector<const PropertyMeta*>& container, Class klass) const {
206225
this->instanceProperties(container, klass);
207226
for (Array<String>::iterator it = protocols->begin(); it != protocols->end(); ++it) {
208-
const ProtocolMeta* protocolMeta = static_cast<const ProtocolMeta*>(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false));
227+
const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr());
209228
if (protocolMeta != nullptr)
210229
protocolMeta->instancePropertiesWithProtocols(container, klass);
211230
}
@@ -215,7 +234,7 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe
215234
std::vector<const PropertyMeta*> BaseClassMeta::staticPropertiesWithProtocols(std::vector<const PropertyMeta*>& container, Class klass) const {
216235
this->staticProperties(container, klass);
217236
for (Array<String>::iterator it = protocols->begin(); it != protocols->end(); ++it) {
218-
const ProtocolMeta* protocolMeta = static_cast<const ProtocolMeta*>(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false));
237+
const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr());
219238
if (protocolMeta != nullptr)
220239
protocolMeta->staticPropertiesWithProtocols(container, klass);
221240
}
@@ -241,7 +260,7 @@ void collectInheritanceChainMembers(const char* identifier, size_t length, Membe
241260
vector<const MethodMeta*> BaseClassMeta::initializersWithProtocols(vector<const MethodMeta*>& container, Class klass) const {
242261
this->initializers(container, klass);
243262
for (Array<String>::iterator it = this->protocols->begin(); it != this->protocols->end(); it++) {
244-
const ProtocolMeta* protocolMeta = static_cast<const ProtocolMeta*>(MetaFile::instance()->globalTable()->findMeta((*it).valuePtr(), false));
263+
const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr());
245264
if (protocolMeta != nullptr)
246265
protocolMeta->initializersWithProtocols(container, klass);
247266
}

src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
}
106106

107107
for (Array<Metadata::String>::iterator it = baseClassMeta->protocols->begin(); it != baseClassMeta->protocols->end(); it++) {
108-
const ProtocolMeta* protocolMeta = (const ProtocolMeta*)MetaFile::instance()->globalTable()->findMeta((*it).valuePtr());
108+
const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr());
109109
if (protocolMeta != nullptr) {
110110
baseClassMetaStack.push_back(protocolMeta);
111111
}

src/NativeScript/ObjC/ObjCPrototype.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) {
162162
}
163163

164164
for (Metadata::Array<Metadata::String>::iterator it = baseClassMeta->protocols->begin(); it != baseClassMeta->protocols->end(); it++) {
165-
const ProtocolMeta* protocolMeta = (const ProtocolMeta*)MetaFile::instance()->globalTable()->findMeta((*it).valuePtr());
165+
const ProtocolMeta* protocolMeta = MetaFile::instance()->globalTable()->findProtocol((*it).valuePtr());
166166
if (protocolMeta != nullptr)
167167
baseClassMetaStack.push_back(protocolMeta);
168168
}

tests/TestFixtures/Api/TNSVersions.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,37 @@ generateMinors(15);
7575
// max availability version that can be currently represented in the binary metadata is 31.7 (major << 3 | minor) -> uint8_t
7676
#define MAX_AVAILABILITY 31.7
7777

78-
@interface TNSInterfaceAlwaysAvailable : NSObject
78+
__attribute__((availability(ios, introduced = MAX_AVAILABILITY)))
79+
@protocol TNSProtocolNeverAvailable<NSObject>
80+
81+
@property(class, readonly) int staticPropertyFromProtocolNeverAvailable;
82+
@property(class, readonly) int staticPropertyFromProtocolNeverAvailableNotImplemented;
83+
84+
+ (void)staticMethodFromProtocolNeverAvailable;
85+
+ (void)staticMethodFromProtocolNeverAvailableNotImplemented;
86+
87+
@property(readonly) int propertyFromProtocolNeverAvailable;
88+
@property(readonly) int propertyFromProtocolNeverAvailableNotImplemented;
89+
90+
- (void)methodFromProtocolNeverAvailable;
91+
- (void)methodFromProtocolNeverAvailableNotImplemented;
92+
93+
@end
94+
95+
__attribute__((availability(ios, introduced = 1.0)))
96+
@protocol TNSProtocolAlwaysAvailable<NSObject>
97+
98+
@property(class, readonly) int staticPropertyFromProtocolAlwaysAvailable;
99+
100+
+ (void)staticMethodFromProtocolAlwaysAvailable;
101+
102+
@property(readonly) int propertyFromProtocolAlwaysAvailable;
103+
104+
- (void)methodFromProtocolAlwaysAvailable;
105+
106+
@end
107+
108+
@interface TNSInterfaceAlwaysAvailable : NSObject <TNSProtocolNeverAvailable, TNSProtocolAlwaysAvailable>
79109
@end
80110

81111
__attribute__((availability(ios, introduced = MAX_AVAILABILITY)))

tests/TestFixtures/Api/TNSVersions.m

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,50 @@
1010
#import "TNSVersions.h"
1111
#undef generateVersionImpl
1212

13+
#pragma clang diagnostic push
14+
// Ignore warnings about not implemented required protocol members.
15+
// Those coming from TNSProtocolNeverAvailable are intentionally left unimplemented
16+
#pragma clang diagnostic ignored "-Wobjc-protocol-property-synthesis"
17+
#pragma clang diagnostic ignored "-Wobjc-property-implementation"
18+
#pragma clang diagnostic ignored "-Wprotocol"
19+
1320
@implementation TNSInterfaceAlwaysAvailable
21+
22+
+ (int)staticPropertyFromProtocolAlwaysAvailable {
23+
TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]);
24+
return 12;
25+
}
26+
27+
+ (int)staticPropertyFromProtocolNeverAvailable {
28+
TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]);
29+
return 23;
30+
}
31+
32+
+ (void)staticMethodFromProtocolAlwaysAvailable {
33+
TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]);
34+
}
35+
36+
+ (void)staticMethodFromProtocolNeverAvailable {
37+
TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]);
38+
}
39+
40+
@synthesize propertyFromProtocolAlwaysAvailable;
41+
@synthesize propertyFromProtocolNeverAvailable;
42+
43+
- (void)methodFromProtocolAlwaysAvailable {
44+
TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]);
45+
}
46+
47+
- (void)methodFromProtocolNeverAvailable {
48+
TNSLog([NSString stringWithFormat:@"%@ called", NSStringFromSelector(_cmd)]);
49+
}
50+
1451
@end
1552

1653
@implementation TNSInterfaceNeverAvailable : TNSInterfaceAlwaysAvailable
1754
@end
1855

1956
@implementation TNSInterfaceNeverAvailableDescendant : TNSInterfaceNeverAvailable
2057
@end
58+
59+
#pragma clang diagnostic pop

tests/TestRunner/app/VersionDiffTests.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,37 @@ describe(module.id, function() {
7878
// TNSInterfaceNeverAvailableDescendant : TNSInterfaceNeverAvailable(API31.7 - skipped) : TNSInterfaceAlwaysAvailable
7979
expect(Object.getPrototypeOf(TNSInterfaceNeverAvailableDescendant).toString()).toBe(TNSInterfaceAlwaysAvailable.toString(), "TNSInterfaceNeverAvailable base class should be skipped as it is unavailable");
8080
});
81+
82+
it("Members of a protocol which is unavailable should be skipped only when not implemented by class", function() {
83+
expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable)).toContain("staticPropertyFromProtocolNeverAvailable", "TNSProtocolNeverAvailable static properties that are implemented should be present although the protocol is unavailable");
84+
expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable)).not.toContain("staticPropertyFromProtocolNeverAvailableNotImplemented", "TNSProtocolNeverAvailable unimplemented static properties should be skipped");
85+
expect(TNSInterfaceAlwaysAvailable.staticMethodFromProtocolNeverAvailable).toBeDefined("TNSProtocolNeverAvailable static methods that are implemented should be present although the protocol is unavailable");
86+
expect(TNSInterfaceAlwaysAvailable.staticMethodFromProtocolNeverAvailableNotImplemented).toBeUndefined("TNSProtocolNeverAvailable unimplemented static methods should be skipped");
87+
expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable.prototype)).toContain("propertyFromProtocolNeverAvailable", "TNSProtocolNeverAvailable properties that are implemented should be present although the protocol is unavailable");
88+
expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable.prototype)).not.toContain("propertyFromProtocolNeverAvailableNotImplemented", "TNSProtocolNeverAvailable unimplemented properties should be skipped");
89+
expect(new TNSInterfaceAlwaysAvailable().methodFromProtocolNeverAvailable).toBeDefined("TNSProtocolNeverAvailable methods that are implemented should be present although the protocol is unavailable");
90+
expect(new TNSInterfaceAlwaysAvailable().methodFromProtocolNeverAvailableNotImplemented).toBeUndefined("TNSProtocolNeverAvailable unimplemented methods should be skipped");
91+
});
92+
93+
it("Members of a protocol which is available should be present", function() {
94+
const obj = new TNSInterfaceAlwaysAvailable();
95+
let expectedOutput = "";
96+
expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable.prototype)).toContain("propertyFromProtocolAlwaysAvailable", "TNSProtocolAlwaysAvailable properties should be present as it is available");
97+
expect(obj.propertyFromProtocolAlwaysAvailable).toBe(0);
98+
99+
expect(obj.methodFromProtocolAlwaysAvailable).toBeDefined("TNSProtocolAlwaysAvailable methods should be present as it is available");
100+
obj.methodFromProtocolAlwaysAvailable(); expectedOutput += "methodFromProtocolAlwaysAvailable called";
101+
expect(TNSGetOutput()).toBe(expectedOutput);
102+
103+
TNSClearOutput();
104+
105+
expectedOutput = "";
106+
expect(Object.getOwnPropertyNames(TNSInterfaceAlwaysAvailable)).toContain("staticPropertyFromProtocolAlwaysAvailable", "TNSProtocolAlwaysAvailable static properties should be present as it is available");
107+
TNSInterfaceAlwaysAvailable.staticPropertyFromProtocolAlwaysAvailable; expectedOutput += "staticPropertyFromProtocolAlwaysAvailable called";
108+
109+
expect(TNSInterfaceAlwaysAvailable.staticMethodFromProtocolAlwaysAvailable).toBeDefined("TNSProtocolAlwaysAvailable static methods should be present as it is available");
110+
TNSInterfaceAlwaysAvailable.staticMethodFromProtocolAlwaysAvailable(); expectedOutput += "staticMethodFromProtocolAlwaysAvailable called";
111+
112+
expect(TNSGetOutput()).toBe(expectedOutput);
113+
});
81114
});

0 commit comments

Comments
 (0)