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

正規表現のoオプションにPerlの暗黒面を見た

Perl

Perlの正規表現のoオプションてご存じでしょうか? うちはあまり使ったことがなかったのですが,効率のよい正規表現処理を行うときには便利なオプションです.

oオプションを使うと,その正規表現のコンパイルが1回しか行われなくなります.たとえば,正規表現の評価が繰り返しのなかで現れると,コンパイルが何度も行われて効率が悪くなります.そういったときにoオプションを付与しておけば,正規表現コンパイルのオーバーヘッドを最小限にとどめることができます.

chomp(my $pattern = <>);
for my $n (0..100000000000) { # すごく回数の多いループ
    # oをつければ正規表現がコンパイルされるのははじめの1回だけなので安心
    if ($n =~ m/^$pattern$/o) {
       say "match!";
    }
}

正規表現が静的な場合は,Perlはこのような最適化を自動で行うのですが,例のように正規表現が動的に決定する場合は,このような最適化ができないので,明示的に指定する必要があるんですね.

しかし,このoオプションは使いどこををまちがえると大変めんどうなバグの原因になります.

例えば,次のようなコードで正規表現のoオプションを使ってみたとします.このmatch関数はすごく何度も呼ばれるのでoオプションをつけました.そのあと,match関数を使ったコードでマッチングをしています.

sub match {
    my ($pattern, $text) = @_;

    # 繰り返しの中で使うのでoオプションをつけよう!
    return $text =~ m/^$pattern$/o;
}                    
                        
# すべてマッチする      
for my $text (qw(vim vim vim vim vim)) {
    if (match('vim', $text)) {
        say "match: vim";
    }                   
    else {
        say "not match: vim";
    }
}

# すべてマッチする?
for my $text (qw(emacs emacs emacs emacs emacs)) {
    if (match('emacs', $text)) {
        say "match: emacs";
    }
    else {
        say "not match: emacs";
    }
}

このプログラムの実行結果は,次のようになります.

match: vim
match: vim
match: vim
match: vim
match: vim
not match: emacs
not match: emacs
not match: emacs
not match: emacs
not match: emacs

なぜか後半のマッチングがすべて失敗してしまいます!ちゃんと引数に'emacs'を指定しているのに!一回目に正規表現をセットしたときはうまく動くのに,二回目以降はなぜかうまく動かない関数ができました.おそろしい.

これはoオプションが原因で起こります.どうやら,oオプションつけた正規表現は,一度コンパイルされたら,スクリプトの実行が終わるまで決して変更されることがなくなるようです.*1 なので,いくら引数で値を渡しても一度コンパイルされた正規表現はかわりません.関数のスコープとか無視してきます.まじでこわいですね.

コードをみても一見まちがいがわからないし,一回目の実行はうまくいったりするので,これのがらみのバグはかなりやな感じです.しかも,oオプションを削ったら普通に動くようになったりするので,かなりショックです.かなりショックでした.

結局何が言いたかったかというと,Perlの闇は深すぎて,油断するとすぐに真っ黒に染められてしまう*2ので,全力で取り扱い注意ということですね.

*1:こまかい条件はわからない

*2:主にコードとココロが