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.

Fonte: https://dev.to/greymothjp/a-width-check-said-the-string-was-safe-to-cut-it-split-a-kanji-in-half-4hjk

Comunidade de aprendizado opcional: https://greymoth-jp.github.io/cjk-failure-corpus/