From df4a5ff1211e51bd08ae1097272d7b2388f52ab5 Mon Sep 17 00:00:00 2001 From: Autopilot Bot Date: Mon, 29 Jun 2026 06:45:54 -0700 Subject: [PATCH] Stop crashing on inconsistent clipping bookkeeping in ReactViewGroup.isViewClipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: WARNING: Generated by Autopilot (alpha) - review carefully, verify the underlying claim before accepting. Agent: React Native Agent | Trajectory: https://www.internalfb.com/intern/devai/devmate/inspector/0ea7c6e3-35be-490c-a17f-66677f75cb75/ | SC job: https://www.internalfb.com/intern/sandcastle/instance/18014401274782879/ --- ## What `ReactViewGroup.isViewClipped` (the `removeClippedSubviews` clipping path) crashes with `java.lang.IllegalStateException: Check failed.` at the hard `check(parent === this)` in its missing-tag fallback. The crash surfaces in CrashBot via `system_vros_crashes` with the title frame `com.facebook.react.internal.tracing.PerformanceTracer.trace` (an incidental Choreographer `doFrame` wrapper); the real throw site is `ReactViewGroup.isViewClipped` ← `updateSubviewClipStatus` ← `ChildrenLayoutChangeListener.onLayoutChange`. Tracked as T277554117 (MID `system_vros_crashes/5593272fc690b1c4d240d949710c49e9`). ## Why When a child view in `allChildren` has no `view_clipped` tag, the method falls back to inferring clip state from the view's parent. The existing code already logs a `ReactNoCrashSoftException` (`RVG_IS_VIEW_CLIPPED`) for this inconsistency, signaling that the intent is graceful degradation — but it then hits `check(parent === this)`, which hard-crashes whenever the tag-less child is parented to some *other* `ViewGroup` (a reparented/stale-bookkeeping state) rather than to `this` or to no parent. This is a long-standing invariant (the bookkeeping dates to D66383241 / D66539065, Dec 2024; the `check` was carried through the Kotlin conversion in D75797215), not a new code regression — it was first observed in crash data on 2026-04-10 and only crossed the top-N FSA detector threshold on 2026-06-28 (`%SAD ≈ 0`, ~17 occurrences / 7 days). ## Fix Replace the hard `check(parent === this)` with a non-fatal classification consistent with the surrounding fallback: a view that is transitioning, has no parent, or is parented elsewhere is no longer a live child of `this`, so it is reported as clipped (`true`); only a view still parented to `this` and not transitioning is reported as not clipped (`false`). This preserves the existing behavior for every previously non-crashing path (`parent == null` / `transitioning` → `true`; `parent === this` → `false`) and converts only the previously-crashing reparented case into the same "treat as removed" outcome the soft exception already anticipates. Differential Revision: D109987865 --- .../com/facebook/react/views/view/ReactViewGroup.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt index 24009633c759..d3059e811bff 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt @@ -756,13 +756,11 @@ public open class ReactViewGroup public constructor(context: Context?) : ), ) } - // fallback - should be transitioning or have no parent if the view was removed - if (parent == null || transitioning) { - return true - } else { - check(parent === this) - return false - } + // fallback - a view that is transitioning, has no parent, or is parented elsewhere is no longer + // a live child of `this`, so it is reported as clipped. Only a view still parented to `this` + // (and not transitioning) is not clipped. The missing-tag inconsistency is already surfaced as + // a soft exception above. + return transitioning || parent !== this } private fun indexOfChildInAllChildren(child: View): Int {