記事

Last Modified:

Carp-1.26を使う場合はメッセージとバックトレースのエンコーディングに気を付ける #Perl

@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('テスト'));

が連結されるので文字化けしてしまう。

encoding::warningsuseすると

Bytes implicitly upgraded into wide characters as iso-8859-1 at foo line 2

という警告が出るので、良い子のみんなはちゃんとuse encoding::warningsするんだぞ☆(ゝω・)v

(普段はuse encoding::warningsしてるんだけど、使い捨てスクリプトでこの現象に出逢ってしまったのでうんぬんかんぬん。言い訳)


EncodeとCarpの不思議な挙動 #Perl

コメントを頂いて再調査したところ、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

どうにも日本語を渡した時の挙動が変だったので、ソース見たらこんなことになってた。

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使えばいいと思うんだけど、何でこんなことしてるのかなぁ。。。

さて、どうしたもんかな…