Repository navigation
Description
Steps to reproduce
Not reliably reproducible on demand — this is from a production (TestFlight) crash report on macOS. Observed conditions:
- A macOS Flutter app with a focused text input, using a third-party Chinese IME (Tencent WeType,
com.tencent.inputmethod.wetype). - Marked (composing) text is active or was recently active.
- The user switches the input source (the crashing stack goes through
-[IMKClient_Modern switchedInputMode:completionHandler:]), which makes the IME deliversetMarkedText:selectedRange:replacementRange:via XPC. - If the engine's
TextInputModelcomposing range is out of sync with the actual text buffer at that moment (frameworkTextInput.setEditingStateracing with the IME's TSM event chain), the engine performs an out-of-boundsstd::u16string::replaceand the process aborts.
Expected results
Text input state is clamped/validated; a desynced composing range must never abort the whole process.
Actual results
SIGABRT on the main thread. std::out_of_range is thrown inside flutter::TextInputModel::UpdateComposingText and never caught, so libc++ calls abort().
Root cause
engine/src/flutter/shell/platform/common/text_input_model.cc (line 80 on engine revision 42d3d75a56, unchanged on current master as published on api.flutter.dev):
void TextInputModel::UpdateComposingText(const std::u16string& text,
const TextRange& selection) {
// Preserve selection if we get a no-op update to the composing region.
if (text.length() == 0 && composing_range_.collapsed()) {
return;
}
const TextRange& rangeToDelete =
composing_range_.collapsed() ? selection_ : composing_range_;
text_.replace(rangeToDelete.start(), rangeToDelete.length(), text); // <-- no bounds check
...
}std::basic_string::replace(pos, len, str) throws std::out_of_range when pos > size(). Neither rangeToDelete.start() nor its length is validated against text_.length(). Related state-desync reports over the years: #64741, #100272, #92060. A neighboring crash in the same plugin (insertText / NSAttributedString) was recently fixed in #176329, but this UpdateComposingText path is still unguarded.
Suggested fix — clamp before replacing (or bail out and resync):
const size_t start = std::min(rangeToDelete.start(), text_.length());
const size_t length = std::min(rangeToDelete.length(), text_.length() - start);
text_.replace(start, length, text);Crash log (symbolicated key frames)
Exception Type: EXC_CRASH (SIGABRT)
Triggered by Thread: 0 (input method 1535 com.tencent.inputmethod.wetype), com.apple.main-thread
0 libsystem_kernel.dylib __pthread_kill + 8
1 libsystem_pthread.dylib pthread_kill + 296
2 libsystem_c.dylib abort + 148
3 FlutterMacOS std::__fl::__libcpp_verbose_abort(char const*, ...) + 48 (verbose_abort.cpp:62)
4 FlutterMacOS std::__fl::__throw_out_of_range[abi:nn210000](char const*) + 28 (stdexcept:253)
5 FlutterMacOS std::__fl::basic_string<char16_t, ...>::__throw_out_of_range[abi:nn210000]() + 20 (string:2398)
6 FlutterMacOS std::__fl::basic_string<char16_t, ...>::replace(unsigned long, unsigned long, char16_t const*, unsigned long) + 504 (string:3263)
8 FlutterMacOS flutter::TextInputModel::UpdateComposingText(std::__fl::basic_string<char16_t, ...> const&, flutter::TextRange const&) + 148 (text_input_model.cc:80)
9 FlutterMacOS -[FlutterTextInputPlugin setMarkedText:selectedRange:replacementRange:] + 672 (FlutterTextInputPlugin.mm:896)
10 AppKit +[NSInputAnalytics(TrackedActionsManager) allowActionTrackingAnalyticsWithName:forAction:] + 160
11 AppKit -[NSTextInputContext(NSInputContext_WithCompletion) setMarkedText:selectedRange:replacementRange:completionHandler:] + 268
...
45 HIToolbox -[IMKInputSession_Modern setMarkedText:selectionRange:replacementRange:completionHandler:] + 540
...
70 HIToolbox -[IMKClient_Modern switchedInputMode:completionHandler:] + 416
...
102 FlutterMacOS -[FlutterTextInputPlugin handleKeyEvent:] + 224 (FlutterTextInputPlugin.mm:637)
FlutterMacOS binary UUID 4C4C44E6-5555-3144-A1FE-1E90541BCD45 — byte-identical to the darwin-x64-release arm64 artifact shipped with Flutter 3.41.9 (engine 42d3d75a56efe1a2e9902f52dc8006099c45d937).
Environment: macOS 26.5.2 (25F84), Apple M5 (arm64), input method Tencent WeType.
Code sample
No minimal reproduction available (race condition with a third-party IME process). The receiving widget was a plain TextField / SuperEditor text input; nothing app-specific is on the crashing stack between AppKit and the engine.
Logs / Flutter Doctor output
flutter doctor -v (trimmed)
[!] Flutter (Channel stable, 3.41.9, on macOS 26.5.2 25F84 darwin-arm64, locale zh-Hans-CN)
• Flutter version 3.41.9 on channel stable
• Framework revision 00b0c91f06 (2026-04-29)
• Engine revision 42d3d75a56
• Dart version 3.11.5
Activity
mbcorona commentedon Aug 7, 2026
Thanks for the symbolicated crash and the precise diagnosis.
Routing to team-macos with fyi-text-input.
cbracken commentedon Aug 13, 2026
This seems likely to be IME-specific; we've definitely seen issues with inconsistent ordering/etc. expectations between IMEs on other platforms (I'm thinking about Sogou IME on Windows as a classic example).
Regardless it seems like it should be trivial for us to guard against a crash; getting the correct behaviour may be trickier.
justinmc commentedon Aug 13, 2026
The raciness sounds like it would be fixed if/when we move to a synchronous text input model.
But yeah makes sense to me to guard against it for now either way.
macOS: Fix crash on out-of-bounds text input ranges
macOS: Fix crash on out-of-bounds text input ranges (flutter#191780)
common: Fix surrogate pair stepping in TextInputModel::DeleteSurrounding
cbracken commentedon Aug 27, 2026
@Scorpiolol I've landed a fix (or at least crash prevention) on master branch. Can you try that out with the third-party IME in question and see if the issue is resolved.
common: Fix surrogate pair stepping in TextInputModel::DeleteSurrounding
common: Fix surrogate pair stepping in TextInputModel::DeleteSurrounding
common: Fix surrogate pair stepping in TextInputModel
github-actions commentedon Sep 10, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.