From b660c0f2fa391594e6b7faf1b5313ea005436a95 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Tue, 28 Apr 2026 10:17:23 -0700 Subject: [PATCH] Fix LayoutAnimation crash from mutation sort ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: `LayoutAnimationDriver::animationMutationsForFrame` was sorting animation-frame Update mutations together with structural mutations (Remove/Insert/Delete) from completed animations. Animation-frame Updates reference views by their pre-structural-change parentTag. When `std::stable_sort` reordered these Updates after Remove/Insert pairs that reparented a view, `StubViewTree::mutate` hit a parentTag mismatch assertion (`react_native_assert(oldStubView->parentTag == mutation.parentTag)`) and crashed with SIGABRT. The fix records the boundary between animation-frame Updates and final structural mutations, then only sorts the structural portion. This preserves the invariant that animation-frame Updates execute before any structural changes. Per zeyap's review: simplified to just the LayoutAnimationDriver.cpp change. The earlier MutationComparator (`Update < Insert`) tweak and the `TEST_F` + feature-flag test refactor were dropped — the driver-side fix alone unbreaks the failing test. Changelog: [Internal] Differential Revision: D102696875 --- .../renderer/animations/LayoutAnimationDriver.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp b/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp index 5b634bfba162..5317ecc5890e 100644 --- a/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animations/LayoutAnimationDriver.cpp @@ -74,6 +74,10 @@ void LayoutAnimationDriver::animationMutationsForFrame( } } + // Record boundary: animation-frame Updates above this point carry + // parentTag from before any structural changes and must execute first. + auto animationUpdatesEnd = mutationsList.size(); + // Clear out finished animations for (auto it = inflightAnimations_.begin(); it != inflightAnimations_.end();) { @@ -99,10 +103,11 @@ void LayoutAnimationDriver::animationMutationsForFrame( } } - // Final step: make sure that all operations execute in the proper order. - // REMOVE operations with highest indices must operate first. + // Sort only the final mutations from completed animations. + // Animation-frame Updates must stay before structural mutations because + // they reference views by their pre-structural-change parentTag. std::stable_sort( - mutationsList.begin(), + mutationsList.begin() + static_cast(animationUpdatesEnd), mutationsList.end(), &shouldFirstComeBeforeSecondMutation); }