Skip to content

[objective_c] Debug builds hang on iOS 27: assert-only _isValidClass calls objc_copyClassList, which deadlocks in realizeAllClasses() #3753

Closed

Description

@LaurentRisser

Summary

In debug builds, _isValidClass (lib/src/internal.dart) rebuilds its class cache with objc_copyClassList whenever a class is missing from _allClasses. That call realizes every Objective-C class in the process on the calling thread (the main thread here). On iOS 27 it never returns: the app freezes during startup, before its first screen. On iOS 26 the same scan completes after about 90 seconds of blank screen. Profile and release builds are unaffected because the check runs only under assert.

Versions

  • objective_c 9.5.0 (via path_provider_foundation 2.6.0). The check is unchanged in 9.6.2.
  • Flutter 3.41.8 stable, Xcode 27.1, iOS 27.0 simulator (iPhone 18 Pro). Also reproduced as a ~90 s stall on an iOS 26.0 simulator.

Evidence

sample of the hung process, 100 s after launch, 0% CPU, main thread in every one of 3,699 samples:

fml::MessageLoopImpl::FlushTasks
tonic::DartMicrotaskQueue::RunMicrotasks()
Dart_InvokeClosure
dart::DartEntry::InvokeFunction
objc_copyClassList  (libobjc.A.dylib)
realizeAllClasses()  (libobjc.A.dylib)
realizeClassMaybeSwiftMaybeRelock(...)  (libobjc.A.dylib)
???  (in AppIntentsServices)

The 0% CPU means it is waiting, not working: realizing a class from AppIntentsServices appears to block while the main thread is inside the call (which is bound with isLeaf: true). We have not identified what it waits on.

Workarounds we found

  • Debug on an iOS 26 runtime, or profile/release on iOS 27.
  • --dart-flags=--no-enable-asserts is refused by the engine's allowlist, so asserts cannot be turned off for one app.

Suggestions

  • Avoid objc_copyClassList in the validity check (for example validate with object_getClass / class_getName on the specific pointer, or objc_lookUpClass by name), or
  • Do not realize all classes on the main thread, or let apps opt out of the debug check.

Activity

  1. liamappelbe commented on Oct 30, 2026

    @liamappelbe
    Contributor

    The reason it's written that way is that the function has to work even if the class pointer is not valid. If we call class_getName on an invalid class pointer, it's undefined behavior (and would likely crash), defeating the purpose of this function. But object_getClass already crashes if the object is not valid, so this check needs to be removed or rewritten.

    I'll look into whether we can do a simpler sanity check of the pointer. Otherwise we should just remove this.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions