Pengecekan Lebar Merusak Sebuah Kanji

Sebuah nama dimasukkan ke dalam tabel terminal dan keluar dalam keadaan rusak. Nama belakangnya adalah 𠮷田.

Karakter pertamanya bukanlah 吉 yang umum. Karakter tersebut adalah 𠮷 (U+20BB7). Ini adalah bentuk langka yang digunakan dalam nama keluarga Jepang asli. Tabel tersebut memotong (truncate) sel agar sesuai dengan kolom. Bukannya menampilkan nama, tabel tersebut malah mencetak karakter yang rusak. Kanji tersebut terbelah menjadi dua.

Bug ini terletak pada sebuah pintasan satu baris. Kode tersebut memutuskan bahwa sebuah string aman untuk dipotong berdasarkan indeks sebelum benar-benar memotongnya. Logika ini gagal karena cara JavaScript menangani string.

Sebuah string JavaScript memiliki tiga jenis panjang yang berbeda:

  • Panjang unit kode (Code unit length): "𠮷".length adalah 2. Ini menghitung unit UTF-16.
  • Jumlah titik kode (Code point count): [..."𠮷"].length adalah 1. Ini menghitung karakter yang sebenarnya.
  • Lebar tampilan (Display width): Jumlah kolom yang diambil dalam terminal adalah 2.

Untuk teks bahasa Inggris biasa, angka-angka ini sama. "abc" memiliki 3 unit, 3 titik, dan 3 kolom. Sebagian besar kode mengasumsikan kebetulan ini adalah sebuah aturan.

Karakter 𠮷 melanggar aturan tersebut. Karakter ini memiliki 2 unit kode dan 2 kolom. Angkanya cocok, tetapi karena alasan yang berbeda. Kode tersebut melihat 2 sama dengan 2 dan menggunakan jalur cepat (fast path) untuk memotong string berdasarkan indeks.

Ketika ia memotong string pada indeks 3, ia mengambil karakter pertama secara utuh dan hanya setengah dari karakter kedua. Hal ini meninggalkan sebuah surrogate yang sendirian. Terminal menampilkannya sebagai kotak yang rusak.

Karakter Jepang umum seperti 漢 aman. Karakter tersebut memiliki 1 unit kode dan 2 kolom. Karena 1 tidak sama dengan 2, kode tersebut menghindari pintasan yang rusak. Bug ini hanya menyerang karakter langka dan emoji.

Untuk memperbaikinya, Anda harus:

  • Melindungi jalur cepat untuk menolak string dengan high surrogate.
  • Melakukan pemotongan (trim) berdasarkan titik kode secara utuh, bukan unit kode.

Menggunakan Array.from(str) memperbaiki masalah ini karena ia melakukan iterasi berdasarkan titik kode. Ia memperlakukan karakter sebagai satu unit utuh.

Pelajaran sederhananya adalah: jangan pernah mengukur dengan satu unit dan memotong dengan unit yang lain. Jika Anda mengukur lebar tampilan tetapi memotong berdasarkan indeks unit kode, Anda akan merusak data pengguna Anda.

Uji kode Anda dengan karakter CJK yang langka atau emoji. ASCII tidak akan menunjukkan kesalahan ini kepada Anda. Anda harus memberikan input yang ditakuti oleh kode Anda.

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

Komunitas pembelajaran opsional: https://greymoth-jp.github.io/cjk-failure-corpus/