Skip to content

Commit 20dbc6b

Browse files
committed
fix: preserve receiver identity for base initializers
1 parent c201b4c commit 20dbc6b

2 files changed

Lines changed: 58 additions & 8 deletions

File tree

NativeScript/ffi/shared/bridge/ClassBuilder.mm

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,31 @@ void rememberNativeApiKnownExposedMethod(
8080
return std::nullopt;
8181
}
8282

83+
Value callNativeApiBaseObjectSelector(
84+
Runtime& runtime, const std::shared_ptr<NativeApiBridge>& bridge,
85+
const Object& receiverObject,
86+
const std::shared_ptr<NativeApiObjectHostObject>& receiverHostObject,
87+
id receiver, const std::string& selectorName,
88+
const NativeApiMember* member, const Value* args, size_t count,
89+
Class dispatchClass) {
90+
Value result = receiverHostObject->callObjectSelector(
91+
runtime, selectorName, member, args, count, dispatchClass);
92+
93+
if (selectorName.rfind("init", 0) != 0 || receiver == nil) {
94+
return result;
95+
}
96+
97+
id resultObject =
98+
NativeApiObjectHostObject::nativeObjectFromValue(runtime, result);
99+
if (resultObject != receiver) {
100+
return result;
101+
}
102+
103+
bridge->rememberRoundTripValue(runtime, receiver,
104+
Value(runtime, receiverObject));
105+
return Value(runtime, receiverObject);
106+
}
107+
83108
const char* nativeApiEngineFastEnumerationEncoding() {
84109
static const char* encoding = nullptr;
85110
if (encoding == nullptr) {
@@ -825,8 +850,9 @@ throw JSError(
825850
if (actualArgc == 0) {
826851
Class dispatchClass =
827852
dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass);
828-
return receiverHostObject->callObjectSelector(
829-
runtime, propertyMember->selectorName, propertyMember, nullptr, 0,
853+
return callNativeApiBaseObjectSelector(
854+
runtime, bridge, receiverObject, receiverHostObject, receiver,
855+
propertyMember->selectorName, propertyMember, nullptr, 0,
830856
dispatchClass);
831857
}
832858
if (actualArgc == 1 && !propertyMember->setterSelectorName.empty() &&
@@ -836,9 +862,10 @@ throw JSError(
836862
NativeApiMember setterMember = *propertyMember;
837863
setterMember.selectorName = propertyMember->setterSelectorName;
838864
setterMember.signatureOffset = propertyMember->setterSignatureOffset;
839-
return receiverHostObject->callObjectSelector(
840-
runtime, setterMember.selectorName, &setterMember, args + 3,
841-
actualArgc, dispatchClass);
865+
return callNativeApiBaseObjectSelector(
866+
runtime, bridge, receiverObject, receiverHostObject, receiver,
867+
setterMember.selectorName, &setterMember, args + 3, actualArgc,
868+
dispatchClass);
842869
}
843870
}
844871
}
@@ -849,7 +876,7 @@ throw JSError(
849876

850877
Class dispatchClass =
851878
dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass);
852-
return receiverHostObject->callObjectSelector(runtime, member->selectorName,
853-
member, args + 3, actualArgc,
854-
dispatchClass);
879+
return callNativeApiBaseObjectSelector(
880+
runtime, bridge, receiverObject, receiverHostObject, receiver,
881+
member->selectorName, member, args + 3, actualArgc, dispatchClass);
855882
}

packages/react-native/test/runtime-instance-selector-base-dispatch.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,27 @@ for (const relativePath of [
2727
);
2828
}
2929

30+
for (const relativePath of [
31+
"NativeScript/ffi/shared/bridge/ClassBuilder.mm",
32+
"packages/react-native/native-api/ffi/shared/bridge/ClassBuilder.mm",
33+
]) {
34+
const source = fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
35+
36+
assert(
37+
source.includes("Value callNativeApiBaseObjectSelector("),
38+
`${relativePath}: base dispatch should go through the receiver-preserving helper`,
39+
);
40+
assert(
41+
source.includes('selectorName.rfind("init", 0) != 0') &&
42+
source.includes("NativeApiObjectHostObject::nativeObjectFromValue(runtime, result)") &&
43+
source.includes("bridge->rememberRoundTripValue(runtime, receiver,") &&
44+
source.includes("return Value(runtime, receiverObject);"),
45+
`${relativePath}: base initializer dispatch should preserve receiver wrapper identity when init returns self`,
46+
);
47+
assert(
48+
source.includes("return callNativeApiBaseObjectSelector("),
49+
`${relativePath}: __invokeBase should use receiver-preserving selector calls`,
50+
);
51+
}
52+
3053
console.log("runtime instance selector base dispatch tests passed");

0 commit comments

Comments
 (0)