読者です 読者をやめる 読者になる 読者になる

Perl 5.19.2から $!が decodeされる

perl

ふと Perl 5.19.6をインストールして, Plackをインストールして
見るとテストがコケたので原因を調べていました. 原因は,
$!がデコードされていたことでした.

エラー

t/HTTP-Server-PSGI/harakiri.tがエラーとなります.
これには LWP::UserAgent->getが失敗することを期待する
テストが含まれています. LWP::UserAgent->getが失敗
したとき, LWP::UserAgentが内部で $!(実際は $@)を
HTTP::Message->contentに渡すのですが, HTTP::Messageは
バイト列以外を受け取ったときは dieするのでこのような
挙動となります.

% prove -bv t/HTTP-Server-PSGI/harakiri.t
t/HTTP-Server-PSGI/harakiri.t .. 
ok 1
ok 2
ok 3
ok 4
ok 5
# check that the server is dead
HTTP::Message content must be bytes at /home/syohei/perl5/perlbrew/perls/perl-5.19.7/lib/site_perl/5.19.7/LWP/UserAgent.pm line 1045.
# Tests were run but no plan was declared and done_testing() was not seen.
# Looks like your test exited with 255 just after 5.
Dubious, test returned 255 (wstat 65280, 0xff00)
All 5 subtests passed 

Test Summary Report
-------------------
t/HTTP-Server-PSGI/harakiri.t (Wstat: 65280 Tests: 5 Failed: 0)
  Non-zero exit status: 255
  Parse errors: No plan found in TAP output
Files=1, Tests=5,  0 wallclock secs ( 0.01 usr  0.00 sys +  0.11 cusr  0.02 csys =  0.14 CPU)
Result: FAIL

エラー表示が日本語でなく, ASCIIの人はエラーは起きないものと
思います.


バイト列, 文字列の違いについては下記等を参照してください

第16回 Perl内部構造の深遠に迫る(2):Perl Hackers Hub|gihyo.jp … 技術評論社

具体例

以下のような簡単なコードを用意します.

#!perl
use strict;
use warnings;

use 5.010;

open my $fh, '<', 'not_found_file';
say $!;

Perl 5.18.1, 5.19.7でそれぞれ実行した場合, 以下のような
出力になります.

% ~/perl5/perlbrew/perls/perl-5.19.7/bin/perl test.pl
Wide character in say at test.pl line 8.
そのようなファイルやディレクトリはありません
% ~/perl5/perlbrew/perls/perl-5.18.1/bin/perl test.pl
そのようなファイルやディレクトリはありません

Perl 5.19.7では, "$!"がデコードされていることがわかります.

解決案

Perl 5.19.2以降のみを使うという場合は以下のように,
$!を encodeすればよいでしょう(Encode::encode等他の方法もあります)

#!perl
use strict;
use warnings;

use 5.010;

binmode STDOUT, ":utf8";

open my $fh, '<', 'not_found_file';
say $!;

ただこのやり方の場合, Perl 5.19.1より前のバージョンも使う場合,
2重にエンコードすることとなり, 文字化けが起こるので注意が必要です.


他には Localeを強制する等が考えられますが, 基本的には "$!"等
Localeに依存するものはあまり利用しないということでしょうか.
例えば, $!を文字列としてチェックするのではなく, 数値すなわち
errnoとしてチェックするようにする等が考えられます.


しかしどうしても使いたいというシーンもあるので, 問題がありうる
ということは頭に入れておいた方がよいかと思います.


おわりに

Perl 5.19.2から $!が decodeされることについて示しました.


この変更により Plack以外にも, App::ack等いくつかテストが
failするようになったようです. バージョンを上げてテストが
こけるようになった場合, この問題も疑ってみるとよいかも
しれません.