ഒരു വിഡ്ത്ത് ചെക്ക് (Width Check) സ്ട്രിംഗ് മുറിക്കാൻ സുരക്ഷിതമാണെന്ന് കരുതി; എന്നാൽ അത് ഒരു കാഞ്ചിയെ (Kanji) പകുതിയായി പിളർന്നു.
ഒരു പേര് ടെർമിനൽ ടേബിളിൽ നൽകിയപ്പോൾ അത് തകരാറിലായി പുറത്തുവന്നു. കുടുംബപ്പേര് 𠮷田 എന്നതായിരുന്നു.
ആദ്യത്തെ അക്ഷരം സാധാരണയായി കാണുന്ന 吉 എന്നതല്ല. അത് 𠮷 (U+20BB7) ആണ്. യഥാർത്ഥ ജാപ്പനീസ് കുടുംബപ്പേരുകളിൽ ഉപയോഗിക്കുന്ന അപൂർവ്വമായ ഒരു രൂപമാണിത്. ഒരു കോളത്തിൽ ഒതുങ്ങാനായി ടേബിൾ ആ സെല്ലിനെ മുറിച്ചു (truncated). ഇത് ഒരു തകരാറിലായ അക്ഷരം അവശേഷിപ്പിച്ചു.
ഒരു വരി കോഡിലായിരുന്നു ഈ ബഗ് (bug) ഒളിഞ്ഞിരുന്നത്. ഇൻഡക്സ് (index) ഉപയോഗിച്ച് ഒരു സ്ട്രിംഗ് മുറിക്കുന്നത് സുരക്ഷിതമാണെന്ന് തീരുമാനിക്കുന്ന ഒരു ഒപ്റ്റിമൈസേഷൻ (optimization) ആയിരുന്നു അത്.
ഒരു ജാവാസ്ക്രിപ്റ്റ് (JavaScript) സ്ട്രിംഗിന് മൂന്ന് വ്യത്യസ്ത നീളങ്ങളുണ്ട്: • കോഡ് യൂണിറ്റുകൾ (Code units) (.length): "𠮷".length എന്നത് 2 ആണ്. • കോഡ് പോയിന്റുകൾ (Code points): [..."𠮷"].length എന്നത് 1 ആണ്. • ഡിസ്പ്ലേ വിഡ്ത്ത് (Display width): 𠮷 എന്നത് 2 കോളങ്ങൾ എടുക്കുന്നു.
സാധാരണ ഇംഗ്ലീഷ് ടെക്സ്റ്റുകളിൽ ഈ സംഖ്യകളെല്ലാം ഒന്നുതന്നെയാണ്. ഈ യാദൃശ്ചികത കോഡ് സുരക്ഷിതമാണെന്ന് തോന്നിപ്പിക്കുന്നു.
𠮷 എന്ന അക്ഷരം ഈ നിയമം ലംഘിക്കുന്നു. ഇതൊരു സർഗേറ്റ് പെയർ (surrogate pair) ആയതുകൊണ്ട് ഇതിന് 2 കോഡ് യൂണിറ്റുകളുണ്ട്. ഇതൊരു വൈഡ് ക്യാരക്ടർ (wide character) ആയതുകൊണ്ട് ഇത് 2 കോളങ്ങൾ എടുക്കുന്നു. സംഖ്യകൾ ഒന്നുതന്നെയാണെങ്കിലും (2 = 2), അവയുടെ കാരണങ്ങൾ വ്യത്യസ്തമാണ്.
cli-table3 എന്ന ലൈബ്രറി ഒരു 'ഫാസ്റ്റ് പാത്ത്' (fast path) ആണ് ഉപയോഗിച്ചത്: കോഡ് യൂണിറ്റ് നീളം ഡിസ്പ്ലേ വിഡ്ത്തിനോട് തുല്യമാണെങ്കിൽ, സ്ട്രിംഗിനെ ഇൻഡക്സ് ഉപയോഗിച്ച് മുറിക്കുക.
വർഷങ്ങളോളം ഇത് ശരിയായി പ്രവർത്തിച്ചു, കാരണം 漢 പോലുള്ള സാധാരണ ജാപ്പനീസ് അക്ഷരങ്ങൾക്ക് നീളം 1-ഉം വിഡ്ത്ത് 2-ഉം ആണ്. അവ ഒരിക്കലും ഈ ഫാസ്റ്റ് പാത്തിൽ എത്തിയിരുന്നില്ല.
𠮷 അല്ലെങ്കിൽ ഇമോജികൾ (emojis) പോലുള്ള അപൂർവ്വ അക്ഷരങ്ങളിൽ മാത്രമേ ഫാസ്റ്റ് പാത്ത് പ്രവർത്തിക്കുകയുള്ളൂ. ഈ അക്ഷരങ്ങൾക്ക് നീളം 2-ഉം വിഡ്ത്ത് 2-ഉം ആണ്. കോഡ് ഇവയെ ലളിതമായ ഒറ്റ യൂണിറ്റ് അക്ഷരങ്ങളായി കരുതുന്നു. ഇത് അവയെ ഇൻഡക്സ് ഉപയോഗിച്ച് പകുതിയായി മുറിക്കുന്നു. ഇത് ഒരു സർഗേറ്റ് (surrogate) മാത്രം അവശേഷിപ്പിക്കുന്നു. അതുകൊണ്ടാണ് ടെർമിനലിൽ ഒരു തകരാറിലായ ബോക്സ് കാണപ്പെടുന്നത്.
ഇത് പരിഹരിക്കാൻ നിങ്ങൾ ചെയ്യേണ്ടത്:
- സർഗേറ്റ് പെയറുകളെ ഒഴിവാക്കാൻ ഫാസ്റ്റ് പാത്തിനെ സംരക്ഷിക്കുക (guard).
- കോഡ് യൂണിറ്റുകൾക്ക് പകരം കോഡ് പോയിന്റുകൾ ഉപയോഗിച്ച് ട്രിം (trim) ചെയ്യുക.
Array.from(str) ഉപയോഗിക്കുന്നത് സഹായിക്കും, കാരണം അത് കോഡ് പോയിന്റുകൾ അടിസ്ഥാനമാക്കിയാണ് പ്രവർത്തിക്കുന്നത്. ഇത് ഒരു അക്ഷരത്തെ പകുതിയായി മുറിക്കില്ലെന്ന് ഉറപ്പാക്കുന്നു.
പാഠം ലളിതമാണ്: ഒരു യൂണിറ്റ് ഉപയോഗിച്ച് അളക്കുകയും മറ്റൊരു യൂണിറ്റ് ഉപയോഗിച്ച് മുറിക്കുകയും ചെയ്യരുത്. നിങ്ങൾ ഡിസ്പ്ലേ വിഡ്ത്തോ കോഡ് പോയിന്റുകളോ ആണ് അളക്കുന്നതെങ്കിൽ, അതേ യൂണിറ്റുകൾ ഉപയോഗിച്ച് തന്നെ മുറിക്കണം.
ഒരു CJK Extension B അക്ഷരമോ ഇമോജിയോ ഉപയോഗിച്ച് നിങ്ങളുടെ കോഡ് പരിശോധിക്കുക. ASCII ഉപയോഗിച്ച് ഈ ബഗ് ഒരിക്കലും കണ്ടെത്താൻ കഴിയില്ല.
Optional learning community: https://greymoth-jp.github.io/cjk-failure-corpus/
