iOS 26: Fabric index corruption + dangling pointer-interaction registration cause use-after-free crashes

Aperta
#46 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
cpp, ios, objective-c, react-native, swift
Ambito
mobile-dev

Direzione di ricerca

Leggi ios/LiquidGlassView.mm e ios/LiquidGlassView.swift, concentrandoti sul montaggio dei figli di Fabric e sui percorsi di riciclo/deallocazione. Esegui il ciclo di stress descritto per iOS 26 con viste glass interattive e frequenti cambi di navigazione; il lavoro è completato quando entrambe le firme di crash segnalate continuano a non comparire e la classe fallback continua a compilare sui branch di SDK meno recenti.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

On iOS 26, apps using LiquidGlassView (especially with interactive) crash sporadically with memory-corruption signatures during React Native (Fabric) mount/unmount transactions. We root-caused two distinct mechanisms in the library's iOS implementation and validated fixes in production-style stress testing. Happy to send a PR if you agree with the approach.

Environment

  • @callstack/liquid-glass 0.8.0
  • react-native 0.81.4 (New Architecture / Fabric)
  • iOS 26.5 (simulator) and iOS 26.6 (device, field crashes)

Crash signatures observed

  1. EXC_BAD_ACCESS in -[_UIPointerInteractionAssistant _assistantForView:] / -[UIView(UIPointerEventsPrivate) _containsView:] while RCTMountingManager performTransaction mounts a child view (use-after-free — the assistant registry enumerates a freed view).
  2. NSInvalidArgumentException: -[__NSArrayM insertSublayer:atIndex:]: unrecognized selector (and OS_xpc_* / CALayerArray variants) during mounting — a freed CALayer's memory reused by an unrelated object, then messaged as a layer.

Both reproduce with rapid navigation churn across screens containing glass views (open/close product-detail screens with glass buttons/badges, tab switches). In our app this crashed roughly 1 in 3 sessions of ~100 navigations.

Root causes

1. Fabric children are mounted directly into UIVisualEffectView.contentView
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
  [_view.contentView insertSubview:childComponentView atIndex:index];
}

Fabric assumes it exclusively owns the index space of the container it mounts into. On iOS 26, UIKit's glass/pointer machinery injects private subviews (e.g. _UIPointerEffectPlatterView) into the effect view hierarchy at runtime. Once an injected view lands in contentView, every Fabric index is off by one: subsequent insert/remove transactions operate on the wrong subviews, detaching views the framework still references — producing the freed-layer insertSublayer crashes above.

2. Live glass effect (and its pointer-interaction registration) survives recycle/dealloc

With isInteractive = true, UIGlassEffect registers the view with UIKit's pointer-interaction system. The component view never tears the effect down:

  • when Fabric recycles the component view (prepareForRecycle), the pooled instance keeps its live effect and registration;
  • when the view deallocates, the registration is never removed while the view is alive.

The next pointer-interaction registry walk (which UIKit performs during ordinary _addSubview passes) messages the freed view — the _UIPointerInteractionAssistant crash above.

Fix we validated

  1. Mount Fabric children into a dedicated plain UIView container inside contentView (autoresized to its bounds). UIKit can then inject anything into the effect hierarchy without disturbing Fabric's index space.
  2. Add a resetForRecycle() on LiquidGlassViewImpl that sets effect = nil and clears cached state; call it from prepareForRecycle and dealloc so UIKit unregisters the interactive-glass pointer machinery while the view is still alive.

Patch (applied via patch-package on 0.8.0):

--- a/ios/LiquidGlassView.mm
+++ b/ios/LiquidGlassView.mm
@@
 @implementation LiquidGlassView {
   LiquidGlassViewImpl * _view;
+  UIView * _childContainer;
   BOOL _needsInvalidateLayer;
 }
@@
     _view = [[LiquidGlassViewImpl alloc] init];
-    
+
+    _childContainer = [[UIView alloc] initWithFrame:CGRectZero];
+    _childContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    [_view.contentView addSubview:_childContainer];
+
     self.contentView = _view;
   }
   
   return self;
 }
 
+- (void)dealloc
+{
+  [_view resetForRecycle];
+}
+
@@
 - (void)layoutSubviews {
   [super layoutSubviews];
   _view.layer.cornerRadius = self.layer.cornerRadius;
   _view.layer.cornerCurve = self.layer.cornerCurve;
+  _childContainer.frame = _view.contentView.bounds;
 }
+
+- (void)prepareForRecycle
+{
+  [super prepareForRecycle];
+  [_view resetForRecycle];
+}
@@
 - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
-  [_view.contentView insertSubview:childComponentView atIndex:index];
+  [_childContainer insertSubview:childComponentView atIndex:index];
 }
--- a/ios/LiquidGlassView.swift
+++ b/ios/LiquidGlassView.swift
@@
+  @objc public func resetForRecycle() {
+    self.effect = nil
+    appliedStyle = nil
+    appliedTintColor = nil
+    appliedInteractive = nil
+    isFirstMount = true
+    effectTintColor = nil
+    interactive = false
+    style = .regular
+    animated = true
+    animationDuration = 0
+    hasAnimationDuration = false
+  }

(Plus an empty resetForRecycle() on the non-iOS-26 fallback class so the ObjC call compiles on older SDK branches.)

Validation

Automated stress loop on iOS 26.5 simulator (Release configuration): repeated product-screen open/close over interactive glass buttons + tab switches, 10 runs x 15 navigation cycles. Before: crashes in ~1/3 of runs (both signatures). After: zero occurrences of either signature.

Related: facebook/react-native#55489 describes the same _UIPointerInteractionAssistant use-after-free pattern from the pointer-interaction side.

Lingua principale
Objective-C++
Stelle
1.7k
Fork
66
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di callstack/liquid-glass

Tutte le issue di callstack/liquid-glass

Issue simili

Altre issue su Mobile Dev

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.