$30 off During Our Annual Pro Sale. View Details »

治っていくmbstring ㋿時代の文字化け

治っていくmbstring ㋿時代の文字化け

繝「繧ク繝舌こ (モジバケをShift_JISで読んだときの化け方)

文字化けとは↑のようなことを差しているように思われますが、
文字化けに悩まされた時代の文字化けはこんなものではなかったように思います。

例えば、Shift_JISではたくさんの亜種が生まれていました。
①は機種依存文字だから使ってはいけないよとか言われました。
メールをJIS(ISO-2022-JP)で送信する際の関数はmb_send_mailの前にmb_languageを設定するのだっけ?

閑話休題。
PHP 8.1から、major overhaul of mbstringという、mbstring拡張の大規模な改修が反映されるようになってきました。
そのためか、後方互換性の失われた動作をする文字を見つけてIssueにて報告しました。
確かに仕様通りに実装するとそうだったけども、当時の実装はそんなに厳密じゃなかったがゆえの後方互換性の破壊だったようです。こういうことこそが文字化けな気がしてきますね。

このトークでは、上記のようなことがあったことから、文字コードがどのように扱われていたのかをおさらいし、きちんと記録に残しておきたいです。

https://fortee.jp/phpcon-2022/proposal/96ddbb63-9f69-4e87-b055-569456c8677d

てきめん tekimen

September 24, 2022
Tweet

More Decks by てきめん tekimen

Other Decks in Programming

