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

Abierto
#46 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
72/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
cpp, ios, objective-c, react-native, swift
Área
mobile-dev

Línea de trabajo

Lee ios/LiquidGlassView.mm y ios/LiquidGlassView.swift, centrándote en el montaje de hijos de Fabric y en las rutas de reciclaje/desasignación. Ejecuta el bucle de estrés de iOS 26 descrito con vistas de glass interactivas y cambios frecuentes de navegación; se considera completado cuando ambas firmas de crash indicadas siguen sin aparecer y la clase fallback continúa compilando en ramas de SDK antiguas.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
Objective-C++
Estrellas
1.7k
Forks
66
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de callstack/liquid-glass

Todos los issues de callstack/liquid-glass

Issues similares

Más issues de Mobile Dev

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.