Skip to content

Add unstable_recreateSchedulerDelegate hook for lifecycle tests#56682

Open
fkgozali wants to merge 3 commits intofacebook:mainfrom
fkgozali:export-D103744935
Open

Add unstable_recreateSchedulerDelegate hook for lifecycle tests#56682
fkgozali wants to merge 3 commits intofacebook:mainfrom
fkgozali:export-D103744935

Conversation

@fkgozali
Copy link
Copy Markdown
Contributor

@fkgozali fkgozali commented May 5, 2026

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:

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 added 3 commits May 4, 2026 19:36
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
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 5, 2026
@facebook-github-tools facebook-github-tools Bot added p: Facebook Partner: Facebook Partner labels May 5, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented May 5, 2026

@fkgozali has exported this pull request. If you are a Meta employee, you can view the originating Diff in D103744935.

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

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. fb-exported meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant