Skip to content

[Linux] App aborts with wstring_convert: to_bytes error when typing with an IME — TextInputModel::DeleteSurrounding splits UTF-16 surrogate pairs #190046

Description

@chen00054

Steps to reproduce

Environment: Debian 12 desktop, ibus with a Chinese Wubi (五笔) input method (e.g. ibus-table-wubi) — any IME engine that uses GTK surrounding-text / delete-surrounding should work.

  1. Run any Flutter Linux desktop app with a TextField (release or debug — the crash is in the engine).
  2. Make sure the field's text contains an emoji (any non-BMP character), e.g. paste a😀.
  3. Place the cursor before the emoji and type with the Wubi IME so that the IME issues a delete-surrounding request covering the BMP character and the emoji (Chinese IMEs do this routinely for reconversion / candidate replacement).
  4. The app aborts immediately.

The crash is probabilistic at the UI level (it depends on the IME issuing delete-surrounding across an emoji), but it is deterministic at the unit level — see the minimal reproduction below, which needs no IME at all.

Minimal reproduction (engine unit test)

// engine/src/flutter/shell/platform/common/text_input_model_unittests.cc
TEST(TextInputModel, DeleteSurroundingMustNotSplitSurrogatePairs) {
  auto model = std::make_unique<TextInputModel>();
  model->SetText("a😀");                       // UTF-16: 'a', 0xD83D, 0xDE00
  EXPECT_TRUE(model->SetSelection(TextRange(0)));
  model->DeleteSurrounding(0, 2);              // delete 'a' and the emoji
  EXPECT_EQ(model->GetText(), "");             // ABORTS before the comparison
}

Trace of DeleteSurrounding(0, 2) with text_ = {'a', 0xD83D, 0xDE00}, start = 0:

  • iteration 0: end += IsLeadingSurrogate(text_.at(start /* =0, 'a' */)) ? 2 : 1 → end = 1
  • iteration 1: end += IsLeadingSurrogate(text_.at(start /* still 0, 'a' */)) ? 2 : 1 → end = 2
    (should have checked text_.at(end /* =1, 0xD83D */) and advanced by 2)
  • text_.erase(0, 2) removes 'a' and the lead surrogate only, leaving a lone trail surrogate 0xDE00 in text_.

The next GetText() (called unconditionally at the top of im_delete_surrounding_cb, and by update_editing_state) runs fml::Utf16ToUtf8 → std::wstring_convert::to_bytes, which throws std::range_error on the lone surrogate. The engine is built with -fno-exceptions, so this is an immediate abort:

range_error was thrown in -fno-exceptions mode with message "wstring_convert: to_bytes error"
Aborted

Root cause

TextInputModel::DeleteSurrounding advances end by checking the character at start instead of at end:

auto end = start;
for (int i = 0; i < count && end != max_pos; i++) {
end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
}

  auto end = start;
  for (int i = 0; i < count && end != max_pos; i++) {
    end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;   // <-- should be text_.at(end)
  }

Consequences:

  • first deleted char is BMP, a later one is non-BMP → end lands mid-surrogate-pair, erase leaves a lone surrogate in text_, and the next UTF-16→UTF-8 conversion aborts the process;
  • first deleted char is non-BMP, later ones are BMP → end over-advances and deletes more text than requested (silent data corruption).

Crash entry point on Linux (GTK delete-surrounding, used heavily by Chinese/Japanese IMEs over ibus/fcitx):

static gboolean im_delete_surrounding_cb(FlTextInputHandler* self,
gint offset,
gint n_chars) {
std::string text_before_change = self->text_model->GetText();
if (self->text_model->DeleteSurrounding(offset, n_chars)) {
if (self->enable_delta_model) {
flutter::TextEditingDelta delta = flutter::TextEditingDelta(
text_before_change, self->text_model->composing_range(),
self->text_model->GetText());
update_editing_state_with_delta(self, &delta);
} else {
update_editing_state(self);
}
}
return TRUE;
}

