ぐらめぬ・ぜぷつぇんのはてダ(2007 to 2011)

2007年~2011年ごろまで はてなダイアリー に書いてた記事を引っ越してきました。

PCRE正規表現で、preg_matchでSAPIの時のみいきなりPHPが終了する時がある。


これ、CLIでは発生しなかったので余計分からないですが・・・。

事の発端は、YakiBikiでWikiを変換したのを、さらにyb_独自タグの解析に掛けようとした時です。SAPIの時のみ、いきなりPHPが終了してたんです。
最初は、意気揚々とWiki形式のテキストを数行作って、ちゃんと表示されてました。ところが少し長めのWikiになった途端、表示する時にいきなりブラウザが「サーバーに接続できない」ような画面になったのです。不審に思ってxdebugを有効化し、WinCacheGrindでプロファイルを見たところ、yb_独自タグの解析の為のpreg_matchの呼び出しでスクリプトが終わっていました。

YakiBikiでは、Wikiを変換したHTMLをそのまま表示しているわけではありません。yb_moreやyb_lsなど、YakiBiki独自のHTMLタグを解析し、対応するプラグインを実行するHTML変換機能を掛けた上で、表示しています。
この、HTML変換機能のところでいきなり終わっていたわけです。

該当するPCREの正規表現はこんな感じでした。

$regexp1 = '!^((?:.|\n)*?)<yb_[a-zA-Z0-9_]+(?:[\t ]+.*?)?[\t ]*?/?>!mi';

で、コマンドラインからも試してみたのですがコマンドラインからだと問題なく解析できています。

多分、""タグが見つかるまでのマッチ部分が一定サイズを超えると、preg_matchがいきなり終了するようになっているみたいです。

悩んだ末、「つまり、"

// "<yb_"までの間は予め削り取られたとしておき、いきなり
// 文字列の先頭から始めてしまう。
$regexp1 = '!^<yb_[a-zA-Z0-9_]+(?:[\t ]+.*?)?[\t ]*?/?>!mi';

$source = preg_replace('/\r?\n/', "\n", $source);
$src = $source;
while ($src != '') {

    // stristrで、予め出現位置までを削り取っておく。
    if (($prematched = stristr($src, '<yb_')) === false) {
        $dest .= $src;
        break;
    }
    $backtrack = _substr($src, 0, strlen($src) - strlen($prematched));

    // 削り取った後、改めてちゃんとした正規表現でチェックする。
    if (preg_match($regexp1, $prematched, $m)) {
        $dest .= $backtrack;
        $src = $prematched;

本来であれば、

$regexp1 = '!^((?:.|\n)*?)<yb_[a-zA-Z0-9_]+(?:[\t ]+.*?)?[\t ]*?/?>!mi';

↑の最初のサブパターン

((?:.|\n)*?)

の部分を「"