Skip to content

UTF-8: обрезка строк по байтам режет символ пополам (промпт и не только) #3763

Description

@bylins

@kvirund, это про ветку kvirund/utf8-migration. Прикладной случай уже закрыт в мастере (#3762), но общий приём в ветке остался, и он даёт видимый игроку мусор.

Что видит игрок

Из #3761, промпт в бою:

...[Белотур:Невредим] [Сальгард:Невредим] [летучая мышь падальщик:О.тяжело ранен?

Строка обрывается, а на конце знак вопроса. Это не артефакт клиента: обрезка разрезала русскую букву пополам, и перекодировщик на выходе честно показал оставшийся байт как ?.

Откуда

iosystem.cpp:810:

strncat(i, MakePrompt(t).c_str(), kMaxPromptLength);

Обрезка по байтам. В KOI8-R байт равен символу, и хуже усечённого слова ничего не случалось. В UTF-8 разрез приходится на середину многобайтовой последовательности, и вместо слова получается битый символ.

В мастере я поднял kMaxPromptLength с 256 до 1024 — это убирает практический повод срабатывать (тот промпт из issue занимал в UTF-8 ровно 256 байт, впритык). Но сам приём никуда не делся: при достаточно длинном промпте разрежет снова, просто реже.

Что предлагаю

В ветке для этого уже есть готовое: native_text::truncate_offset(s, max_bytes) — наибольшее смещение, не разрывающее символ. Тогда строка кончается целой буквой, даже если не влезла.

const std::string prompt = MakePrompt(t);
strncat(i, prompt.c_str(), native_text::truncate_offset(prompt, kMaxPromptLength));

Сам я править не стал: место твоё, и ты лучше знаешь, не завязано ли что-то ещё на побайтовую семантику этого лимита.

Заодно рядом

Прочесал ветку на тот же приём. truncate_offset/char_offset вне самого native_text используется один раз на всю кодовую базу, а мест, где режут по байтам, заметно больше. Кандидаты, где в буфер может приехать русский текст:

  • iosystem.cpp:831strncat(o, str_goahead, kMaxPromptLength), тот же лимит;
  • iosystem.cpp:596-605 — склейка подстановки, три обрезки по kMaxInputLength;
  • identify.cpp:329/339/357/388 — дописывание \r\n в остаток буфера;
  • jewelry.cpp:326, login.cpp:1896/1905 — копирование аргумента и почты в фиксированные поля.

Часть безобидна (там дописывают ASCII), но проверить стоит все: симптом у этого класса всегда один и тот же — вопросительный знак или ромбик на конце обрезанной строки, и жалоба приходит не от того, кто может объяснить, что случилось.

Для полноты: буферы [128]/[256] я вчера прошёл отдельно, там переполнений не осталось — правки 9ce270254, e529a9463, bcb7aa816, 7aa9abbf5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions