एक विड्थ चेक (Width Check) ने कहा कि स्ट्रिंग को काटना सुरक्षित है। इसने एक कांजी (Kanji) को बीच से दो हिस्सों में बाँट दिया।
एक नाम टर्मिनल टेबल में दर्ज किया गया और वह टूटा हुआ निकला। उपनाम 𠮷田 था।
पहला अक्षर सामान्य 吉 नहीं है। यह 𠮷 (U+20BB7) है। यह वास्तविक जापानी पारिवारिक नामों में उपयोग किया जाने वाला एक दुर्लभ रूप है। टेबल ने कॉलम में फिट होने के लिए सेल को ट्रंकेट (truncate) कर दिया। इसके कारण एक टूटा हुआ अक्षर पीछे रह गया।
यह बग कोड की एक ही लाइन में छिपा था। यह एक ऑप्टिमाइज़ेशन (optimization) था जिसने यह तय किया कि स्ट्रिंग को इंडेक्स (index) के आधार पर काटना सुरक्षित है।
एक JavaScript स्ट्रिंग की तीन अलग-अलग लंबाई होती हैं: • कोड यूनिट्स (Code units) (.length): "𠮷".length 2 है। • कोड पॉइंट्स (Code points): [..."𠮷"].length 1 है। • डिस्प्ले विड्थ (Display width): 𠮷 2 कॉलम घेरता है।
मानक अंग्रेजी टेक्स्ट के लिए, ये सभी संख्याएँ एक समान होती हैं। यह संयोग कोड को सुरक्षित दिखाता है।
अक्षर 𠮷 इस नियम को तोड़ता है। इसमें 2 कोड यूनिट्स हैं क्योंकि यह एक सरोगेट पेयर (surrogate pair) है। इसमें 2 कॉलम हैं क्योंकि यह एक वाइड कैरेक्टर (wide character) है। संख्याएँ मेल खाती हैं (2 = 2), लेकिन अलग-अलग कारणों से।
लाइब्रेरी cli-table3 ने एक फ़ास्ट पाथ (fast path) का उपयोग किया:
यदि कोड यूनिट की लंबाई डिस्प्ले विड्थ के बराबर है, तो स्ट्रिंग को इंडेक्स द्वारा काटें।
यह सालों तक काम करता रहा क्योंकि 漢 जैसे सामान्य जापानी अक्षरों की लंबाई 1 और विड्थ 2 होती है। वे कभी भी फ़ास्ट पाथ तक नहीं पहुँचते।
फ़ास्ट पाथ केवल 𠮷 या इमोजी जैसे दुर्लभ अक्षरों के लिए ही ट्रिगर होता है। इन अक्षरों की लंबाई 2 और विड्थ 2 होती है। कोड सोचता है कि वे साधारण एक-यूनिट वाले अक्षर हैं। यह उन्हें इंडेक्स के आधार पर आधा काट देता है। इससे एक अकेला सरोगेट (surrogate) पीछे रह जाता है। यही कारण है कि टर्मिनल में एक टूटा हुआ बॉक्स दिखाई देता है।
इसे ठीक करने के लिए, आपको चाहिए:
- सरोगेट पेयर्स को बाहर करने के लिए फ़ास्ट पाथ को सुरक्षित (guard) करें।
- कोड यूनिट्स के बजाय कोड पॉइंट्स द्वारा ट्रिम (trim) करें।
Array.from(str) का उपयोग करने से मदद मिलती है क्योंकि यह कोड पॉइंट द्वारा इटरेट (iterate) करता है। यह सुनिश्चित करता है कि आप कभी भी किसी अक्षर को आधा न काटें।
सबक सरल है: कभी भी एक यूनिट से मापें और दूसरी यूनिट से काटें नहीं। यदि आप डिस्प्ले विड्थ या कोड पॉइंट्स मापते हैं, तो आपको उन्हीं यूनिट्स का उपयोग करके काटना चाहिए।
अपने कोड का परीक्षण CJK Extension B कैरेक्टर या इमोजी के साथ करें। ASCII इस बग को कभी उजागर नहीं करेगा।
Optional learning community: https://greymoth-jp.github.io/cjk-failure-corpus/
