Eine Breitenprüfung hat ein Kanji zerstört
Ein Name wurde in eine Terminal-Tabelle eingegeben und kam beschädigt wieder heraus. Der Nachname war 𠮷田.
Das erste Zeichen ist nicht das gebräuchliche 吉. Es ist 𠮷 (U+20BB7). Dies ist eine seltene Form, die in echten japanischen Familiennamen verwendet wird. Die Tabelle kürzte die Zelle, um in eine Spalte zu passen. Anstatt eines Namens wurde ein beschädigtes Zeichen ausgegeben. Das Kanji wurde in der Mitte gespalten.
Der Bug lag in einer einzeiligen Abkürzung. Der Code entschied, dass ein String sicher nach Index gekürzt werden konnte, bevor er ihn tatsächlich kürzte. Diese Logik schlug fehl, weil JavaScript Strings auf eine bestimmte Weise handhabt.
Ein JavaScript-String hat drei verschiedene Längen:
- Code-Unit-Länge:
"𠮷".lengthist 2. Dies zählt die UTF-16-Einheiten. - Code-Point-Anzahl:
[..."𠮷"].lengthist 1. Dies zählt die tatsächlichen Zeichen. - Anzeigebreite: Die Anzahl der Spalten, die es in einem Terminal einnimmt, ist 2.
Bei einfachem englischem Text sind diese Zahlen identisch. "abc" hat 3 Einheiten, 3 Points und 3 Spalten. Die meisten Programme gehen davon aus, dass dieser Zufall eine Regel ist.
Das Zeichen 𠮷 bricht diese Regel. Es hat 2 Code-Units und 2 Spalten. Die Zahlen stimmen überein, aber aus unterschiedlichen Gründen. Der Code sah, dass 2 gleich 2 ist, und nutzte einen Fast Path, um den String nach Index zu kürzen.
Als der String am Index 3 gekürzt wurde, nahm er das erste vollständige Zeichen und nur die Hälfte des zweiten. Dies hinterließ ein einzelnes Surrogate. Terminals zeigen dies als defektes Kästchen an.
Gebräuchliche japanische Zeichen wie 漢 sind sicher. Sie haben 1 Code-Unit und 2 Spalten. Da 1 nicht gleich 2 ist, umgeht der Code die fehlerhafte Abkürzung. Der Bug tritt nur bei seltenen Zeichen und Emojis auf.
Um dies zu beheben, müssen Sie:
- Den Fast Path absichern, um Strings mit High Surrogates abzulehnen.
- Nach vollständigen Code-Points statt nach Code-Units kürzen.
Die Verwendung von Array.from(str) behebt dies, da es nach Code-Points iteriert. Es behandelt das Zeichen als eine einzige Einheit.
Die Lektion ist einfach: Messen Sie niemals nach einer Einheit und kürzen Sie nach einer anderen. Wenn Sie die Anzeigebreite messen, aber nach dem Code-Unit-Index kürzen, beschädigen Sie die Daten Ihrer Nutzer.
Testen Sie Ihren Code mit einem seltenen CJK-Zeichen oder einem Emoji. ASCII wird Ihnen diese Fehler nicht zeigen. Sie müssen genau die Eingabe bereitstellen, vor der Ihr Code Angst hat.
Optionale Lern-Community: https://greymoth-jp.github.io/cjk-failure-corpus/
