بررسی عرض، یک کانجی را خراب کرد

نامی وارد یک جدول ترمینال شد و به صورت خراب از آن خارج شد. نام خانوادگی 𠮷田 بود.

کاراکتر اول، 吉 رایج نیست؛ بلکه 𠮷 (U+20BB7) است. این یک شکل نادر است که در نام‌های خانوادگی واقعی ژاپنی استفاده می‌شود. جدول برای جا شدن در یک ستون، سلول را کوتاه (truncate) کرد. به جای نام، یک کاراکتر شکسته چاپ شد. آن کانجی از وسط دو نیم شد.

این باگ در یک میان‌بر تک‌خطی نهفته بود. کد قبل از اینکه واقعاً رشته را کوتاه کند، تصمیم گرفت که رشته برای برش بر اساس ایندکس (index) ایمن است. این منطق به دلیل نحوه مدیریت رشته‌ها در JavaScript شکست خورد.

یک رشته در JavaScript سه طول متفاوت دارد:

  • طول واحد کد (Code unit length): "𠮷".length برابر با ۲ است. این مقدار واحدهای UTF-16 را می‌شمارد.
  • تعداد نقطه کد (Code point count): [..."𠮷"].length برابر با ۱ است. این مقدار کاراکترهای واقعی را می‌شمارد.
  • عرض نمایش (Display width): تعداد ستون‌هایی که در ترمینال اشغال می‌کند ۲ است.

برای متن‌های ساده انگلیسی، این اعداد یکسان هستند. "abc" دارای ۳ واحد، ۳ نقطه و ۳ ستون است. بیشتر کدها فرض می‌کنند که این تصادف، یک قاعده است.

کاراکتر 𠮷 این قاعده را می‌شکند. این کاراکتر دارای ۲ واحد کد و ۲ ستون است. اعداد با هم مطابقت دارند، اما به دلایل متفاوت. کد عدد ۲ را با ۲ برابر دید و از یک مسیر سریع (fast path) برای برش رشته بر اساس ایندکس استفاده کرد.

وقتی رشته را در ایندکس ۳ برش داد، اولین کاراکتر کامل و تنها نیمی از کاراکتر دوم را برداشت. این کار باعث شد یک surrogate تنها باقی بماند. ترمینال‌ها این را به صورت یک مربع شکسته نشان می‌دهند.

کاراکترهای رایج ژاپنی مانند 漢 ایمن هستند. آن‌ها ۱ واحد کد و ۲ ستون دارند. از آنجایی که ۱ با ۲ برابر نیست، کد از آن میان‌بر خراب اجتناب می‌کند. این باگ فقط کاراکترهای نادر و ایموجی‌ها را تحت تأثیر قرار می‌دهد.

برای رفع این مشکل، باید:

  • مسیر سریع را محافظت کنید تا رشته‌های دارای high surrogates را رد کند.
  • به جای واحدهای کد، بر اساس نقاط کد کامل (whole code points) برش (trim) دهید.

استفاده از Array.from(str) این مشکل را حل می‌کند زیرا بر اساس نقطه کد پیمایش (iterate) می‌کند. این روش با کاراکتر به عنوان یک واحد کامل برخورد می‌کند.

درس ساده است: هرگز با یک واحد اندازه‌گیری نکنید و با واحدی دیگر برش ندهید. اگر عرض نمایش را اندازه‌گیری کنید اما بر اساس ایندکس واحد کد برش دهید، داده‌های کاربران خود را خراب خواهید کرد.

کد خود را با یک کاراکتر نادر 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/