Transcript

  1. 治っていくmbstring 時代の文字化け ㋿ PHP 8.1からの変更点について

  2. 自己紹介 てきめん • https://tekitoh-memdhoi.info • @youkidearitai • https://www.youtube.com/user/tekit ohmrp •

    https://www.nicovideo.jp/user/29577 48 • https://github.com/youkidearitai • 今回で5つ目のIssueをphp-srcに送り込 んだ オレ
  3. mbstringとは • PHPのマルチバイト文字の拡張(extension)で、C言語で書かれている • 内部ではlibmbflというLGPLのライブラリが使われている – 元は https://github.com/moriyoshi/libmbfl だが、もはや似て非なるものになっている –

    1990年代後半から存在する、文字コード処理の一つの回答であるといえる • どうやら、PHP3の頃に漢字パッチというものがあったらしい(元をたどるのは困難) • 近年、major overhaul of mbstringという大規模改修をしていて、PHP 8.1から反映され始めている • 最早日本語だけでなく、世界中の文字を扱っている拡張 – PHP 8.1.8 ChangeLogより • mb_detect_encoding recognizes all letters in Czech alphabet (チェコ語のアルファベットの検出) • mb_detect_encoding recognizes all letters in Hungarian alphabet (ハンガリー語のアルファベットの検出)
  4. ASCIIについて • 当初はASCII(American Standard Code for Information Interchange)がアメリカで開発された、7ビットの1バイトコー ド •

    これをベースにISO/IEC 646が策定され各国で規格化され る。日本版はJIS X 0201(旧JIS C 6220) • ASCIIはISO/IEC 646国際基準版となるため、現在ではASCII を国際基準と思ってもらって差し支えないはず
  5. ISO/IEC 2022 • 文字集合を7ビットもしくは8ビットで表現する – CL、GL、CR、GRという概念が出てくる、Cは制御文字、Gは図形文字。LとRは第8ビットの フラグで0がL、1がR • 各国の文字を収録することが可能になり、切り替えることも可能 –

    ISO-2022-JPがこれ(によく似ている) CLとGL領域を組み合わせて使う7ビット符号化方式 • 複数バイトの文字集合を作ることも可能 – EUC-JPがこれ (ASCIIとの互換のため、GLをASCII、GR領域を複数バイトとしている)
  6. ISO/IEC 8859 • 欧州のまとまった地域では8ビットの1バイトコードで一つの文 字集合でまとめることになる • GL領域にはASCIIを、GR領域にまとめた文字を収録という形 でパートごとに分けていった – ISO/IEC

    8859-1などと、10以上のパートが作られていく • パートをまたがると結局ISO/IEC 646の時代と変わりなくない!?となる – ちなみに ISO/IEC 8859-1は別名Latin-1と言う
  7. PHP 8.1とISO-8859-1 • PHP8.1: mb_convert_encoding not working with ASCII chars

    a bove 127 #8744 • mb_convert_encoding ( chr(252), 'UTF-8', 'ASCII' ); がüを出力し ないというIssue – ここまでみれば分かる通り、$from_encodingにASCIIはおかしいですよね? – ところがPHP 8.1の前まではこれが変換できてしまっていた!! • üはISO/IEC 8859-1に収録されているので、$from_encoding に'ISO-8859-1'を指定する必要がある • こういうところが治っているのがPHP 8.1のmbstring
  8. JIS X 0201(旧 JIS C 6220)とは • 7ビット及び8ビットの情報交換用符号化文字集合 • ラテン文字集合とカタカナ集合(半角カタカナ)の2つの文字符

    号化集合を持っている。 • ラテン文字集合部分では、0x5Cが¥に、0x7Eが‾になっている – JIS X 0201では‾は~(チルダ)にしてもよいとなってる JIS X 0201:1997「7ビット及び8ビットの情報交換用符号化文字集 合」(日本産業標準調査会、経済産業省) • 現在でもShift_JISのベースになっている – つまり0x5Cと0x7Eが受け継いでいる
  9. JIS X 0208(旧JIS C 6226)とは • 日本語版のISO/IEC 2022準拠した、漢字・ひらがな・カタカナなどを収録した2バイ トの符号化文字集合 •

    漢字の収録は、第一水準、第二水準と分けられた – 6879文字の図形文字を収録 • これをもとに様々な符号化形式が生まれた – Shift_JIS – EUC-JP – ISO-2022-JP – 更に詳しく説明しだすと完全に歴史の話になるので割愛
  10. Shift_JISの生まれ • 1980年代に実装された文字コード – 初めての製品は三菱電機のMULTI 16 (1982年) • OSはデジタルリサーチのCP/M-86(Shift_JISの実装はマイクロソフトウェアアソシエイツ) •

    当時は1区1点(全角スペース)に2020(半角スペース2文字分)を割り当てていた部分が独 特(プログラミングでParse Errorにならないというメリットがある…!?) – MS-DOSでは1983年 • 「MS 漢字コード」と言われることもある。コードページ932 • 1区1点が8040に割り当てられた。こちらがShift_JISでは?と思う人も多いのでは?(少なく とも私はそう思う、「 」を打ってParse Errorになる方だし) Shift_JISは最初から違いが生まれている
  11. 外字について • 各ベンダーが外字という独自の領域を割り当てて独自の文字 を出すという事が行われた • そのことを知らない環境では文字が化ける • 例えば、Shift_JISでいう、①(CP932)が㈪(MacJapanese)にな るなどがある •

    当初は通信しなかったので問題なかったが、インターネットなど で通信するようになると文字化けするようになった
  12. 0x5Cと0x7EはASCIIと互換がない問題 • Shift_JISの0x5Cは¥が登録されている、なぜならばJIS X 0201で定められているから • Shift_JISの0x7Eは‾もしくは~が登録されている、なぜならばJIS X 0201で定められ ているから

    • しかしながら、我が国では「\は¥として表示されることが多い」となどとされているが、 ¥100が\100として表示されることもあるのはそのせい • この挙動はブラウザや実行環境、フォントに依存している – 例:iconvで変換すると¥‾として変換される $ echo '\~' | iconv -f sjis -t utf8 ¥‾
  13. Shift_JISの0x5Cと0x7Eはどうすればいい • 使う環境によるしかないというくらい曖昧 – なぜかというと、JIS X 0201があるのに、MS 漢字コードを使うユー ザーがあまりにも多かった(デファクトスタンダード) •

    殆どは、1バイトコードの領域に手を出さずにそのまま変換して いる実装が多いと感じる – 0x5CをそのままU+005Cとして変換 – 0x7EをそのままU+007Eとして変換
  14. Shift_JISが曖昧に生まれる 様々なベンダーから 独自のSJIS実装が生まれる JIS X 0208にて標準化 IANAにShift_JISという 名前が登録される 非常に大雑把なShift_JISの経緯 1.様々な独自のShift_JISが生まれる

    • 例えばMS 漢字コード、MacJapanese、NEC MS- DOS Codepage 932、IBM拡張文字、…無数に生ま れる 2.JIS X 0208にて標準化 3.IANAにShift_JISという名前が登録される 4.とどまることを知らず、独自実装が追加される 例:フィーチャーフォンに絵文字が実装される 5.(JIS X 0213を収録したShift_JIS-2004が実装される) Shift_JISを規格から実装しようとすると、実際に使わ れない変換になったり、文字化けしたりする可能性が あることにもなる 独自の実装が生まれ続ける
  15. ISO/IEC 10646とUnicode • 大量の文字コードがあっても、互換性がなかったら意味がないので、一つの文字集 合でまとめてしまおうと言う試み • Unicodeは16ビットでまとめようとし、ISO/IEC 10646は31ビットでまとめようとした – 結果、Unicodeの設計を組み込みながら、4バイトでの収録となる

    – 最終的にUnicodeとISO/IEC 10646は同じ規格となるようにしている • ざっくりと110万文字くらいは収録できそうで、そのうち13万文字くらい(Unicode 12.1)収録しているので、当分大丈夫そう – 「我々は」あふれる心配はしなくて良さそう • 既存の文字コードの相互変換もある程度想定されている
  16. UTF-8について • ASCIIとの互換がありながら、Unicodeを扱えるも のすごく便利な文字コード – ASCIIは1バイトとして扱い、他の文字を2~6バイトで扱 う • ほとんどの新規プロジェクトではこの文字コードを 使えばほとんど問題なしっていう認識

  17. PHP 8.1が日本語に与えた影響 • mb_convert_encoding(“~\\”, “UTF-8”, “SJIS”)で、〜\が返ってくるよ うになる https://3v4l.org/nSVPB – これはSJISとして使われないとIssueを出す

    • https://github.com/php/php-src/issues/8281 • 0x5C、0x7Eのこの変換はJIS X 0201からすると正しい変換方法である。 • IANAのShift_JIS登録からしてもこうであるだろう。 http://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/SHIFTJIS.TXT – 日本人から見ると半角が全角になっていることから、特に異質に見えてしまう • いろいろな意見を聞いてみても、やはりレガシーエンコーディングであり、 後方互換性を維持して欲しいというのが総意と判断し報告、修正いただく
  18. Shift_JIS-2004について • JIS X 0213の符号化方式の一つで、Shift_JISの上方互換 – 外字部分を確保したため、Shift_JISの亜種は全く互換性がない • 例えば「﨑」が使える(第三水準・第四水準漢字のサポート) –

    「髙」は使えない、MS 漢字コード(CP932)ならば使える • PHPでは”SJIS-2004”として使える • mb_convert_encoding(“~\\”, “UTF-8”, “SJIS-2004”) とすると~\が返ってくる https://3v4l.org/83Bam – 5.4からの実装で、8.0まで‾¥が返ってくる(これはSJISと違うのはどうして?) – これも後方互換性の問題で‾¥に戻してもらいます mb_convert_encoding "\" (backslash) and "~" (tilde) BC breaks to Shift_JIS-2004
  19. PHP 8.1が日本語に与えた影響 • The return value of mb_list_encodings() doesn't contain

    'SJIS- win' – SJISとCP932はmb_list_encodingsにあるが、SJIS-winになくなっ ているということに気がついてもらった – CP932はeucJP-winと同じマッピング、U+00A5に全角円記 号、U+203Eに全角オーバーラインを対応付けていた – SJIS-winはU+00A5に半角円記号、U+203Eに半角チルダを割り当 てる必要があったので作られた – PHP 8.1で再統合されたが、BC breakなので修正して頂いた BC(Backward Compatible) break → 後方互換性の破壊
  20. mbstring内でバグを見つけたら • 文字コードは、公開されている仕様が正となるように実装されている – Shift_JISであればIANAなどが参照されている • しかし、Shift_JISの産まれ、使われている経緯からすると仕様の参照から実装というのは 不自然に思えるケースがある – つまり、使っている文字コードが違う使われ方をしているなどがある場合は一人

    で指摘してもなかなか通じない – 残念ながら、その場合は協力者を募って「こう使われてる」と説得するしかない、 一人で説得するのは難しかった – 世界中でバラバラだった文字コードのこういうところは正直仕方がない感
  21. mbstring拡張の開発プロセス • 通常のRFCのプロセスを通していない • メンテナー(Alex Dowadさん)がmajor overhaul of mbstringとい うプルリクエストにひたすら開発している

    • レビュアー(主にNikita Popovさん)もいる • Alex Dowadさん曰く「このうさぎの穴は深く、より深くなっています」 • いつ、どのタイミングでPHP本体に入るのかわかりかねている(それ で、PHP 8.1に入ってるのに気づいた)
  22. Major overhaul of mbstringの内容 • 不要なコードの削除 • 曖昧なコードを読みやすくする • トリッキーな部分をコメントに残す

    • 高速化 • バグ修正 • テストカバレッジの改善 • セマンティクスの調整 Major overhaul of mbstring (part 1) によると、これら全部をやろうとした が、大きすぎたので分割している(と私は解釈している) 大きすぎて全然わからない…
  23. どんどんと治っていくmbstring • 先述した問題を修正しているように、major overhaul of mbstringはいい方向に向 かっている • 現在、major overhaul

    of mbstringはPart 26(2022/09/24現在)まで進んでお り、PHP 8.1.9で反映されているのはPart 18まで – Major overhaul of mbstring (part 18) • part 18では、mb_convert_encoding、mb_strlen、mb_strwidthの高速化が図られた • Unicodeによる文字数の数え方が治っている – var_dump(mb_strimwidth(" ", 1, 2)); ‍ ‍ ‍ 👨‍👩 ‍👦 ‍👦 などで、 ZERO WIDTH JOINERが含まれているケースを 考慮するようになった https://3v4l.org/gJTee
  24. mb_convert_encodingなどの高速化 Major overhaul of mbstring (part 18) #7659 より引用、高速化されている

  25. PHP 8.1で他気をつけること • mb_convert_encoding(“a”, “UTF-8”, “UTF-16”); などで、明らかにおかしいエ ンコーディングがあった場合にmb_substitute_characterの設定値を出すよう になった https://3v4l.org/QR059

    • mb_strcutの挙動にバグがあった、内部で切り出したバイトにおかしいバイトが 発生し?に変換される。これは修正される予定 – The behavior of mb_strcut in mbstring has been changed in PHP8.1 # 9535 – ただ、mb_substitute_characterがサポートしていないmb_strcut関数は不 正なバイトがあると必ず?に変換されるようになっている • どうしてmb_substitute_characterの設定値じゃないの?と質問してみた • 多分他にもたくさんある
  26. まとめ😫 • Major overhaul of mbstringはかなり手が入っており、すごく良くなっている – しかし、修正されている内容がユーザーに伝わっている状態ではないのが惜しい – ドキュメンテーションを充実させればこのあたり伝わる?

    • 変更された箇所がまだよくわかってない… • Shift_JIS何もわからなくなってきた – 長い歴史を持つPHPのmbstringの実装は一つの回答ではある – Shift_JISと言ってやり取りしていたものは、実はMS 漢字コードだった気がする – 日本人的にこれは使われないですっていうのは声を上げていくべきだと思う – Shift_JISは最初からすべてバラバラだった経緯から、標準から参照して実装すると思わぬ落とし穴がある – Shift_JISはレガシーエンコーディングでいいですよね?終活を考えるべきかも • mbstringの一部しか取り上げられなかったが、mbstring全部手を入れて沼に沈みましょう、次の 文字化け(Unicode)が待ってるぜ
  27. 参考文献 • moriyoshi/libmbfl • Major overhaul of mbstring (part 1)

    #6052 • mb_convert_encoding "\" (backslash) and "~" (tilde) convert failed to Shif t_JIS • 長谷川均 岡崎健:漢字CP/Mのコード体系 情報処理学会マイクロコンピュータ研 究会資料 26-2 (1983年3月7日) • プログラマのための文字コード技術入門 初版・第二版 • JIS X 0201:1997「7ビット及び8ビットの情報交換用符号化文字集合」(日本産業 標準調査会、経済産業省) • IANAによるShift_JISの登録
  28. 参考文献 • The return value of mb_list_encodings() doesn't contain 'SJIS-win'

    – Reintroduce legacy 'SJIS-win' text encoding in mbstring • PHP 3 漢字パッチ (Internet Archiveより) • @moriyoshitさんのツイート • PHP重鎮の廣川類氏によるコラム「PHPの最新状況:PHP 8.2採用の機能が決ま る」(第25回) – PHP 8.1のmbstringの修正の解説がある • Windows 用の日本の新元号対応更新プログラムについて - KB4469068 – は ㋿ Shift_JIS(CP932)には対応しないと明言がある • 文字符号の歴史 欧米と日本編 ISBN978-4-320-12102-7 • UTF-8で動くRailsがShift_JISな外部システムと通信する方法 - BOOK☆WALKE R Tech Blog
  29. 余談 • 20年ものの巨大レガシープロダクトをPHP 8.0にアップデートした際の対策と得ら れた知見 のスライドを拝見して、PHP 8.xのマイグレーションガイドを参考にしているという ところを読んで、「このスライドにある殆どのBC breaks、変更、修正、改善がマニュ アルどころか、ChangeLogにもなく、GitかGitHubにしか反映されていない」という

    この状況が「ヤバい」と思うようになりました。