Skip to content

fix(0.83, fabric): synchronize native props across shadow node clones - #3178

Open
Jason Morse (JasonVMo) wants to merge 4 commits into
0.83/user/jasonvmo/safe-area-callbackfrom
0.83/user/jasonvmo/fabric-native-props-sync
Open

Jason Morse (JasonVMo) wants to merge 4 commits into
0.83/user/jasonvmo/safe-area-callbackfrom
0.83/user/jasonvmo/fabric-native-props-sync

Conversation

@JasonVMo

@JasonVMo Jason Morse (JasonVMo) commented Nov 12, 2026 •

Copy link
Copy Markdown

Summary:

Backport the MIT-licensed upstream implementation by OrfeasZ,
react/react-native@923e7ddcaf4d2de45ca7dedafbb84c6b87dbffc4
(react#52646), adapting only branch-specific APIs, tests and
snapshots. This resolves the missing native-props synchronization exposed by
microsoft/fluentui-react-native#4499's Fabric window-resize qualification.

  • A single family-owned mutable mutex guards all ten native-props map
    accesses: the clone reader and both UIManager writer paths.
  • Copy owned snapshots while locked; unlock before parsing props, cloning
    through descriptors, visiting registries, or committing shadow trees.
  • Preserve Ignore/Override merge precedence, null handling, and explicit
    fragment props behavior. Production logic matches upstream.
  • Port upstream FabricUIManagerTest coverage with focused merge/clone and
    bounded concurrency cases. Add a bounded real-NSWindow resize/native-props
    regression to the existing native test host.
  • Update only the snapshot file that exists on the target branch. Main/0.83
    use scripts/cxx-api; 0.81 uses private/cxx-public-api. Each snapshot adds
    exactly the new mutex declaration, not unrelated generator churn.

ABI: ShadowNodeFamily layout changes. Regenerate Pods and clean-build all
affected native libraries and C++ consumers together; do not combine new
headers with old renderer/prebuilt binaries.

Branch/package findings and release metadata

Main, 0.81-stable and 0.83-stable lack an equivalent fix. All ten published
0.81.x tarballs (0.81.0–0.81.9) and published 0.83.0 were downloaded,
integrity-verified and source-inspected; all are missing it. Upgrading to
0.83.0 does not fix this gap.

Do not duplicate future work: 0.87-merge contains the original upstream fix;
existing #3036's release head contains equivalent upstream stable backport
0b941bc51a740670470c05e08bca5898d1a6b78c.

The owner explicitly approved these incident-driven stable backports. This is
not a claim that upstream React Native still supports 0.81/0.83, or a new
macOS maintenance commitment. Metadata follows each branch: no bump record
on main; an Nx patch plan on 0.81, whose inert Changeset was removed after
review; a Changeset on 0.83.

Separate prerequisites; no PRs merged

The native test host was already broken by UIKit-only image tests and gzip
C++ linkage. Main reuses existing #3095. Separate stable repairs are #3180
(0.81) and #3179 (0.83).

Stable initializer CI also exposed an existing missing UIKit-compatible
safe-area callback. It is fixed separately in #3181/#3182; main has #3183.
No dependency, animation, assertion, resize check, or integration job was
removed to conceal either failure.

With explicit owner approval, stable PRs form native GitHub stacks:

Ordinary merge commits preserve all existing history; no force-push or PR
merge occurred. The reviewed three production-file blobs are unchanged
after integrating prerequisites.

Test Plan:

Paired native reproduction, not just ordinary test success

The same committed XCTest fixture, source hash
03915e9f4ae6e255b5682006250dd12b8e3aaf35a95cb1e8bba540aa5b9609e0,
runs against separately clean-built original and repaired 0.81 libraries:
120 owned-window resize callbacks, 3,840 native writes and 3,840 reader
clones, a fixed deadline, asserted final state, and joined worker teardown.

Instrumented native run Actual result
Original 0.81, ASan Expected failure: heap-use-after-free copying folly::dynamic in ShadowNode::clone, reached through Yoga layout, SurfaceHandler::constraintLayout, and windowDidResize:.
Repaired 0.81, ASan Passed: 1 testcase, 0 failures, no ASan diagnostic.
Original 0.81, TSan Expected failure: same family pointer read in ShadowNode::clone concurrently with replacement in UIManager::setNativeProps_DEPRECATED.
Repaired 0.81, TSan Passed: 1 testcase, 0 failures, no TSan diagnostic.

This establishes the race in the controlled RNmacOS native fixture.
It does not establish the exact JavaScript writer in the historical FURN
incident; that remains inferred. One bounded passing run is not a claim of
global race freedom.

Actual instrumented compiler response files for ShadowNode.cpp,
ShadowNodeFamily.cpp and UIManager.cpp were checked, as were linked sanitizer
runtimes and source hashes. Baseline/repaired archives are not mixed.

The existing target's pre-existing image-test compile failure initially
blocked execution. Qualification therefore also built the exact same
fixture source
as a focused XCTest bundle against the fresh matching
archives, using the existing XCTest runtime. It does not read the repository
test plan or retry failed tests.

Exact runner commands (NATIVE_PROPS_EVIDENCE denotes the retained local
artifact directory; its scripts contain full compiler/link argv):

python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" baseline081-asan address direct-host
# Expected native failure: ASan heap-use-after-free; testcase started.
python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" baseline081-tsan thread
# Expected native failure: TSan reader/writer data race; testcase started.
python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" patched081-tsan thread
# Passed: one case, no failures or race diagnostic.
bash "$NATIVE_PROPS_EVIDENCE/build-native-props-xctest.sh" "$PATCHED081_WORKTREE" "$NATIVE_PROPS_EVIDENCE/build-patched081-asan-app/Build/Products/Debug" address patched081-asan
python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" patched081-asan address
# Passed: one case, no failures or ASan diagnostic.

The first ASan launcher used the xcrun wrapper, which stripped runtime
preloading and aborted before a testcase started. Resolving and invoking
the actual XCTest executable corrected that launch error. The subsequent
native baseline failure was retained, not retried until green. Likewise,
Xcode's rejected -test-iterations 1 invocation and the early zero-test
safe-area invocation were not counted as coverage.

Existing native host, Paper, C++ and snapshots

  • Fresh main renderer/support libraries: 4/4 actual
    FabricUIManagerTest GTests pass
    , including merge and concurrency cases.
  • All three branch-specific production, GTest and native fixture syntax/API
    checks pass. Stock API generators ran; only the intended member delta was
    committed. Scoped native format/project/whitespace checks pass.
  • Proper-cwd Pods regeneration and clean native app builds pass on main,
    0.83, and original/repaired 0.81 ASan/TSan configurations.
  • Repaired Paper app clean-builds; 9 existing layout/gzip tests pass.
  • After separate host repair, the existing 0.83 XCTest target builds, links
    and executes the committed resize fixture: 1/1 passed.
  • Separate callback compatibility regressions: 2/2 modern, and
    11/11 legacy including the nine Paper regressions.

Representative exact existing-host qualification commands:

RCT_NO_LAUNCH_PACKAGER=1 xcodebuild -workspace packages/rn-tester/RNTesterPods.xcworkspace -scheme RNTester-macOS -sdk macosx -destination 'platform=macOS,arch=arm64' -configuration Debug -jobs 2 -derivedDataPath "$NATIVE_PROPS_EVIDENCE/build-patched083-clean" -resultBundlePath "$NATIVE_PROPS_EVIDENCE/results-patched083-existing-host.xcresult" CODE_SIGNING_ALLOWED=NO COMPILER_INDEX_STORE_ENABLE=NO -enableAddressSanitizer NO -enableThreadSanitizer NO -enableUndefinedBehaviorSanitizer NO -parallel-testing-enabled NO -only-testing:RNTester-macOSUnitTests/RCTFabricNativePropsTests/testWindowResizeWhileUpdatingNativeProps test-without-building
# Passed: TEST EXECUTE SUCCEEDED, 1 case.
NX_DAEMON=false yarn nx release plan:check --base=origin/0.81-stable
# Passed for the 0.81 release-plan remediation.

Ordinary native-host qualification uses an explicitly local single-run
profile. No test-plan retry/sanitizer-policy changes are committed, and no
ordinary build/unit pass is used as the race-freedom argument.

One Opus 5.5 review and remediation

Exactly one requested Opus 5.5 review found no production synchronization
correctness defects
across the three branch adapters. Its two findings
were remediated by the implementation role:

  • Unrunnable pre-existing native host: separately repaired/reused and the
    exact existing-host fixture actually executed.
  • Inert 0.81 Changeset: replaced with the branch-native Nx plan; validation
    passes. The pre-existing stable-dist-tag resolver mismatch was also corrected
    in a separate commit and checked with the actual resolver.

The original implementing sub-agent later encountered a provider error.
The owner authorized direct implementation takeover; the failed request was
not retried/rephrased and no second review was launched.

Not run: FURN's full physical/accessible suites against an upstream release
artifact, iOS/Android/device sanitizer execution, the entire legacy native
unit suite, or publication. CI status is tracked separately; no merge is
requested or performed.

FURN consumption / removing only the temporary patch

No inspected published 0.81/0.83 package contains this fix yet. FURN should
consume the next source-verified 0.81 patch artifact containing the final
#3177 head (including required prerequisites), retain its React Native peer
alignment and minimum-package-age gate, and clean-rebuild Pods, renderer and
all native consumers. Prebuilt renderer binaries must contain the same fix.

Remove only .yarn/patches/react-native-macos-npm-0.81.9-native-props.patch
and its segment of the Yarn patch chain after verifying the artifact's
family mutex/read/write implementation and rerunning PGB-006 plus the owned
physical/accessible Fabric qualification. Preserve/rebase the independent
RNmacOS patch ending in aa583a0648.patch and all other unrelated patches.

Port the merge regression from MIT-licensed react/react-native commit 923e7dd by OrfeasZ (react#52646), preserving its license headers. Add native/React precedence and placeholder clone cases, a bounded concurrent clone case, and an in-process AppKit XCTest using the real Scheduler, SurfaceHandler and UIManager. The fixture never accesses the new mutex and can run unchanged against an unpatched baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96
Backport MIT-licensed react/react-native commit 923e7dd by OrfeasZ (react#52646), preserving upstream copyright and license headers. Use one family-owned mutex for both UIManager writers and ShadowNode cloning, releasing it before parsing, descriptor callbacks and commits. Update this branch's existing public C++ API snapshot and add a patch Changeset. Rebuild all native consumers because ShadowNodeFamily layout changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96
…tory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96

# Conflicts:
#	packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
@JasonVMo
Jason Morse (JasonVMo) changed the base branch from 0.83-stable to 0.83/user/jasonvmo/safe-area-callback November 13, 2026 01:50
@JasonVMo
Jason Morse (JasonVMo) marked this pull request as ready for review November 13, 2026 01:53
@JasonVMo
Jason Morse (JasonVMo) requested a review from a team as a code owner November 13, 2026 01:53
…istory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96
@JasonVMo

Copy link
Copy Markdown
Author

CI follow-up: after the separate safe-area blocker was fixed, the 0.83
generated-app job reached an existing Swift template error:

immutable value 'self.reactNativeFactory' may only be initialized once
immutable value 'self.reactNativeDelegate' may only be initialized once
property 'self.reactNativeDelegate' not initialized at super.init call

The template's two immutable stored properties were assigned after
NSObject.init. Keep them immutable and move only super.init() after
their assignments. This is a separate initializer prerequisite, not a change
to the reviewed renderer mutex implementation.

The exact template class, compiled with lightweight dependency interfaces,
reproduces all three errors before the fix and compiles after it. An initial
swiftc -typecheck did not exercise definite-initialization analysis and was
not counted as validation; actual xcrun swiftc -c ... -o /dev/null - did.
The full generated application remains covered by the hosted init job.

Main prerequisite commit: eee401ea2f in #3183.
0.83 prerequisite commit: a1de6d1000 in #3182.
The existing 0.83 top #3178 incorporates it by ordinary merge
bf859537d83abfdbeb4fb3b563295d2a24a56612; stack 3185 and its complete
membership are preserved. No force-push or PR merge occurred. No retry,
assertion, dependency, sanitizer, or integration job was removed.

@JasonVMo

Copy link
Copy Markdown
Author

Final-base CI run 43353951532 completed successfully at bf85953: 21 jobs passed, no failed jobs, including the generated-app init job that exposed the separate safe-area and Swift template defects. Both defects were fixed in prerequisite commits rather than hidden by retries or weakened checks. Stack 3185 remains #3179 -> #3182 -> #3178, ultimate 0.83-stable, with all PRs open and unmerged. The reviewed renderer production blobs are unchanged. Main run43348256429 and 0.81 final-base run43348256508 also passed. No force-push, PR merge, publication, or FURN patch removal occurred.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant