Перевірка ширини зламала кандзі
Ім'я потрапило в таблицю термінала, а вийшло зламаним. Прізвище було 𠮷田.
Перший символ — це не звичайне 吉. Це 𠮷 (U+20BB7). Це рідкісна форма, що використовується в справжніх японських прізвищах. Таблиця обрізала комірку, щоб вона вписалася в колонку. Замість імені було надруковано зламаний символ. Кандзі було розрізано навпіл.
Баг ховався в однорядковому скороченні. Код вирішував, що рядок можна безпечно обрізати за індексом, ще до того, як фактично здійснював обрізання. Ця логіка не спрацювала через те, як JavaScript працює з рядками.
Рядок у JavaScript має три різні довжини:
- Довжина кодових одиниць:
"𠮷".lengthдорівнює 2. Це підрахунок одиниць UTF-16. - Кількість кодових точок:
[..."𠮷"].lengthдорівнює 1. Це підрахунок фактичних символів. - Ширина відображення: кількість колонок, яку символ займає в терміналі, дорівнює 2.
Для звичайного англійського тексту ці числа однакові. "abc" має 3 одиниці, 3 точки та 3 колонки. Більшість коду припускає, що цей збіг є правилом.
Символ 𠮷 порушує це правило. Він має 2 кодові одиниці та 2 колонки. Числа збігаються, але з різних причин. Код побачив, що 2 дорівнює 2, і використав швидкий шлях для обрізання рядка за індексом.
Коли він обрізав рядок на індексі 3, він взяв перший повний символ і лише половину другого. Це залишило самотній сурогат. Термінали відображають це як зламаний квадрат.
Поширені японські символи, такі як 漢, є безпечними. Вони мають 1 кодову одиницю та 2 колонки. Оскільки 1 не дорівнює 2, код уникає помилкового скорочення. Баг проявляється лише на рідкісних символах та емодзі.
Щоб виправити це, потрібно:
- Додати перевірку для швидкого шляху, щоб відхиляти рядки з високими сурогатами.
- Обрізати за цілими кодовими точками замість кодових одиниць.
Використання Array.from(str) вирішує цю проблему, оскільки воно ітерує за кодовими точками. Воно сприймає символ як одну цілу одиницю.
Урок простий: ніколи не вимірюйте однією одиницею, а обрізайте за іншою. Якщо ви вимірюєте ширину відображення, але обрізаєте за індексом кодової одиниці, ви зіпсуєте дані своїх користувачів.
Тестуйте свій код рідкісним символом CJK або емодзі. ASCII не покаже вам цих помилок. Ви повинні надати саме ті вхідні дані, яких боїться ваш код.
Додаткова спільнота для навчання: https://greymoth-jp.github.io/cjk-failure-corpus/
