การตรวจสอบความกว้างทำให้คันจิพัง

ชื่อหนึ่งถูกใส่ลงในตารางเทอร์มินัลแล้วออกมาในสภาพที่พัง ชื่อสกุลนั้นคือ 𠮷田

ตัวอักษรแรกไม่ใช่ 吉 ที่พบเห็นทั่วไป แต่เป็น 𠮷 (U+20BB7) ซึ่งเป็นรูปแบบที่หาได้ยากและใช้ในนามสกุลญี่ปุ่นจริงๆ ตารางได้ทำการตัดข้อความ (truncate) ในเซลล์เพื่อให้พอดีกับคอลัมน์ แทนที่จะแสดงชื่อ มันกลับพิมพ์ตัวอักษรที่พังออกมา ตัวคันจิถูกแบ่งออกเป็นสองส่วน

บั๊กนี้ซ่อนอยู่ในโค้ดทางลัดเพียงบรรทัดเดียว โค้ดตัดสินใจว่าสตริงนั้นปลอดภัยที่จะตัดตามดัชนี (index) ก่อนที่จะทำการตัดจริงๆ ตรรกะนี้ล้มเหลวเนื่องจากวิธีการที่ JavaScript จัดการกับสตริง

สตริงใน JavaScript มีความยาวที่แตกต่างกันสามแบบ:

  • ความยาวหน่วยรหัส (Code unit length): "𠮷".length คือ 2 ซึ่งเป็นการนับหน่วย UTF-16
  • จำนวนจุดรหัส (Code point count): [..."𠮷"].length คือ 1 ซึ่งเป็นการนับจำนวนตัวอักษรจริงๆ
  • ความกว้างในการแสดงผล (Display width): จำนวนคอลัมน์ที่ใช้ในเทอร์มินัลคือ 2

สำหรับข้อความภาษาอังกฤษทั่วไป ตัวเลขเหล่านี้จะเท่ากัน "abc" มี 3 หน่วย, 3 จุดรหัส และ 3 คอลัมน์ โค้ดส่วนใหญ่จึงทึกทักเอาว่าความบังเอิญนี้คือกฎเกณฑ์

ตัวอักษร 𠮷 ทำลายกฎนั้น มันมี 2 หน่วยรหัสและ 2 คอลัมน์ ตัวเลขดูเหมือนจะตรงกัน แต่เป็นเพราะเหตุผลที่ต่างกัน โค้ดเห็นว่า 2 เท่ากับ 2 จึงเลือกใช้เส้นทางลัด (fast path) ในการตัดสตริงตามดัชนี

เมื่อมันตัดสตริงที่ดัชนี 3 มันได้ตัวอักษรแรกแบบเต็มตัว แต่ได้ตัวที่สองมาเพียงครึ่งเดียว สิ่งนี้ทำให้เหลือ surrogate (ตัวแทนรหัส) เพียงตัวเดียวทิ้งไว้ ซึ่งเทอร์มินัลจะแสดงผลเป็นกล่องที่พัง (broken box)

ตัวอักษรญี่ปุ่นทั่วไปอย่าง 漢 นั้นปลอดภัย เพราะมี 1 หน่วยรหัสและ 2 คอลัมน์ เนื่องจาก 1 ไม่เท่ากับ 2 โค้ดจึงหลีกเลี่ยงทางลัดที่พังนั้น บั๊กนี้จึงจะเกิดขึ้นกับตัวอักษรที่หาได้ยากและอีโมจิเท่านั้น

ในการแก้ไขเรื่องนี้ คุณต้อง:

  • ป้องกันเส้นทางลัด (guard the fast path) เพื่อปฏิเสธสตริงที่มี high surrogates
  • ตัดข้อความ (trim) โดยใช้จุดรหัส (code points) ทั้งหมด แทนที่จะใช้หน่วยรหัส (code units)

การใช้ Array.from(str) สามารถแก้ไขปัญหานี้ได้ เพราะมันจะวนลูปตามจุดรหัส (code point) โดยจะมองตัวอักษรเป็นหนึ่งหน่วยที่สมบูรณ์

บทเรียนนี้เรียบง่าย: อย่าวัดด้วยหน่วยหนึ่งแล้วไปตัดด้วยอีกหน่วยหนึ่ง หากคุณวัดความกว้างในการแสดงผลแต่ไปตัดด้วยดัชนีของหน่วยรหัส คุณจะทำให้ข้อมูลของผู้ใช้เสียหาย

ทดสอบโค้ดของคุณด้วยตัวอักษร CJK ที่หาได้ยากหรืออีโมจิ ตัวอักษร ASCII จะไม่แสดงข้อผิดพลาดเหล่านี้ คุณต้องป้อนข้อมูลที่โค้ดของคุณ "กลัว" เข้าไป

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

Optional learning community: https://greymoth-jp.github.io/cjk-failure-corpus/