Skip to content

[expo-font][iOS] OTA reload removes the active registration after a duplicate font name #50557

Description

@eliotgevers

Minimal reproducible example

https://github.com/eliotgevers/expo-font-ota-repro

Steps to reproduce

  1. Install the pinned dependencies, export the iOS bundle, and start the local update server as described in the reproduction README.
  2. Build and launch the app on an iOS simulator using expo run:ios --configuration Release.
  3. Wait for Font loaded, then tap Download and restart.

The embedded bundle loads one Inter font with useFonts, without rendering it. The downloaded bundle contains the same font and renders it after Updates.reloadAsync().

Expected: the downloaded update renders the font and stays open.

Actual: the process terminates with a null EXC_BAD_ACCESS through CFStringFind → RCTGetFontWeight → RCTFontWithFontProperties during Fabric layout. Loading without rendering before the reload is important: an existing UIKit font object can mask the registration loss.

There are no icon libraries, invalid font files, application unregister calls, or native modifications in the stock reproduction.

Source-level finding

In FontLoaderModule.loadAsync, the process-wide alias still refers to the embedded asset URL after the JS runtime restarts. The update supplies a different URL for the same PostScript name:

  1. Unregistering the incoming URL reports notRegistered (201).
  2. Registering that URL reports duplicatedName (305), because the embedded URL still owns the name. registerFont currently accepts this as success.
  3. The alias is moved to the incoming URL.
  4. Stale-URL cleanup successfully unregisters the embedded URL.
  5. Neither URL now provides a registration. Alias lookup can return nil to React Native font selection.

An instrumented run recorded this sequence and a nil UIFont for Inter-Regular. The unmodified native package reproduced the same crash independently. The same stale cleanup exists in the multi-face loading path.

A candidate change distinguishes successful/already-registered URLs from duplicate-name collisions and skips stale cleanup when the incoming registration still depends on another URL. It preserves the current behavior of accepting a duplicated name.

Related reports

Environment and verification

See the reproduction README and recorded evidence for exact package versions and controls. All runtime tests use iOS Release builds and real locally served Expo updates, not Metro refresh.

Pinned reproduction: Expo 58.0.0-preview.5, expo-font 58.0.2, expo-updates 58.0.7, React Native 0.88.0-rc.2, React 19.3.0, @expo-google-fonts/inter 0.4.2. The same stock crash was also observed on preview 4 / expo-updates 58.0.6.

Toolchain: macOS 27.0, Xcode 27.0 (27A266a), iOS 27 Simulator, Node 26.8.1, Bun, CocoaPods 1.16.2 (invoked by Expo).

Current upstream main at 80a28600623dea779f27c6e2e2a6cea4d754dc7a has the same affected font-loader and registration-helper code. The SDK 57 control was blocked before JavaScript by an unrelated iOS 27 scene-lifecycle trap; no stable-SDK font result is claimed.

Expo Doctor Diagnostics

Running 20 checks on your project...
20/20 checks passed. No issues detected!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

contributor: externalPR author is not a member of the expo GitHub orgneeds reviewIssue is ready to be reviewed by a maintainer

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions