Skip to content

Commit 2295794

Browse files
committed
fix: prefer JS subclass property accessors
1 parent 3b7a492 commit 2295794

3 files changed

Lines changed: 74 additions & 5 deletions

File tree

NativeScript/ffi/shared/bridge/HostObjects.mm

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,16 +1814,14 @@ throw JSError(
18141814
// methods); defer so the engine resolves them instead of the bridge
18151815
// returning a registered getter IMP as a raw callable.
18161816
if (isEngineExtendedInstance) {
1817-
#ifdef NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE
1818-
// Engines whose exotic property handler invokes prototype accessors with
1819-
// the wrong receiver need the JS-prototype getter resolved here with this
1820-
// instance as the receiver.
1817+
// Prefer JS prototype accessors before falling back to runtime ObjC
1818+
// getters; otherwise an ObjC getter implemented by the JS subclass can
1819+
// re-enter the same JS accessor recursively.
18211820
bool found = false;
18221821
Value resolved = resolveEnginePrototypeGetter(runtime, property, &found);
18231822
if (found) {
18241823
return resolved;
18251824
}
1826-
#endif
18271825
if (auto selector =
18281826
runtimeReadablePropertyGetter(object_, property)) {
18291827
return callObjectSelector(runtime, *selector, nullptr, nullptr, 0);

PROGRESS.md

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

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

20+
### 2026-06-30 01:44 EDT - JS subclass getters prefer prototype accessors before ObjC runtime getters
21+
22+
- Scope check:
23+
- Still on the RN-module branch, still fixing generic NativeScript runtime
24+
behavior exposed by RN module work, and still not touching the original
25+
`refactor` direct-engine backend goal.
26+
- Simulator-only rule remains active; no physical devices were used.
27+
- CI finding:
28+
- GitHub Actions run `28421961031` on `3b7a492c` kept macOS green and no
29+
longer timed out on iOS.
30+
- The previous iOS blocker passed:
31+
`ApiTests.js NSMutableArrayMethods => passed`.
32+
- The iOS suite completed with one remaining failure:
33+
`ApiTests.js SpecialCaseProperty_When_CustomSelector_ImplementedInJS`.
34+
- The failure output was a long repetition of `getter`, showing that a native
35+
runtime property getter was re-entering the JS override instead of serving
36+
the JS prototype accessor directly.
37+
- Change:
38+
- JS-extended object property reads now resolve JS prototype getters before
39+
falling back to runtime Objective-C property getters on every backend.
40+
- This keeps JS-owned accessors in JS and prevents an ObjC getter implemented
41+
by the same JS subclass from recursively re-entering the accessor.
42+
- Source coverage guards the tracked runtime bridge and the RN mirror.
43+
- Verification:
44+
- Focused construction/property guard test passed:
45+
`node packages/react-native/test/runtime-instance-selector-base-dispatch.test.js`.
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+
- Existing generated macOS project compile/link passed with
51+
`xcodebuild -project dist/intermediates/macos/NativeScript.xcodeproj ...`.
52+
- Full local `npm run test:ios` remains blocked by local host tooling:
53+
`scripts/build_metadata_generator.sh` tries to link an x86_64 metadata
54+
generator against an arm64-only Xcode libclang on this machine. CI remains
55+
the simulator source of truth for iOS.
56+
- Still next:
57+
- Commit/push this prototype-getter precedence fix and watch fresh PR CI.
58+
Expected result: macOS remains green and iOS clears the last
59+
`SpecialCaseProperty_When_CustomSelector_ImplementedInJS` failure.
60+
2061
### 2026-06-30 01:13 EDT - guard JS callbacks during subclass construction
2162

2263
- Scope check:

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,36 @@ for (const relativePath of [
5656
);
5757
}
5858

59+
for (const relativePath of [
60+
"NativeScript/ffi/shared/bridge/HostObjects.mm",
61+
"packages/react-native/native-api/ffi/shared/bridge/HostObjects.mm",
62+
]) {
63+
const source = fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
64+
const branchIndex = source.indexOf("if (isEngineExtendedInstance) {");
65+
const resolveIndex = source.indexOf(
66+
"Value resolved = resolveEnginePrototypeGetter(runtime, property, &found);",
67+
branchIndex,
68+
);
69+
const nativeGetterIndex = source.indexOf(
70+
"runtimeReadablePropertyGetter(object_, property)",
71+
branchIndex,
72+
);
73+
const guardedIndex = source.lastIndexOf(
74+
"#ifdef NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE",
75+
resolveIndex,
76+
);
77+
78+
assert(
79+
branchIndex !== -1 &&
80+
resolveIndex !== -1 &&
81+
nativeGetterIndex !== -1 &&
82+
branchIndex < resolveIndex &&
83+
resolveIndex < nativeGetterIndex &&
84+
guardedIndex < branchIndex,
85+
`${relativePath}: JS-subclassed property reads should prefer prototype getters before native runtime getters on every backend`,
86+
);
87+
}
88+
5989
for (const relativePath of [
6090
"NativeScript/ffi/shared/bridge/Callbacks.mm",
6191
"packages/react-native/native-api/ffi/shared/bridge/Callbacks.mm",

0 commit comments

Comments
 (0)