Uma Verificação de Largura Quebrou um Kanji
Um nome entrou em uma tabela de terminal e saiu quebrado. O sobrenome era 𠮷田.
O primeiro caractere não é o comum 吉. É 𠮷 (U+20BB7). Esta é uma forma rara usada em sobrenomes japoneses reais. A tabela truncou a célula para caber em uma coluna. Em vez de um nome, ela imprimiu um caractere quebrado. O kanji foi dividido ao meio.
O bug residia em um atalho de uma única linha. O código decidia que uma string estava segura para ser cortada por índice antes de realmente truncá-la. Essa lógica falhou devido à forma como o JavaScript lida com strings.
Uma string em JavaScript possui três comprimentos diferentes:
- Comprimento de unidade de código:
"𠮷".lengthé 2. Isso conta unidades UTF-16. - Contagem de pontos de código:
[..."𠮷"].lengthé 1. Isso conta os caracteres reais. - Largura de exibição: O número de colunas que ele ocupa em um terminal é 2.
Para texto simples em inglês, esses números são os mesmos. "abc" tem 3 unidades, 3 pontos e 3 colunas. A maioria dos códigos assume que essa coincidência é uma regra.
O caractere 𠮷 quebra essa regra. Ele possui 2 unidades de código e 2 colunas. Os números coincidem, mas por motivos diferentes. O código viu que 2 é igual a 2 e usou um caminho rápido para cortar a string por índice.
Quando cortou a string no índice 3, ele pegou o primeiro caractere completo e apenas metade do segundo. Isso deixou um surrogate isolado para trás. Os terminais exibem isso como um quadrado quebrado.
Caracteres japoneses comuns como 漢 estão seguros. Eles possuem 1 unidade de código e 2 colunas. Como 1 não é igual a 2, o código evita o atalho problemático. O bug só afeta caracteres raros e emojis.
Para corrigir isso, você deve:
- Proteger o caminho rápido para rejeitar strings com high surrogates.
- Truncar por pontos de código inteiros em vez de unidades de código.
Usar Array.from(str) corrige isso porque ele itera por ponto de código. Ele trata o caractere como uma unidade inteira.
A lição é simples: nunca meça por uma unidade e corte por outra. Se você medir a largura de exibição, mas cortar pelo índice da unidade de código, você quebrará os dados dos seus usuários.
Teste seu código com um caractere CJK raro ou um emoji. O ASCII não mostrará esses erros. Você deve fornecer a entrada que seu código teme.
Comunidade de aprendizado opcional: https://greymoth-jp.github.io/cjk-failure-corpus/
