Test::LeakTrace 0.07

Devel::LeakTraceもそうだが,0.07以前のTest::LeakTraceは不可解な値を報告することがあった。

$ perl -MDevel::LeakTrace::Fast -e 'my $a; my $b = \$a'
leaked SV(0x0x8f87dd8) from -e line 1
leaked SV(0x0x8f87c94) from -e line 1
$ perl -MTest::LeakTrace::Script -le -e 'my $a; my $b = \$a'
leaked SCALAR(0x9f4cdd8) from -e line 1.
$ 

Test::LeakTraceをCPANにあげた」の記事でも,目的の相互参照だけでなく,空のハッシュが報告されている。

長らくこれが何なのか分からなかったのだが,この現象のメカニズムをどうやら特定できたので,それを修正してTest::LeakTrace 0.07としてリリースした。

この現象はperlのSVアロケーションカニズムに関係しているようだ。まず,my変数はスクリプトコンパイル時にperlによってスクラッチパッド(PAD)と呼ばれる領域に割り当てられる:

sub foo{
  my $bar; # $barは静的に割り当てられる
}

つまり,foo()を呼び出しても新しくSVが確保されることはない*1

一方,my変数のハードリファレンスを返すこともできる:

sub foo{
  my $bar;
  return \$bar;
}

先の説明によれば,$barは静的に割り当てられているはずだ。しかし実際には,foo()を実行するたびに別のSVへのリファレンスが得られるのは周知の通りである。これは$barが静的に割り当てられるという事実と矛盾するように見える。

これはどうやら,my変数への参照*2を作成する時点で新たにSVが作成され,そのmy変数が所属するサブルーチンが次に呼び出されたときに,その新しいSVが利用されるという仕組みによるらしい。
このようなSVはTest::LeakTraceでのメモリリークの定義,つまり「ある区間内で新たに作成され,その区間が終了しても開放されないまま残っているSV」に当てはまるので,今までは報告されていたのだった。
これを検出するには,Perl 5.10.0で導入されたSvPADSTALEが利用できる。これはPAD変数が未使用のときにつけられるフラグで,ある種の警告を発生させるためや,state変数の初期化状態を保存するために使われている。
問題はSvPADSTALEが5.8で利用できないことだったが,これはリークの疑いのあるSVが,PADのどこかに存在すればリークではないものとみなすということにして対応した。

*1:ただし,$barに代入を行うと$barのBODYにメモリが割り当てられる。このBODY領域はサブルーチンが終了しても開放されず,次のサブルーチン呼び出し時に再利用される。

*2:ハードリファレンスによる参照だけでなく,クロージャによる参照も含む。