2013/12/07 追記: Ruby 2.1.0 には不正バイトを除去する専用のメソッド String#scrub が追加されています。詳しくは Ruby 2.1.0 に追加される String#scrub の紹介 を参照
こんにちは @sonots です。
Ruby の invalid byte sequence in UTF-8 例外を encode("UTF-8", "UTF-8") で回避するのはおかしいよ、という話をします。
Ruby 1.9 でUTF-8的に正しくないバイト列がある文字列を扱っていると、正規表現マッチや gsub といったメソッドを使っているところで ArgumentError: invalid byte sequence in UTF-8 例外が発生します。文字列を生成したときではなくて正規表現マッチなんかをしたときに始めてエラーが出るのですが、その辺の話については @tmtms さんのブログの記事が大変詳しくて勉強になるのでそちらを見るとよいかと思います。
再現コードはこんなかんじですね。
1. Ruby の String#encode メソッドは dst_encoding と src_encoding に同じエンコードを指定すると変換処理を行いません。
Ruby Doc に書かれていないのでちょっと不親切な気がしないでもないですが、そういう仕様になっています。不正な文字列を replace する処理も行わないということですね。コードにするとこんなかんじですかね。
2. String#encode("UTF-8", "UTF-8") で invalid byte sequence 例外が出なくなるのは Ruby のバグです。
チケットもあがっているようです。https://bugs.ruby-lang.org/issues/6190
ということを @repeatedly さんが成瀬さんに聞いてくださいました ^ ^
追記: @n0kada さんによると 2.0 系統ではすでに修正済みとのことです!
こんなかんじになりますかね # invalid byte を除去する専用のメソッドが欲しいですね ...
最後に
ちなみにここで @repeatedly さんが話している pull req をくれた人、というのが私のことですね ^ ^; 例外を回避するためにこの処理をいれたコードを @tagomoris さんの fluent-plugin-parser のほうに pull request を送っておりまして(コレ)、ツッコミを受けたという話でした。
バグだとは気付きませんで、テスト通ってるし、あってるんじゃない?とか浅い考えをしておりました。すみません。そういう深い所まで見れるようになっていかないといけませんね。精進いたします。
@tagomorisさん、@repeatedlyさん、@nalshさんありがとうございました!
あと、修正版の pull req 送ってあるのでよろしくおねがいいたします :D > @tagomorisさん
こんにちは @sonots です。
Ruby の invalid byte sequence in UTF-8 例外を encode("UTF-8", "UTF-8") で回避するのはおかしいよ、という話をします。
Ruby 1.9 でUTF-8的に正しくないバイト列がある文字列を扱っていると、正規表現マッチや gsub といったメソッドを使っているところで ArgumentError: invalid byte sequence in UTF-8 例外が発生します。文字列を生成したときではなくて正規表現マッチなんかをしたときに始めてエラーが出るのですが、その辺の話については @tmtms さんのブログの記事が大変詳しくて勉強になるのでそちらを見るとよいかと思います。
再現コードはこんなかんじですね。
str = "\xff" str.force_encoding('UTF-8') begin str =~ /hoge/ rescue => e p e # ArgumentError: invalid byte sequence in UTF-8 endそこでどう解決するのかぐぐるわけですが、"invalid byte sequence in UTF-8" で検索すると、
str = str.encode("UTF-8", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?')のようにすると回避できるよ、という記事がいくつかヒットします。ですがそれは間違いです(ブログを晒すのははばかられるのでリンクは貼りませんが、ぐぐるとすぐみつかるでしょう)。これには2つの間違いが含まれています。
1. Ruby の String#encode メソッドは dst_encoding と src_encoding に同じエンコードを指定すると変換処理を行いません。
Ruby Doc に書かれていないのでちょっと不親切な気がしないでもないですが、そういう仕様になっています。不正な文字列を replace する処理も行わないということですね。コードにするとこんなかんじですかね。
str = "\xff" str.force_encoding('UTF-8') str = str.encode("UTF-8", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?') p str #=> "\xff" のまま begin str =~ /hoge/ rescue => e p e endそこについては、そういう仕様なんですが、実行してみた人はあれ?と気付くでしょう。そう、例外が起きなくなるのです。正確には encode("UTF-8", "UTF-8") だけでもOKです。
str = "\xff" str.force_encoding('UTF-8') str = str.encode("UTF-8", "UTF-8") begin str =~ /a/ rescue => e # No throw!! p e endこの性質を逆手におって、String#encode を呼べば例外が出なくなるよ、とバッドノウハウを教えているブログも見つかります(例によってリンクは貼りませんがぐぐると見つかるでしょう)。これが2つ目の間違いです。
2. String#encode("UTF-8", "UTF-8") で invalid byte sequence 例外が出なくなるのは Ruby のバグです。
チケットもあがっているようです。https://bugs.ruby-lang.org/issues/6190
ということを @repeatedly さんが成瀬さんに聞いてくださいました ^ ^
@repeatedly バグですね。Stringがvalidかのキャッシュがあるんですが、誤った値をセットしちゃってるんでしょう
— 成瀬さん (@nalsh) 2013年2月15日
ということでこの性質を利用するのは間違いです。
@repeatedly 気付いてしまったので2.0や次の1.9.3 patch releaseでは例外が上がることになるとおもいまする
— 成瀬さん (@nalsh) 2013年2月15日
とのことで 2.0や次の1.9.3 patch release からはおそらく例外があがることになると思われますので、そのようなコードを書いてしまっている方は今のうちに直しておきましょう!追記: @n0kada さんによると 2.0 系統ではすでに修正済みとのことです!
こんなかんじになりますかね # invalid byte を除去する専用のメソッドが欲しいですね ...
str = "\xff" str.force_encoding('UTF-8') str = str.encode("UTF-16BE", "UTF-8", :invalid => :replace, :undef => :replace, :replace => '?').encode("UTF-8") p str #=> "?" begin str =~ /a/ rescue => e p e end2012/03/22 に起票されたチケットなので、けっこう前からあるバグで、ぐぐった感じだとバッドノウハウ化しているようなので今のうちに警鐘をならす意味でブログを書きました。
最後に
ちなみにここで @repeatedly さんが話している pull req をくれた人、というのが私のことですね ^ ^; 例外を回避するためにこの処理をいれたコードを @tagomoris さんの fluent-plugin-parser のほうに pull request を送っておりまして(コレ)、ツッコミを受けたという話でした。
バグだとは気付きませんで、テスト通ってるし、あってるんじゃない?とか浅い考えをしておりました。すみません。そういう深い所まで見れるようになっていかないといけませんね。精進いたします。
@tagomorisさん、@repeatedlyさん、@nalshさんありがとうございました!
あと、修正版の pull req 送ってあるのでよろしくおねがいいたします :D > @tagomorisさん