Last Modified:
Carp-1.26を使う場合はメッセージとバックトレースのエンコーディングに気を付ける #Perl
- Carp-1.26
@lestrratに助けて頂きました。ありがとうございます。id:gfxの指摘のおかげで真実に気付けたのも感謝。
深追いしたら理由がわかったからバグ報告しておいた https://t.co/cZP2hR4Z9o RT @robario: @__gfx__
— Daisuke Maki (@lestrrat) July 30, 2013
結論としては全然不思議なことではなく、単にCarpのバグでした。
Carpは与えられたメッセージにバックトレースを連結するので、こういう風に書くと
sub p {
carp( encode_utf8(shift) );
}
p(decode_utf8('テスト'));
- メッセージ:
「encodeされた'テスト'」 at foo line 2
- バックトレース:
「main::p(decodeされた'テスト')」 called at foo line 4
が連結されるので文字化けしてしまう。
encoding::warnings
をuse
すると
Bytes implicitly upgraded into wide characters as iso-8859-1 at foo line 2
という警告が出るので、良い子のみんなはちゃんとuse encoding::warnings
するんだぞ☆(ゝω・)v
(普段はuse encoding::warnings
してるんだけど、使い捨てスクリプトでこの現象に出逢ってしまったのでうんぬんかんぬん。言い訳)
EncodeとCarpの不思議な挙動 #Perl
- perl-v5.10.1, perl-v5.16.3
- Carp-1.26
- Encode-2.35, Encode-2.51
コメントを頂いて再調査したところ、use utf8
は確かに関係ありませんでした。どうもありがとうございます>id:gfx
ちょっと良く分からないのですが「サブルーチン内で文字列をencode_utf8
してcarp
に渡すと文字化けする」という挙動になっていました。
さっぱり意味が分からないので誰か助けてください。
関数内でencode_utf8(再現コード)
my $s = decode_utf8('テスト');
my $t = encode_utf8($s);
warn($t); # ok
carp($t); # ok
sub p {
my $u = encode_utf8(shift);
warn($u); # ok
carp($u); # ng
}
p($s);
結果、sub p
内のcarp
だけ2回encode_utf8
をしたバイト列になります。
$ perl-v5.10.1 -MCarp=carp -MEncode=encode_utf8,decode_utf8 carp.pl
テスト at carp.pl line 4.
テスト at carp.pl line 5
テスト at carp.pl line 9.
ãã¹ã at carp.pl line 10
main::p('テスト') called at carp.pl line 12
$ perl-v5.16.3 -MCarp=carp -MEncode=encode_utf8,decode_utf8 carp.pl
テスト at carp.pl line 4.
テスト at carp.pl line 5.
テスト at carp.pl line 9.
Wide character in warn at /usr/local/lib/perl5/Carp.pm line 102.
ãã¹ã at carp.pl line 10.
main::p('テスト') called at carp.pl line 12
というわけでEncode
がおかしいのかな?でもwarn
だと問題無いのが気になる。
関数外でencode_utf8(意図した結果)
予めエンコードしてあると、サブルーチン内でも問題無いのです。
my $s = decode_utf8('テスト');
my $t = encode_utf8($s);
warn($t); # ok
carp($t); # ok
sub p {
my $u = shift; # ここじゃなくて
warn($u); # ok
carp($u); # ok
}
p( encode_utf8($s) ); # ここでエンコード
結果
$ perl-v5.10.1 -MCarp=carp -MEncode=encode_utf8,decode_utf8 carp.pl
テスト at carp.pl line 4.
テスト at carp.pl line 5
テスト at carp.pl line 9.
テスト at carp.pl line 10
main::p('\x{e3}\x{83}\x{86}\x{e3}\x{82}\x{b9}\x{e3}\x{83}\x{88}') called at carp.pl line 12
$ perl-v5.16.3 -MCarp=carp -MEncode=encode_utf8,decode_utf8 carp.pl
テスト at carp.pl line 4.
テスト at carp.pl line 5.
テスト at carp.pl line 9.
テスト at carp.pl line 10.
main::p('\x{e3}\x{83}\x{86}\x{e3}\x{82}\x{b9}\x{e3}\x{83}\x{88}') called at carp.pl line 12
関係無いはずの関数呼び出し引数の有無で結果が変わる不思議
もっと不思議な事に、引数を使わない関数にしておくと、呼び出し時の引数の有無で結果が変わるようで。
my $s = decode_utf8('テスト');
sub p { carp( encode_utf8($s) ); }
p(); # ok
p($s); # ng
↓
$ perl -MEncode -MCarp=carp -MEncode=encode_utf8,decode_utf8 carp.pl
テスト at carp.pl line 2
main::p() called at carp.pl line 3
ãã¹ã at carp.pl line 2
main::p('テスト') called at carp.pl line 4
えー・・・?
use utf8していないとCarpの動きがおかしい #Perl
- Carp-1.26
どうにも日本語を渡した時の挙動が変だったので、ソース見たらこんなことになってた。
BEGIN {
no strict "refs";
if(exists($::{"utf8::"}) && exists(*{$::{"utf8::"}}{HASH}->{"is_utf8"}) &&
defined(*{*{$::{"utf8::"}}{HASH}->{"is_utf8"}}{CODE})) {
*is_utf8 = \&{"utf8::is_utf8"};
} else {
*is_utf8 = sub { 0 };
}
}
use utf8
してなかったら必ず偽を返すis_utf8
を定義するんだと。
いやいや確かにuse utf8
してないけど、それはソースコードがASCIIなだけであってCarpに渡す文字列がどうとか関係ないじゃん。
「ソースコードにuse utf8
が無くても、引数にUTF8 flagが付いている場合(ファイルなどの外部環境から取得した文字列)」が考慮されていないっぽい。
更に実際にis_utf8
が使われている箇所を見てみたら
# The following handling of "control chars" is direct from
# the original code - it is broken on Unicode though.
# Suggestions?
is_utf8($arg)
or $arg =~ s/([[:cntrl:]]|[[:^ascii:]])/sprintf("\\x{%x}",ord($1))/eg;
って…おいっ!┌(`Д´)ノ)゚∀゚)
他にdowngrade
も似たような感じになってて多分そこもおかしな動きになると思う。
普通にCarp
側でutf8::is_utf8
使えばいいと思うんだけど、何でこんなことしてるのかなぁ。。。
さて、どうしたもんかな…