Mobile Development 13 min read

Investigation and Resolution of a Ghost Crash in Xianyu's Flutter Infrastructure

After a 2018 Flutter upgrade caused a mysterious “Ghost Crash” in Xianyu’s app, engineers traced an over‑released NSMutableArray in the accessibility bridge, fixed an illegal dealloc call, added proper checks for iOS Speak Screen, and eliminated the crash through a rapid gray‑scale rollout.

Xianyu Technology
Xianyu Technology
Xianyu Technology
Investigation and Resolution of a Ghost Crash in Xianyu's Flutter Infrastructure

The Xianyu Flutter infrastructure was considered stable until a sudden crash, dubbed the "Ghost Crash," appeared after a large‑scale upgrade in 2018. The crash rate spiked dramatically, prompting the formation of a dedicated investigation team.

Initial analysis of the crash log revealed a typical over‑released (wild pointer) issue, but the exact offending object was unclear due to ARC obscuring the problem. The log showed a stack trace dominated by CoreFoundation and UIKit calls, ending with a reference to Runner main .

Because the crash could not be reproduced on developer devices, the team employed several strategies:

Reviewed recent code changes for memory‑related bugs.

Implemented a GCD thread‑tracking technique using __builtin_return_address and fishhook to capture the dispatch site of blocks.

Enabled online NSZombie monitoring to capture deallocated objects.

Used SwiftMonkey UI automation combined with a local Flutter engine to reproduce the crash on a simulator.

The automation run captured an identical crash log, pointing to an autorelease NSMutableArray in accessibility_bridge.mm (line 533). The relevant code snippet is:

NSMutableArray *newChildren = [[[NSMutableArray alloc] initWithCapacity:newChildCount] autorelease];
for (NSUInteger i = 0; i < newChildCount; ++i) {
    SemanticsObject *child = GetOrCreateObject(node.childrenInTraversalOrder[i], nodes);
    child.parent = object;
    [newChildren addObject:child];
}
object.children = newChildren;

Further inspection of the SemanticsObject dealloc method uncovered an illegal direct call to [_children dealloc] instead of a proper release , a classic MRC pitfall:

- (void)dealloc {
    for (SemanticsObject *child in _children) {
        child.parent = nil;
    }
    [_children removeAllObjects];
    [_children dealloc]; // <-- should be release
    _parent = nil;
    [_container release];
    _container = nil;
    [super dealloc];
}

Additional investigation revealed that the crash only occurred when iOS Accessibility (Speak Screen) was enabled. On real devices, this feature is disabled by default, but some users had it turned on, causing the Flutter accessibility bridge to be active and triggering the faulty code path.

After confirming the root cause, a quick fix was applied: replace the illegal [_children dealloc] with a proper [_children release] and guard the accessibility initialization with the correct runtime checks. The fix was rolled out in a gray‑scale experiment, which immediately reduced the crash rate to normal levels.

Subsequent review showed that the upstream Flutter master branch had already merged a similar fix, but Xianyu's custom engine had not been synchronized. The issue was fully resolved, and the investigation demonstrated the importance of:

Combining log analysis, runtime monitoring, and automated UI testing.

Close communication with affected users.

Understanding platform‑specific features (iOS accessibility) that can expose hidden bugs.

debuggingflutteriOSaccessibilitycrash analysismemory management
Xianyu Technology
Written by

Xianyu Technology

Official account of the Xianyu technology team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.