Skip to content

fix(android): soft-fail missing view in SurfaceMountingManager mount ops - #59773

Closed
VimukthiShohan wants to merge 2 commits into
react:mainfrom
VimukthiShohan:fix/android-soft-fail-overflow-inset-padding
Closed

VimukthiShohan wants to merge 2 commits into
react:mainfrom
VimukthiShohan:fix/android-soft-fail-overflow-inset-padding

Conversation

@VimukthiShohan

@VimukthiShohan VimukthiShohan commented Nov 3, 2026 •

Copy link
Copy Markdown

Summary:

A Fabric ViewState can exist without an Android View: createViewUnsafe creates one only for layoutable
nodes, and updateEventEmitter registers a bare placeholder for a tag it has not seen (see #58526).
When the mount instruction stream then sends updateOverflowInset, updatePadding, removeViewAt or
updateState for such a tag, each method hits a checkNotNull and throws, which tears down the whole surface:

java.lang.IllegalStateException: Unable to find View for tag: <tag>
    at com.facebook.react.fabric.mounting.SurfaceMountingManager.updateOverflowInset(SurfaceMountingManager.kt)

We see this crash in production on Android (Play Store builds, RN 0.87.1). All four methods already handle a
missing ViewState one branch above the assert by logging a ReactNoCrashSoftException under
SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE and returning. This PR uses the same pattern when the
ViewState exists but has no view. #58527 makes the same change for addViewAt.

  • removeViewAt: Fix IllegalStateException in SurfaceMountingManager.removeViewAt (#57860) #57860 (landed as 3f553d7) soft-failed the case where the parent view is not a ViewGroup,
    but left the checkNotNull(parentView) just above it. A parent ViewState with no view still throws
    Unable to find parentView for tag. This PR soft-fails that case too.
  • updateState: the method stores the new state wrapper before the asserts. On the new early return it keeps
    doing so and still calls prevStateWrapper?.destroyState(), so the previous C++ State is freed right away
    instead of waiting for GC.

Could this be picked into 0.87-stable? Apps on prebuilt AARs cannot patch it locally.

Changelog:

[ANDROID] [FIXED] - Log a soft exception instead of crashing when updateOverflowInset, updatePadding, removeViewAt or updateState targets a ViewState with no view

Test Plan:

Four Robolectric tests were added to SurfaceMountingManagerTest:
updateOverflowInset_doesNotThrowWhenViewStateHasNoView, updatePadding_doesNotThrowWhenViewStateHasNoView,
removeViewAt_doesNotThrowWhenParentViewStateHasNoView and updateState_doesNotThrowWhenViewStateHasNoView.
Each one preallocates tag 42 with isLayoutable = false, so the ViewState is registered but has no view and
no view manager, and then calls the method (removeViewAt(43, 42, 0) uses 42 as the parent).
A fifth test, updateState_destroysPreviousStateWhenViewStateHasNoView, calls updateState twice on the
same tag with mock StateWrappers and verifies that the first one is destroyed on the early return.

./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "*SurfaceMountingManagerTest*"

Each fix was checked test-first. Every test failed at its own assert before its fix and passed after it.

updateOverflowInset and updatePadding (first commit), tests only:

SurfaceMountingManagerTest > updateOverflowInset_doesNotThrowWhenViewStateHasNoView FAILED
    java.lang.IllegalStateException: Unable to find View for tag: 42
        at com.facebook.react.fabric.mounting.SurfaceMountingManager.updateOverflowInset(SurfaceMountingManager.kt:925)
SurfaceMountingManagerTest > updatePadding_doesNotThrowWhenViewStateHasNoView FAILED
    java.lang.IllegalStateException: Unable to find View for tag: 42
        at com.facebook.react.fabric.mounting.SurfaceMountingManager.updatePadding(SurfaceMountingManager.kt:889)
11 tests completed, 2 failed

removeViewAt and updateState (second commit), tests only:

SurfaceMountingManagerTest > removeViewAt_doesNotThrowWhenParentViewStateHasNoView FAILED
    java.lang.IllegalStateException: Unable to find parentView for tag 42
        at com.facebook.react.fabric.mounting.SurfaceMountingManager.removeViewAt(SurfaceMountingManager.kt:428)
SurfaceMountingManagerTest > updateState_doesNotThrowWhenViewStateHasNoView FAILED
    java.lang.IllegalStateException: Unable to find ViewManager for tag: 42
        at com.facebook.react.fabric.mounting.SurfaceMountingManager.updateState(SurfaceMountingManager.kt:972)
13 tests completed, 2 failed

With only removeViewAt fixed, the updateState test still fails at its own assert:

SurfaceMountingManagerTest > updateState_doesNotThrowWhenViewStateHasNoView FAILED
    java.lang.IllegalStateException: Unable to find ViewManager for tag: 42
        at com.facebook.react.fabric.mounting.SurfaceMountingManager.updateState(SurfaceMountingManager.kt:978)
13 tests completed, 1 failed

With the prevStateWrapper?.destroyState() line removed from the updateState early return, the leak test
fails at its verify:

SurfaceMountingManagerTest > updateState_destroysPreviousStateWhenViewStateHasNoView FAILED
    org.mockito.exceptions.verification.WantedButNotInvoked: Wanted but not invoked: stateWrapper.destroyState();
        at com.facebook.react.fabric.SurfaceMountingManagerTest.updateState_destroysPreviousStateWhenViewStateHasNoView(SurfaceMountingManagerTest.kt:264)
14 tests completed, 1 failed

With all four fixes:

<testsuite name="com.facebook.react.fabric.SurfaceMountingManagerTest" tests="14" skipped="0" failures="0" errors="0">
BUILD SUCCESSFUL

ktfmt --dry-run --set-exit-if-changed passes on both changed files.

…ePadding

A ViewState can exist without an Android View (createViewUnsafe only creates
one for layoutable nodes, and updateEventEmitter registers bare placeholders).
updateOverflowInset and updatePadding then hit checkNotNull and throw
"Unable to find View for tag", tearing down the whole surface. Log a
ReactNoCrashSoftException and return instead, matching the missing-viewState
branch right above each assert.

Adds Robolectric tests for both methods.
@meta-cla

meta-cla Bot commented Nov 3, 2026

Copy link
Copy Markdown

Hi @VimukthiShohan!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

Same ViewState-without-View case as updateOverflowInset and updatePadding.
removeViewAt hit checkNotNull on the parent view ("Unable to find parentView
for tag") and updateState on the ViewManager, both throwing and tearing down
the surface. Log a ReactNoCrashSoftException and return instead.

updateState keeps storing the new state wrapper and still destroys the
previous one on the early return, so the C++ State is freed immediately.

Adds Robolectric tests for both methods, plus one that verifies the replaced
state wrapper is destroyed on the updateState early return.
@VimukthiShohan VimukthiShohan changed the title fix(android): soft-fail missing view in updateOverflowInset and updatePadding fix(android): soft-fail missing view in SurfaceMountingManager mount ops Nov 3, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 3, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Nov 3, 2026
@javache

javache commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

We should not be suppressing this error, but identifying the root cause. Have you got a reliable repro for this?

Non-layoutable nodes should never receive children, the path where createView does not allocate one is only used for text nodes iirc.

@VimukthiShohan

VimukthiShohan commented Nov 4, 2026 •

Copy link
Copy Markdown
Author

No reliable repro, unfortunately. It is a production-only crash: 4 users / 18 events on one release (RN 0.87.1, Galaxy S23 Ultra(Android 17) and Samsung A56(Android 16)), always ~20 s after launch when a login screen mounts with the keyboard open. Three Maestro/manual variants on an Android 17 emulator produced zero Fabric soft exceptions.

Agreed on non-layoutable nodes: I re-read the dispatcher and updateOverflowInset is only emitted for non-virtual mutations, so the crashing tag was a real view-bearing node. The only way I can find for its ViewState to have no view is the one FabricMountingManager.cpp already flags: an Update for a tag that is no longer in allocatedViewTags ("Emitting update for unallocated view", logged but still dispatched). If that batch carries an event-emitter change, updateEventEmitter synthesises a placeholder ViewState via getOrPut, and the next batch's overflow-inset update trips checkNotNull. That is a differ ordering fault, not something this PR fixes.

I can't yet prove that from the field: the crash message carries only the tag. Two options, your call:

  1. Reframe this PR as diagnostic: keep the hard failure but include the ViewState in the message (as addViewAt does), so the next crash report says whether it is a placeholder (props: null - viewManager: null) or a created-but-viewless node.
  2. Close it, and I come back with data. Our next release registers a ReactSoftExceptionLogger listener that attaches the preceding soft exceptions (e.g. Unable to find viewState for tag N for updateLayout) as breadcrumbs, which would identify the deleted tag and the batch sequence.

Happy to do either.

@javache

javache commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Let's come back to this once we have more info.

@javache javache closed this Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants