react / react/react-native

[Android][Fabric] addViewAt hard-crashes on a placeholder ViewState synthesised by updateEventEmitter

オープン 初心者向け
#58,526 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Needs: Attention Needs: Repro
主要言語
C++
スター
127k
フォーク
25.3k
平均マージ
1日 23時間
マージ済み PR(30日)
4

説明

Description

On Android, SurfaceMountingManager.addViewAt throws a fatal IllegalStateException on a ViewState whose view is null:

java.lang.IllegalStateException: Unable to find view for viewState
  ViewState [3548] - isRoot: false - props: null - viewManager: null - isLayoutOnly: true and tag 3548

The ViewState was not created by createViewUnsafe. It was synthesised by updateEventEmitter, because an UPDATE EVENTEMITTER mutation for that tag arrived before any Create for it.

That placeholder is what makes this fatal. addViewAt already handles a missing ViewState with a soft exception and an early return (line 322), but the placeholder makes the lookup succeed, so the guard is skipped and checkNotNull(view) at line 330 tears down the surface.

props: null identifies the construction site uniquely. There are exactly three ViewState(...) construction sites in SurfaceMountingManager.kt on 0.87.1:

Line Site Would print
134 root view isRoot: true
590 createViewUnsafe props: non-null: currentProps = propMap is assigned unconditionally, before the isLayoutable branch
978 updateEventEmitter, tagToViewState.getOrPut(reactTag) { ViewState(reactTag) } isRoot: false - props: null - viewManager: null, view null

Only line 978 matches. And nothing nulls these fields after creation: across the file the only assignments are view/viewManager at 134 and 607 to 609, and currentProps at 591, 666 and 668. So this is not a view-recycling or delete-path artefact, and it is not an ordinary flattened node either, since a flattened node still goes through createViewUnsafe and therefore still has currentProps.

The getOrPut at 978 is intentional (it carries the T62717437 TODO about virtual nodes needing event emitters without a view). The problem is only that the placeholder it leaves behind is indistinguishable from a real ViewState at every later lookup.

Steps to reproduce

I do not have an app-level deterministic reproducer: this is a production crash, and the underlying mutation ordering (an UPDATE EVENTEMITTER reaching a tag with no Create) is a race I cannot trigger on demand. #58265 reports what appears to be the same missing-Create cause.

The failure itself is deterministic and can be driven against SurfaceMountingManager directly, without reproducing the race:

  1. Start a surface and mount a parent ViewGroup tag normally.
  2. Call updateEventEmitter(tag) for a tag that has had no createView call. This registers a bare ViewState(tag) via the getOrPut at line 978.
  3. Call addViewAt(parentTag, tag, 0) for that same tag.
  4. The getNullableViewState(tag) check at line 321 succeeds, the soft-exception early return is skipped, and checkNotNull(view) at line 330 throws.

Step 3 is exactly what the mount batch does when the Create is missing, so whether or not the race that drops the Create is fixed separately, this branch should degrade rather than kill the surface.

React Native Version

0.87.1

Affected Platforms

Runtime - Android

Areas

Fabric - The New Renderer

Output of npx @react-native-community/cli info
System:
  OS: macOS 26.5.2
  CPU: (10) arm64 Apple M2 Pro
  Memory: 192.73 MB / 16.00 GB
Binaries:
  Node: 24.16.0
  npm: 11.13.0
  Watchman: 2026.07.27.00
Managers:
  CocoaPods: 1.17.0
IDEs:
  Android Studio: 2025.3 AI-253.30387.90.2532.14935130
Languages:
  Java: 21.0.9
npmPackages:
  react: 19.2.8
  react-native: 0.87.1
Android:
  hermesEnabled: true
  newArchEnabled: true

Crashing device: HONOR REA-NX9, Android 15, arm64. Production build, main thread.

Stacktrace or Logs
java.lang.IllegalStateException: Unable to find view for viewState ViewState [3548] - isRoot: false - props: null - viewManager: null - isLayoutOnly: true and tag 3548
  at com.facebook.react.fabric.mounting.SurfaceMountingManager.addViewAt (SurfaceMountingManager.kt:330)
  at com.facebook.react.fabric.mounting.mountitems.IntBufferBatchMountItem.execute (IntBufferBatchMountItem.kt:122)
  at com.facebook.react.fabric.mounting.MountItemDispatcher.executeOrEnqueue (MountItemDispatcher.kt:379)
  at com.facebook.react.fabric.mounting.MountItemDispatcher.dispatchMountItems$lambda$7$lambda$6 (MountItemDispatcher.kt:269)
  at com.facebook.react.internal.tracing.PerformanceTracer.trace (PerformanceTracer.java:46)
  at com.facebook.react.fabric.mounting.MountItemDispatcher.dispatchMountItems (MountItemDispatcher.kt:250)
  at com.facebook.react.fabric.mounting.MountItemDispatcher.tryDispatchMountItems (MountItemDispatcher.kt:94)
  at com.facebook.react.fabric.FabricUIManager$DispatchUIFrameCallback.doFrameGuarded (FabricUIManager.java:1622)
  at com.facebook.react.uimanager.GuardedFrameCallback.doFrame (GuardedFrameCallback.kt:42)
  at com.facebook.react.modules.core.ReactChoreographer.frameCallback$lambda$1 (ReactChoreographer.java:58)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:2329)
  at android.view.Choreographer.doCallbacks (Choreographer.java:1499)
  at android.os.Handler.handleCallback (Handler.java:997)
Reproducer

No public reproducer app, for the reason given under Steps to reproduce. The proposed fix is defensive and does not depend on reproducing the race:

val view = viewState.view
if (view == null) {
  ReactSoftExceptionLogger.logSoftException(
      ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE,
      ReactNoCrashSoftException("Unable to find view for viewState for tag: [$tag] for addViewAt"),
  )
  return
}

This matches the recovery pattern already used in this file, and the one taken in #56389 and #57181 for their branches.

Screenshots and Videos

Not applicable.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

SurfaceMountingManager.kt の addViewAt から始め、missing-ViewState の処理を updateEventEmitter によって作成されるプレースホルダーと比較します。説明されている updateEventEmitter/addViewAt のシーケンスを再現し、missing view が surface をクラッシュさせずに処理されることを確認します。完了時には、既存の soft-exception からの復旧動作を維持してください。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
android, kotlin, react-native
領域
mobile
issue の種類
バグ
難易度
2/5
見積もり時間
1〜3時間
活発さ
活発
明瞭さ
明確に書かれている
初心者へのやさしさ
85/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。