Une vérification de la largeur indiquait que la chaîne pouvait être coupée en toute sécurité. Elle a coupé un kanji en deux.

Un nom a été saisi dans un tableau de terminal et en est ressorti corrompu. Le nom de famille était 𠮷田.

Le premier caractère n'est pas le 吉 courant. Il s'agit de 𠮷 (U+20BB7). C'est une forme rare utilisée dans de véritables noms de famille japonais. Le tableau a tronqué la cellule pour qu'elle s'adapte à une colonne, laissant derrière lui un caractère corrompu.

Le bug résidait dans une seule ligne de code. Il s'agissait d'une optimisation qui décidait qu'une chaîne pouvait être coupée en toute sécurité par index.

Une chaîne JavaScript possède trois longueurs différentes : • Unités de code (.length) : "𠮷".length est égal à 2. • Points de code : [..."𠮷"].length est égal à 1. • Largeur d'affichage : 𠮷 occupe 2 colonnes.

Pour du texte anglais standard, ces nombres sont tous identiques. Cette coïncidence donne l'illusion que le code est sûr.

Le caractère 𠮷 enfreint cette règle. Il possède 2 unités de code car il s'agit d'une paire de substitution (surrogate pair). Il occupe 2 colonnes car c'est un caractère large. Les chiffres correspondent (2 = 2), mais pour des raisons différentes.

La bibliothèque cli-table3 utilisait un chemin rapide (fast path) : Si la longueur en unités de code est égale à la largeur d'affichage, alors couper la chaîne par index.

Cela a fonctionné pendant des années car les caractères japonais courants comme 漢 ont une longueur de 1 et une largeur de 2. Ils ne déclenchent jamais le chemin rapide.

Le chemin rapide ne se déclenche que pour des caractères rares comme 𠮷 ou des emojis. Ces caractères ont une longueur de 2 et une largeur de 2. Le code pense qu'il s'agit de simples caractères d'une seule unité. Il les coupe en deux par index, laissant derrière lui une unité de substitution isolée. C'est pourquoi le terminal affiche un carré corrompu.

Pour corriger cela, vous devez :

  • Protéger le chemin rapide pour exclure les paires de substitution.
  • Tronquer par points de code plutôt que par unités de code.

L'utilisation de Array.from(str) aide car elle itère par point de code. Cela garantit que vous ne couperez jamais un caractère en deux.

La leçon est simple : Ne mesurez jamais avec une unité pour couper avec une autre. Si vous mesurez la largeur d'affichage ou les points de code, vous devez couper en utilisant ces mêmes unités.

Testez votre code avec un caractère CJK Extension B ou un emoji. L'ASCII ne révélera jamais ce bug.

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

Communauté d'apprentissage optionnelle : https://greymoth-jp.github.io/cjk-failure-corpus/