Add unstable_recreateSchedulerDelegate hook for lifecycle tests#56682
Open
fkgozali wants to merge 3 commits intofacebook:mainfrom
Open
Add unstable_recreateSchedulerDelegate hook for lifecycle tests#56682fkgozali wants to merge 3 commits intofacebook:mainfrom
fkgozali wants to merge 3 commits intofacebook:mainfrom
Conversation
Summary: Introduces a new common React Native feature flag, `enableSchedulerDelegateInvalidation`, that gates a defensive guard around `Scheduler::uiManagerDidDispatchCommand` and `uiManagerDidFinishTransaction`. Those call sites enqueue a lambda via `runtimeScheduler_->scheduleRenderingUpdate` that captures the raw `SchedulerDelegate` pointer by value. With `RuntimeScheduler_Modern` (bridgeless) the lambda runs asynchronously, so if the `SchedulerDelegate` is destroyed between enqueue and execution the lambda dereferences dangling memory and the process crashes (`EXC_BAD_ACCESS` / `KERN_INVALID_ADDRESS`). The follow-up commit adds the actual guard wired to this flag. Defaults to `false` so behavior is unchanged on landing. Apps opt in by overriding the flag in their own `ReactNativeFeatureFlagsDefaults` subclass. Includes the regenerated feature-flag boilerplate (Kotlin/Java/Cxx accessors, defaults, providers, JS spec, native module) emitted by `yarn featureflags --update`. Changelog: [Internal] Reviewed By: shwanton Differential Revision: D103757271
…56680) Summary: Scheduler::uiManagerDidFinishTransaction and Scheduler::uiManagerDidDispatchCommand queue lambdas via runtimeScheduler_->scheduleRenderingUpdate that capture the raw delegate_ pointer by value. With RuntimeScheduler_Modern (the bridgeless implementation), the lambda runs asynchronously, so if the SchedulerDelegate is destroyed between enqueue and execution (surface teardown, Scheduler destruction, or setDelegate flip), the lambda dereferences dangling memory → EXC_BAD_ACCESS / KERN_INVALID_ADDRESS. Add a per-delegate-identity invalidation token (`shared_ptr<atomic<bool>>`) owned by Scheduler and captured by-value into the deferred lambdas. The destructor and setDelegate flip the flag to true; lambdas check it (acquire ordering) before dereferencing the captured raw delegate. The shared_ptr keeps the atomic alive for any outstanding lambdas. Public API is unchanged (private member only), so C++ API snapshots are unaffected. Changelog: [General][Fixed] - Prevent Scheduler use-after-free crash when surfaces tear down with pending rendering updates Reviewed By: mdvacca Differential Revision: D103727974
Summary: Adds a Fantom-only escape hatch that mirrors the iOS RCTScheduler dealloc lifecycle: detach the host's current SchedulerDelegate from the Scheduler and destroy it, then install a fresh one — all while the RuntimeScheduler (and any queued rendering-update lambdas) remains alive. This is exactly the production sequence in `RCTScheduler.mm` (`setDelegate(nullptr)` followed by destruction of the previous SchedulerDelegateProxy) that the host-level Scheduler in Fantom does not otherwise reproduce — surfaces come and go, but the SchedulerDelegate normally lives for the entire test process. Without this hook, Fantom tests cannot exercise SchedulerDelegate-lifecycle bugs (e.g. queued lambdas that captured a raw delegate pointer by value). With it, a test can: ```js dispatchCommand(element, 'someCommand', []); Fantom.unstable_recreateSchedulerDelegate(); Fantom.runWorkLoop(); ``` and observe a SIGSEGV when the queued lambda dereferences the previous delegate's freed storage. Implementation notes: - `ReactHost::unstable_recreateSchedulerDelegateForTesting()` allocates the replacement delegate **before** destroying the old one, to ensure the new SchedulerDelegate lands at a distinct heap address. Without this, malloc tends to reuse the just-freed slot, masking the use-after-free behind a same-address re-allocation. - Surface area is hidden behind the `unstable_` prefix and an explicit ForTesting suffix on the underlying ReactHost method to discourage product use. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D103744935
|
@fkgozali has exported this pull request. If you are a Meta employee, you can view the originating Diff in D103744935. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Adds a Fantom-only escape hatch that mirrors the iOS RCTScheduler dealloc lifecycle: detach the host's current SchedulerDelegate from the Scheduler and destroy it, then install a fresh one — all while the RuntimeScheduler (and any queued rendering-update lambdas) remains alive. This is exactly the production sequence in
RCTScheduler.mm(setDelegate(nullptr)followed by destruction of the previous SchedulerDelegateProxy) that the host-level Scheduler in Fantom does not otherwise reproduce — surfaces come and go, but the SchedulerDelegate normally lives for the entire test process.Without this hook, Fantom tests cannot exercise SchedulerDelegate-lifecycle bugs (e.g. queued lambdas that captured a raw delegate pointer by value). With it, a test can:
and observe a SIGSEGV when the queued lambda dereferences the previous delegate's freed storage.
Implementation notes:
ReactHost::unstable_recreateSchedulerDelegateForTesting()allocates the replacement delegate before destroying the old one, to ensure the new SchedulerDelegate lands at a distinct heap address. Without this, malloc tends to reuse the just-freed slot, masking the use-after-free behind a same-address re-allocation.unstable_prefix and an explicit ForTesting suffix on the underlying ReactHost method to discourage product use.Changelog:
[Internal]
Reviewed By: mdvacca
Differential Revision: D103744935