Note that DeleteSurrounding is in shell/platform/common, so Windows is affected by the same defect as well (Windows reaches it through its own text input plugin paths).

Related hardening opportunity

TextInputModel::GetCursorOffset is a second unguarded conversion site: if selection_.extent() ever lands inside a surrogate pair (e.g. via an inconsistent preedit cursor offset from im_preedit_changed_cb), substr produces a string ending in a lone lead surrogate and fml::Utf16ToUtf8 aborts the same way:

int TextInputModel::GetCursorOffset() const {
// Measure the length of the current text up to the selection extent.
// There is probably a much more efficient way of doing this.
auto leading_text = text_.substr(0, selection_.extent());
return fml::Utf16ToUtf8(leading_text).size();
}

Since fml::Utf16ToUtf8 / Utf8ToUtf16 (std::wstring_convert, deprecated since C++17) turn any invalid input into a process abort under -fno-exceptions, it may be worth making the conversion lenient (replacement character) in addition to fixing the call sites. This is the same failure signature as #154072 and the macOS issue #182443.

Expected results

Deleting surrounding text that contains non-BMP characters deletes whole code points; the app does not crash.

Actual results

The app aborts with:

range_error was thrown in -fno-exceptions mode with message "wstring_convert: to_bytes error"
Aborted (core dumped)

Suggested fix

  auto end = start;
  for (int i = 0; i < count && end != max_pos; i++) {
    end += IsLeadingSurrogate(text_.at(end)) ? 2 : 1;
  }

Flutter version

Flutter 3.44.8 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 058e0af2c2 • 2026-07-23 10:56:21 -0700
Engine • hash 13ffd72b2f9a5ca4db2a74ea52d5353ec2e8f939 (revision 0cd610717b) • 2026-07-23 16:11:34.000Z
Tools • Dart 3.12.2 • DevTools 2.57.0

Reproduced on Debian 12 (GNOME/X11, ibus). The defect is present on current master — text_input_model.cc has not been modified since the code was introduced.

Activity

  1. added
    in triagePresently being triaged by the triage team
    a: text inputEntering text in a text field or keyboard related problems
    c: crashStack traces logged to the console
    engineflutter/engine related. See also e: labels.
    platform-linuxBuilding on or for Linux specifically
    c: fatal crashCrashes that terminate the process
    team-linuxOwned by the Linux platform team
    fyi-engineFor the attention of Engine team
    and removed
    in triagePresently being triaged by the triage team
    on Jul 28, 2026
  2. mbcorona commented on Jul 28, 2026

    @mbcorona
    Member

    HI @chen00054, thanks for the detailed report and for pointing to the source code.
    Routing to the Linux team for evaluation.

  3. gaaclarke commented on Aug 3, 2026

    @gaaclarke
    Member

    @mbcorona I think it's generally better to let downstream engineers use the fyi-engine label as a way for engineers to tag in other teams if they need help.

  4. Vi-debug commented on Aug 4, 2026

    @Vi-debug
    Contributor

    Opened PR #190514 resolve this issue

  5. added 2 commits that reference this issue on Aug 5, 2026
    d14198e
    6581a11
  6. added a commit that references this issue on Aug 18, 2026
    d31df22
  7. added
    P2Important issues not at the top of the work list
    triaged-linuxTriaged by the Linux platform team
    on Aug 19, 2026
  8. robert-ancell commented on Aug 19, 2026

    @robert-ancell
    Contributor

    While filed against Linux, the fix suggests this could affect multiple platforms.

  9. added a commit that references this issue on Sep 3, 2026
    37ad6a3
  10. github-actions commented on Sep 17, 2026

    @github-actions

    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 -v and a minimal reproduction of the issue.

  11. locked as resolved and limited conversation to collaborators on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: text inputEntering text in a text field or keyboard related problemsc: crashStack traces logged to the consolec: fatal crashCrashes that terminate the processengineflutter/engine related. See also e: labels.platform-linuxBuilding on or for Linux specificallyteam-linuxOwned by the Linux platform teamtriaged-linuxTriaged by the Linux platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions