Skip to content

Don't trap on an unknown AVSpeechSynthesisVoiceGender - #649

Open
ronjb wants to merge 1 commit into
dlutton:masterfrom
ronjb:unknown-voice-gender-upstream
Open

Don't trap on an unknown AVSpeechSynthesisVoiceGender#649
ronjb wants to merge 1 commit into
dlutton:masterfrom
ronjb:unknown-voice-gender-upstream

Conversation

@ronjb

@ronjb ronjb commented Aug 12, 2026

Copy link
Copy Markdown

The crash

getVoices reads voice.gender.stringValue for every installed voice. AVSpeechSynthesisVoiceGender is an Objective-C enum, so AVSpeechSynthesisVoice.speechVoices() can hand back a voice whose gender is a case the app was not compiled against. The switch in stringValue covers only .male, .female and .unspecified and has no @unknown default, so Swift traps in _diagnoseUnexpectedEnumCaseValue and the whole app goes down.

Here is a production crash from an iPad Air 2 on iOS 15.8.8, reporting rawValue: 3:

EXC_BREAKPOINT: Fatal error: unexpected enum case 'AVSpeechSynthesisVoiceGender(rawValue: 3)'

  at SwiftFlutterTtsPlugin.getVoices (SwiftFlutterTtsPlugin.swift:346)
        voiceDict["gender"] = voice.gender.stringValue
  at AVSpeechSynthesisVoiceGender.stringValue.getter (SwiftFlutterTtsPlugin.swift:502)
      case .unspecified:
  at _diagnoseUnexpectedEnumCaseValue<T>
  at _assertionFailure

Two things make this worse than a normal bug:

  • It is not recoverable from Dart. A Swift fatalError is a process trap, not a PlatformException, so wrapping flutterTts.getVoices in try/catch does nothing.
  • It is not transient. The offending voice stays installed, so every fetch of the voice list kills the app for that user.

The fix

Add @unknown default returning "unspecified", which is already the documented fallback on the Dart side. The macOS plugin carries an identical extension and gets the same change.

Verified with swiftc -typecheck -warnings-as-errors against the iOS SDK: the enum is non-frozen, so the new case compiles clean with no "will never be executed" warning.

AVSpeechSynthesisVoiceGender is an Objective-C enum, so speechVoices() can
return a voice whose gender is a case the app was not compiled against. The
switch in stringValue covered only male, female and unspecified, so such a
voice made getVoices trap in _diagnoseUnexpectedEnumCaseValue and crash the
app.

Seen in the wild on iOS 15.8.8 with rawValue 3:

  Fatal error: unexpected enum case 'AVSpeechSynthesisVoiceGender(rawValue: 3)'
  SwiftFlutterTtsPlugin.getVoices
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant