فحص العرض تسبب في تلف حرف كانجي
دخل اسمٌ في جدول داخل الطرفية (terminal table) وخرج تالفاً. كان اسم العائلة هو 𠮷田.
الحرف الأول ليس 吉 الشائع، بل هو 𠮷 (U+20BB7). هذا شكل نادر يُستخدم في أسماء العائلات اليابانية الحقيقية. قام الجدول باقتطاع الخلية لتناسب العمود، وبدلاً من الاسم، طبع حرفاً تالفاً؛ حيث انقسم حرف الكانجي إلى نصفين.
كان الخطأ البرمجي يكمن في اختصار من سطر واحد. قرر الكود أن السلسلة النصية آمنة للقص بناءً على الفهرس (index) قبل اقتطاعها فعلياً. فشل هذا المنطق بسبب الطريقة التي تتعامل بها JavaScript مع السلاسل النصية.
تمتلك السلسلة النصية في JavaScript ثلاثة أطوال مختلفة:
- طول وحدة الرمز (Code unit length): قيمة
"𠮷".lengthهي 2. هذا يحسب وحدات UTF-16. - عدد نقاط الرمز (Code point count): قيمة
[..."𠮷"].lengthهي 1. هذا يحسب الرموز الفعلية. - عرض العرض (Display width): عدد الأعمدة التي يشغلها في الطرفية هو 2.
بالنسبة للنصوص الإنجليزية العادية، تكون هذه الأرقام متطابقة. فكلمة "abc" تتكون من 3 وحدات، و3 نقاط، و3 أعمدة. تفترض معظم الأكواد أن هذه المصادفة هي قاعدة ثابتة.
لكن الحرف 𠮷 يكسر هذه القاعدة. فهو يمتلك وحدتي رمز (code units) وعمودين. الأرقام متطابقة، ولكن لأسباب مختلفة. رأى الكود أن 2 تساوي 2، فاستخدم مساراً سريعاً لقص السلسلة النصية بناءً على الفهرس.
عندما قام بقص السلسلة عند الفهرس 3، أخذ الحرف الأول كاملاً ونصف الحرف الثاني فقط. أدى ذلك إلى ترك رمز بديل (surrogate) وحيد. وتظهر الطرفيات هذا على شكل مربع تالف.
أما أحرف الكانجي اليابانية الشائعة مثل 漢 فهي آمنة؛ إذ تمتلك وحدة رمز واحدة وعمودين. وبما أن 1 لا يساوي 2، يتجنب الكود الاختصار التالف. لا يظهر هذا الخطأ إلا مع الرموز النادرة والرموز التعبيرية (emojis).
لإصلاح ذلك، يجب عليك:
- حماية المسار السريع لرفض السلاسل النصية التي تحتوي على رموز بديلة عالية (high surrogates).
- الاقتطاع بناءً على نقاط الرمز الكاملة بدلاً من وحدات الرمز.
استخدام Array.from(str) يحل هذه المشكلة لأنه يقوم بالتكرار بناءً على نقاط الرمز، حيث يعامل الحرف كوحدة واحدة كاملة.
الدرس بسيط: لا تقم أبداً بالقياس بوحدة ما ثم القص بوحدة أخرى. إذا قمت بقياس عرض العرض ولكنك قمت بالقص بناءً على فهرس وحدة الرمز، فسوف تتلف بيانات مستخدميك.
اختبر الكود الخاص بك باستخدام حرف CJK نادر أو رمز تعبيري. لن تظهر لك رموز ASCII هذه الأخطاء. يجب عليك تقديم المدخلات التي يخشاها الكود الخاص بك.
المصدر: https://dev.to/greymothjp/a-width-check-said-the-string-was-safe-to-cut-it-split-a-kanji-in-half-4
