Skip to content

Commit f907a10

Browse files
committed
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 `getOwnPropertySlot` of `ObjCPrototype`, `ObjCConstructorBase`, `ObjCConstructorNative` * Add `findInterfaceMeta` function with fallback to base interface if the desired one is unavailable in the current SDK version * Add `encodeVersion` `getMajorVersion` and `getMinorVersion` helpers * Remove hacky patch from `ObjCMethodWrapper::preInvocation` which fixed the symptoms of #978 but not its root cause * Modify fixtures and add test cases for methods availability in `InheritanceTests.js` * Add test case for unavailable base class refs #1085
1 parent e93bc8b commit f907a10

15 files changed

+254
-81
lines changed

src/NativeScript/GlobalObject.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989

9090
static Strong<ObjCProtocolWrapper> createProtocolWrapper(GlobalObject* globalObject, const ProtocolMeta* protocolMeta, Protocol* aProtocol) {
9191
Structure* prototypeStructure = ObjCPrototype::createStructure(globalObject->vm(), globalObject, globalObject->objectPrototype());
92-
auto prototype = ObjCPrototype::create(globalObject->vm(), globalObject, prototypeStructure, protocolMeta);
92+
auto prototype = ObjCPrototype::create(globalObject->vm(), globalObject, prototypeStructure, protocolMeta, nullptr);
9393
Structure* protocolWrapperStructure = ObjCProtocolWrapper::createStructure(globalObject->vm(), globalObject, globalObject->objectPrototype());
9494
auto protocolWrapper = ObjCProtocolWrapper::create(globalObject->vm(), protocolWrapperStructure, prototype.get(), protocolMeta, aProtocol);
9595
prototype->materializeProperties(globalObject->vm(), globalObject);

src/NativeScript/Metadata/Metadata.h

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ static const V& getProperFunctionFromContainer(const std::vector<V>& container,
4646
return *callee;
4747
}
4848

49+
inline UInt8 encodeVersion(UInt8 majorVersion, UInt8 minorVersion) {
50+
return (majorVersion << 3) | minorVersion;
51+
}
52+
53+
inline UInt8 getMajorVersion(UInt8 encodedVersion) {
54+
return encodedVersion >> 3;
55+
}
56+
57+
inline UInt8 getMinorVersion(UInt8 encodedVersion) {
58+
return encodedVersion & 0b111;
59+
}
60+
4961
// Bit indices in flags section
5062
enum MetaFlags {
5163
HasName = 7,
@@ -126,6 +138,7 @@ enum BinaryTypeEncodingType : Byte {
126138
template <typename T>
127139
struct PtrTo;
128140
struct Meta;
141+
struct InterfaceMeta;
129142
struct ProtocolMeta;
130143
struct ModuleMeta;
131144
struct LibraryMeta;
@@ -273,6 +286,12 @@ struct GlobalTable {
273286

274287
ArrayOfPtrTo<ArrayOfPtrTo<Meta>> buckets;
275288

289+
const InterfaceMeta* findInterfaceMeta(WTF::StringImpl* identifier) const;
290+
291+
const InterfaceMeta* findInterfaceMeta(const char* identifierString) const;
292+
293+
const InterfaceMeta* findInterfaceMeta(const char* identifierString, size_t length, unsigned hash) const;
294+
276295
const Meta* findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable = true) const;
277296

278297
const Meta* findMeta(const char* identifierString, bool onlyIfAvailable = true) const;
@@ -667,6 +686,24 @@ struct MethodMeta : MemberMeta {
667686
const char* constructorTokens() const {
668687
return this->_constructorTokens.valuePtr();
669688
}
689+
690+
bool isImplementedInClass(Class klass, bool isStatic) const {
691+
// Required methods are treated as implemented
692+
if (!this->isOptional()) {
693+
return true;
694+
}
695+
696+
// class can be null for Protocol prototypes, treat all members in a protocol as implemented
697+
if (klass == nullptr) {
698+
return true;
699+
}
700+
701+
return nullptr != (isStatic ? class_getClassMethod(klass, this->selector()) : class_getInstanceMethod(klass, this->selector()));
702+
}
703+
704+
bool isAvailableInClass(Class klass, bool isStatic) const {
705+
return this->isAvailable() && this->isImplementedInClass(klass, isStatic);
706+
}
670707
};
671708

672709
std::unordered_map<std::string, std::vector<const MemberMeta*>> getMetasByJSNames(std::vector<const MemberMeta*> methods);
@@ -691,6 +728,16 @@ struct PropertyMeta : MemberMeta {
691728
const MethodMeta* setter() const {
692729
return (this->hasSetter()) ? (this->hasGetter() ? method2.valuePtr() : method1.valuePtr()) : nullptr;
693730
}
731+
732+
bool isImplementedInClass(Class klass, bool isStatic) const {
733+
bool getterAvailable = this->hasGetter() && this->getter()->isImplementedInClass(klass, isStatic);
734+
bool setterAvailable = this->hasSetter() && this->setter()->isImplementedInClass(klass, isStatic);
735+
return !this->isOptional() || getterAvailable || setterAvailable;
736+
}
737+
738+
bool isAvailableInClass(Class klass, bool isStatic) const {
739+
return this->isAvailable() && this->isImplementedInClass(klass, isStatic);
740+
}
694741
};
695742

696743
struct BaseClassMeta : Meta {
@@ -843,9 +890,10 @@ struct InterfaceMeta : BaseClassMeta {
843890

844891
const InterfaceMeta* baseMeta() const {
845892
if (this->baseName() != nullptr) {
846-
const Meta* baseMeta = MetaFile::instance()->globalTable()->findMeta(this->baseName());
847-
return baseMeta->type() == MetaType::Interface ? reinterpret_cast<const InterfaceMeta*>(baseMeta) : nullptr;
893+
const InterfaceMeta* baseMeta = MetaFile::instance()->globalTable()->findInterfaceMeta(this->baseName());
894+
return baseMeta;
848895
}
896+
849897
return nullptr;
850898
}
851899
};

src/NativeScript/Metadata/Metadata.mm

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ static UInt8 getSystemVersion() {
2828
UInt8 majorVersion = (UInt8)[versionTokens[0] intValue];
2929
UInt8 minorVersion = (UInt8)[versionTokens[1] intValue];
3030

31-
iosVersion = (majorVersion << 3) | minorVersion;
32-
33-
return iosVersion;
31+
return encodeVersion(majorVersion, minorVersion);
3432
}
3533
std::unordered_map<std::string, std::vector<const MemberMeta*>> getMetasByJSNames(std::vector<const MemberMeta*> members) {
3634
std::unordered_map<std::string, std::vector<const MemberMeta*>> result;
@@ -45,6 +43,42 @@ static int compareIdentifiers(const char* nullTerminated, const char* notNullTer
4543
return (result == 0) ? strlen(nullTerminated) - length : result;
4644
}
4745

46+
const InterfaceMeta* GlobalTable::findInterfaceMeta(WTF::StringImpl* identifier) const {
47+
return this->findInterfaceMeta(reinterpret_cast<const char*>(identifier->characters8()), identifier->length(), identifier->hash());
48+
}
49+
50+
const InterfaceMeta* GlobalTable::findInterfaceMeta(const char* identifierString) const {
51+
unsigned hash = WTF::StringHasher::computeHashAndMaskTop8Bits<LChar>(reinterpret_cast<const LChar*>(identifierString));
52+
return this->findInterfaceMeta(identifierString, strlen(identifierString), hash);
53+
}
54+
55+
const InterfaceMeta* GlobalTable::findInterfaceMeta(const char* identifierString, size_t length, unsigned hash) const {
56+
const Meta* meta = MetaFile::instance()->globalTable()->findMeta(identifierString, length, hash, /*onlyIfAvailable*/ false);
57+
if (meta == nullptr) {
58+
return nullptr;
59+
}
60+
61+
assert(meta->type() == MetaType::Interface);
62+
if (meta->type() != MetaType::Interface) {
63+
return nullptr;
64+
}
65+
66+
const InterfaceMeta* interfaceMeta = static_cast<const InterfaceMeta*>(meta);
67+
if (interfaceMeta->isAvailable()) {
68+
return interfaceMeta;
69+
} else {
70+
const char* baseName = static_cast<const InterfaceMeta*>(interfaceMeta)->baseName();
71+
72+
NSLog(@"** \"%s\" introduced in iOS SDK %d.%d is currently unavailable, attempting to load its base: \"%s\". **",
73+
std::string(identifierString, length).c_str(),
74+
getMajorVersion(interfaceMeta->introducedIn()),
75+
getMinorVersion(interfaceMeta->introducedIn()),
76+
baseName);
77+
78+
return this->findInterfaceMeta(baseName);
79+
}
80+
}
81+
4882
const Meta* GlobalTable::findMeta(WTF::StringImpl* identifier, bool onlyIfAvailable) const {
4983
return this->findMeta(reinterpret_cast<const char*>(identifier->characters8()), identifier->length(), identifier->hash(), onlyIfAvailable);
5084
}

src/NativeScript/ObjC/Constructor/ObjCConstructorBase.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ static void writeAdapter(ExecState* execState, const JSValue& value, void* buffe
171171
do {
172172
std::vector<const Metadata::MethodMeta*> initializers = metadata->initializersWithProtcols();
173173
for (const Metadata::MethodMeta* method : initializers) {
174-
if (method->isAvailable()) {
174+
if (method->isAvailableInClass(this->klass(), /*isStatic*/ false)) {
175175
auto constructorWrapper = ObjCConstructorWrapper::create(vm, globalObject, globalObject->objCConstructorWrapperStructure(), this->_klass, method);
176176
this->_initializers.append(WriteBarrier<ObjCConstructorWrapper>(vm, this, constructorWrapper.get()));
177177
}
@@ -218,7 +218,7 @@ static JSValue getInitializerForSwiftStyleConstruction(ExecState* execState, Obj
218218
do {
219219
interface->initializersWithProtcols(initializers);
220220
for (const Metadata::MethodMeta* method : initializers) {
221-
if (method->isAvailable() && strcmp(method->constructorTokens(), ctorMetadata.data()) == 0) {
221+
if (strcmp(method->constructorTokens(), ctorMetadata.data()) == 0) {
222222
result = method;
223223
break;
224224
}

src/NativeScript/ObjC/Constructor/ObjCConstructorNative.mm

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,22 @@
9393

9494
for (ArrayOfPtrTo<MethodMeta>::iterator it = baseClassMeta->staticMethods->begin(); it != baseClassMeta->staticMethods->end(); it++) {
9595
const MethodMeta* meta = (*it).valuePtr();
96-
if (meta->isAvailable())
96+
if (meta->isAvailableInClass(constructor->klass(), /*isStatic*/ true)) {
9797
propertyNames.add(Identifier::fromString(execState, meta->jsName()));
98+
}
9899
}
99100

100101
for (Metadata::ArrayOfPtrTo<PropertyMeta>::iterator it = baseClassMeta->staticProps->begin(); it != baseClassMeta->staticProps->end(); it++) {
101-
if ((*it)->isAvailable())
102+
if ((*it)->isAvailableInClass(constructor->klass(), /*isStatic*/ true)) {
102103
propertyNames.add(Identifier::fromString(execState, (*it)->jsName()));
104+
}
103105
}
104106

105107
for (Array<Metadata::String>::iterator it = baseClassMeta->protocols->begin(); it != baseClassMeta->protocols->end(); it++) {
106108
const ProtocolMeta* protocolMeta = (const ProtocolMeta*)MetaFile::instance()->globalTable()->findMeta((*it).valuePtr());
107-
if (protocolMeta != nullptr)
109+
if (protocolMeta != nullptr) {
108110
baseClassMetaStack.push_back(protocolMeta);
111+
}
109112
}
110113
}
111114

@@ -116,11 +119,11 @@
116119
std::vector<const PropertyMeta*> properties = this->metadata()->staticPropertiesWithProtocols();
117120

118121
for (const PropertyMeta* propertyMeta : properties) {
119-
if (propertyMeta->isAvailable()) {
122+
if (propertyMeta->isAvailableInClass(this->klass(), /*isStatic*/ true)) {
120123
SymbolLoader::instance().ensureModule(propertyMeta->topLevelModule());
121124

122-
const MethodMeta* getterMeta = (propertyMeta->getter() != nullptr && propertyMeta->getter()->isAvailable()) ? propertyMeta->getter() : nullptr;
123-
const MethodMeta* setterMeta = (propertyMeta->setter() != nullptr && propertyMeta->setter()->isAvailable()) ? propertyMeta->setter() : nullptr;
125+
const MethodMeta* getterMeta = (propertyMeta->getter() != nullptr && propertyMeta->getter()->isAvailableInClass(this->klass(), /*isStatic*/ true)) ? propertyMeta->getter() : nullptr;
126+
const MethodMeta* setterMeta = (propertyMeta->setter() != nullptr && propertyMeta->setter()->isAvailableInClass(this->klass(), /*isStatic*/ true)) ? propertyMeta->setter() : nullptr;
124127

125128
PropertyDescriptor descriptor;
126129
Strong<ObjCMethodWrapper> getter;

src/NativeScript/ObjC/ObjCMethodCall.mm

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,7 @@ static bool isJavaScriptDerived(JSC::JSValue value) {
146146
bool isInstance = !class_isMetaClass(object_getClass(target));
147147
NSLog(@"> %@[%@ %@]", isInstance ? @"-" : @"+", NSStringFromClass(object_getClass(target)), NSStringFromSelector(call->_selector));
148148
#endif
149-
if (call->_isOptional && ![target respondsToSelector:call->_selector]) {
150-
// Unimplemented optional method: Silently perform a dummy call to nil object
151-
invocation.setArgument(0, nil);
152-
} else {
153-
invocation.setArgument(0, target);
154-
}
149+
invocation.setArgument(0, target);
155150
invocation.setArgument(1, call->_selector);
156151
invocation.function = call->_msgSend;
157152
}

src/NativeScript/ObjC/ObjCPrototype.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class ObjCPrototype : public JSC::JSDestructibleObject {
2323

2424
static const unsigned StructureFlags;
2525

26-
static JSC::Strong<ObjCPrototype> create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, const Metadata::BaseClassMeta* metadata) {
27-
JSC::Strong<ObjCPrototype> prototype(vm, new (NotNull, JSC::allocateCell<ObjCPrototype>(globalObject->vm().heap)) ObjCPrototype(globalObject->vm(), structure));
26+
static JSC::Strong<ObjCPrototype> create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, const Metadata::BaseClassMeta* metadata, Class klass) {
27+
JSC::Strong<ObjCPrototype> prototype(vm, new (NotNull, JSC::allocateCell<ObjCPrototype>(globalObject->vm().heap)) ObjCPrototype(globalObject->vm(), structure, klass));
2828
prototype->finishCreation(vm, globalObject, metadata);
2929
return prototype;
3030
}
@@ -39,13 +39,18 @@ class ObjCPrototype : public JSC::JSDestructibleObject {
3939

4040
void materializeProperties(JSC::VM& vm, GlobalObject* globalObject);
4141

42-
const Metadata::BaseClassMeta* metadata() {
42+
const Metadata::BaseClassMeta* metadata() const {
4343
return this->_metadata;
4444
}
4545

46+
Class klass() const {
47+
return this->_klass;
48+
}
49+
4650
private:
47-
ObjCPrototype(JSC::VM& vm, JSC::Structure* structure)
48-
: Base(vm, structure) {
51+
ObjCPrototype(JSC::VM& vm, JSC::Structure* structure, Class klass)
52+
: Base(vm, structure)
53+
, _klass(klass) {
4954
}
5055

5156
void finishCreation(JSC::VM&, JSC::JSGlobalObject*, const Metadata::BaseClassMeta*);
@@ -59,6 +64,8 @@ class ObjCPrototype : public JSC::JSDestructibleObject {
5964
static void getOwnPropertyNames(JSC::JSObject*, JSC::ExecState*, JSC::PropertyNameArray&, JSC::EnumerationMode);
6065

6166
const Metadata::BaseClassMeta* _metadata;
67+
68+
Class _klass;
6269
};
6370
} // namespace NativeScript
6471

src/NativeScript/ObjC/ObjCPrototype.mm

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) {
150150

151151
void ObjCPrototype::getOwnPropertyNames(JSObject* object, ExecState* execState, PropertyNameArray& propertyNames, EnumerationMode enumerationMode) {
152152
ObjCPrototype* prototype = jsCast<ObjCPrototype*>(object);
153+
Class klass = prototype->klass();
153154

154155
std::vector<const BaseClassMeta*> baseClassMetaStack;
155156
baseClassMetaStack.push_back(prototype->_metadata);
@@ -159,12 +160,12 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) {
159160
baseClassMetaStack.pop_back();
160161

161162
for (Metadata::ArrayOfPtrTo<MethodMeta>::iterator it = baseClassMeta->instanceMethods->begin(); it != baseClassMeta->instanceMethods->end(); it++) {
162-
if ((*it)->isAvailable())
163+
if ((*it)->isAvailableInClass(klass, /*isStatic*/ false))
163164
propertyNames.add(Identifier::fromString(execState, (*it)->jsName()));
164165
}
165166

166167
for (Metadata::ArrayOfPtrTo<PropertyMeta>::iterator it = baseClassMeta->instanceProps->begin(); it != baseClassMeta->instanceProps->end(); it++) {
167-
if ((*it)->isAvailable())
168+
if ((*it)->isAvailableInClass(prototype->klass(), /*isStatic*/ false))
168169
propertyNames.add(Identifier::fromString(execState, (*it)->jsName()));
169170
}
170171

src/NativeScript/TypeFactory.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@
247247

248248
VM& vm = globalObject->vm();
249249

250-
const InterfaceMeta* metadata = static_cast<const InterfaceMeta*>(MetaFile::instance()->globalTable()->findMeta(klassName.impl()));
250+
const InterfaceMeta* metadata = MetaFile::instance()->globalTable()->findInterfaceMeta(klassName.impl());
251251
Class klass = Nil;
252252

253253
if (metadata) {
@@ -289,7 +289,7 @@
289289
}
290290

291291
Structure* prototypeStructure = ObjCPrototype::createStructure(vm, globalObject, parentPrototype);
292-
auto prototype = ObjCPrototype::create(vm, globalObject, prototypeStructure, metadata);
292+
auto prototype = ObjCPrototype::create(vm, globalObject, prototypeStructure, metadata, klass);
293293

294294
Structure* constructorStructure = ObjCConstructorNative::createStructure(vm, globalObject, parentConstructor);
295295
auto constructor = ObjCConstructorNative::create(vm, globalObject, constructorStructure, prototype.get(), klass);

0 commit comments

Comments
 (0)