Data::Dumper が壊れているという惨事

あるいは PERL_CPANM_OPT=-n は危険だという話。

それはこのtweetから始まった。

結局これは Data::Dumper 2.143 が壊れていたからなのだが、そこに至るまでがちょっと大変だったので記録しておく。

まず、再現スクリプトは以下のものだった。

# executed in perl 5.10.1
use strict;
use warnings;
 
use Data::Dumper;
use Text::Xslate;
 
my $tx = Text::Xslate->new({
  syntax => 'TTerse'
});
 
my $template = <<TMPL;
[% value | dump %]
TMPL
 
my $value = +{
	'ab' => 'c',
};
 
print Dumper($value);
 
my $ret = $tx->render_string($template, +{
	value => $value,
});
 
print $ret;

実行結果:

$VAR1 = {
          'ab' => 'c'
        };
perl: symbol lookup error: /home/*/Data/Dumper/Dumper.so: undefined symbol: isWORDCHAR

このエラー自体は、XSモジュールをビルドしたperlと実行しようとしているperlが異なるとよく見るものなのだが、Xslateの中で実行したときだけエラーが起きるというのはかなり不可解だ。Xslateの中では単にData::Dumperを呼び出しているだけなので、最初のData::Dumperの使用がうまくいっている以上、次の呼び出しが失敗する理由がない…ように思える*1

ともかく isWORDCHAR を調べると、5.13.x で導入されたマクロらしい。そしてこれはppport.h *2 では提供されていない。今回調べている perl は 5.10.1 だから、isWORDCHAR は提供されていない。ということは Data::Dumper で使っている可能性が高い!

そう思ってソースを調べるとはたして使っていた。そして、Perl 5.10.1 でData::Dumper 2.143 を make test してみるとfailする。つまりこれは Data::Dumper のバグだったのだ。

さて、翻って Xslate の中と外で結果が変わった理由、それは、Data::Dumper は pure Perl でもそれなりに動くようになっていることと、Xslate の dumper() がいくつか DD のオプションを設定していることにある。

まず、Data::Dumper は pure Perl でもある程度動くようになっている。また、XSLoader による .so のロードに失敗すると完全に pure Perl mode で動く。だから先のスクリプトも、XSLoader も意図的に .so のロードを失敗させてやると動作する。
具体的には、PERL_DL_NONLAZY=1 を指定してやると Dumper.so のシンボル解決を即座に行うが、isWORDCHAR が存在しないので .so のロードが失敗して pure Perl 実装にフォールバックする*3。これに対してデフォルトは Dumper.so のシンボルの解決は実際に呼び出すまで行われないので、XS実装を呼び出すタイミングで初めてシンボルの解決を試みて失敗する。

このXS実装を呼び出すのが、Xslate の dumper() で指定しているオプションだったようだ。だから Data::Dumper::Dumper() を直接呼び出すとPP実装のみを使うため正常に実行でき、Xslateの dumper() を呼び出すとXS実装を呼びだそうとしてシンボル解決を試み、異常終了する。

このバグは make test で検出されるので本来であればこのバグのあるバージョンのDDをインストールすることはないのだが、PERL_CPANM_OPT=-n (or --notest) を指定する習慣が根付いてしまったので起きた悲劇といえる。-nは指定しないほうがよさそうだ。

*1:もちろん、これは勘違いだった!

*2:XS用互換ヘッダファイル

*3:最初は単に PERL_DL_NONLAZY=1 を指定すると即座に異常終了するかと思ったが、実際にはフォールバックされるのでこの環境変数を定義すると正常に動作するようになる。