Skip to content

Commit 59a756f

Browse files
committed
fix: preserve init receiver native identity
1 parent ca53096 commit 59a756f

4 files changed

Lines changed: 60 additions & 2 deletions

File tree

NativeScript/ffi/shared/bridge/ClassBuilder.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ void rememberNativeApiKnownExposedMethod(
119119
}
120120

121121
Value preserved(runtime, receiverValue);
122-
bridge->rememberRoundTripValue(runtime, receiver, preserved);
122+
bridge->rememberNativeObjectRoundTripValue(runtime, receiver, preserved);
123123
return preserved;
124124
}
125125

NativeScript/ffi/shared/bridge/ObjCBridge.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,13 @@ void rememberRoundTripValue(Runtime& runtime, const void* native,
674674
#endif
675675
}
676676

677+
void rememberNativeObjectRoundTripValue(Runtime& runtime, id object,
678+
const Value& value,
679+
bool stringLikeNative = false) {
680+
rememberRoundTripValue(runtime, object, value, stringLikeNative,
681+
nativeObjectClassKey(object));
682+
}
683+
677684
void rememberScopedRoundTripValue(Runtime& runtime, const void* native,
678685
const Value& value,
679686
bool stringLikeNative = false,

PROGRESS.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,43 @@ TypeScript/UI worklets.
1717

1818
## Latest Update - 2026-06-30
1919

20+
### 2026-06-30 00:32 EDT - preserved init receivers use native-object round-trip keys
21+
22+
- Scope check:
23+
- This is still generic NativeScript JS-subclass construction/runtime identity
24+
work for PR #46. It is not React Native Screens-specific and does not touch
25+
the original `refactor` branch's direct-engine backend goal.
26+
- Simulator-only rule remains active; no physical devices were used.
27+
- CI finding:
28+
- GitHub Actions run `28419724124` on `ca530965` built successfully but failed
29+
macOS tests before iOS ran.
30+
- The single failure was
31+
`InheritanceTests.js ExposeVariadicSelector`: a native call into a
32+
JS-exposed selector returned a wrapper for the same native object, but not
33+
the identical JS object expected by `toBe(object)`.
34+
- The alloc/init constructor path exposed that initializer-preserved wrappers
35+
were being stored in the round-trip cache without the native-object class
36+
validation key used by ordinary native return marshalling.
37+
- Change:
38+
- Added `NativeApiBridge::rememberNativeObjectRoundTripValue(...)`, which
39+
stores persistent native-object wrapper identity with the same validation key
40+
that `makeNativeObjectValue`/native return conversion later requires.
41+
- `preservedNativeApiInitializerSelfReturn` now uses that helper when
42+
re-establishing the original JS receiver after native `init` returns `self`.
43+
- Source coverage guards that preserved initializer receivers use the
44+
native-object round-trip path rather than the unvalidated generic path.
45+
- Verification:
46+
- Runtime RN JS tests passed:
47+
`for f in packages/react-native/test/*.test.js; do node "$f" || exit 1; done`.
48+
- `git diff --check` passed.
49+
- `npm run check:ffi-boundaries` passed.
50+
- `npm run build:macos-cli` passed.
51+
- Still next:
52+
- Commit/push this identity-cache correction and watch fresh PR CI. macOS
53+
should get past `InheritanceTests.js ExposeVariadicSelector`; iOS then needs
54+
to prove whether the zero-argument alloc/init constructor path fixes
55+
`ApiTests.js NSMutableArrayMethods`.
56+
2057
### 2026-06-30 00:10 EDT - JS subclasses use remembered alloc/init construction
2158

2259
- Scope check:

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ for (const relativePath of [
5353
source.includes("NativeApiObjectHostObject::nativeObjectFromValue(runtime, result)") &&
5454
source.includes("resultHostObject != receiverHostObject") &&
5555
source.includes("detachObjectPreservingBridgeState(receiver)") &&
56-
source.includes("bridge->rememberRoundTripValue(runtime, receiver,") &&
56+
source.includes("bridge->rememberNativeObjectRoundTripValue(runtime, receiver,") &&
5757
source.includes("return preserved;"),
5858
`${relativePath}: initializer dispatch should preserve the original JS receiver when native init returns self`,
5959
);
@@ -64,6 +64,20 @@ for (const relativePath of [
6464
);
6565
}
6666

67+
for (const relativePath of [
68+
"NativeScript/ffi/shared/bridge/ObjCBridge.mm",
69+
"packages/react-native/native-api/ffi/shared/bridge/ObjCBridge.mm",
70+
]) {
71+
const source = fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
72+
73+
assert(
74+
source.includes("void rememberNativeObjectRoundTripValue(Runtime& runtime, id object,") &&
75+
source.includes("rememberRoundTripValue(runtime, object, value, stringLikeNative,") &&
76+
source.includes("nativeObjectClassKey(object));"),
77+
`${relativePath}: preserved native object wrappers should use the same validation key as native return marshalling`,
78+
);
79+
}
80+
6781
for (const relativePath of [
6882
"NativeScript/ffi/jsc/NativeApiJSCSelectorGroups.mm",
6983
"NativeScript/ffi/v8/NativeApiV8SelectorGroups.mm",

0 commit comments

Comments
 (0)