15

次の関数は、$pattern 変数で指定した正規表現で壊れます。正規表現を変更しても大丈夫なので、それが問題だと思います。ただし、問題は見られません。また、有効になっているにもかかわらず、標準の PHP エラーも表示されません。

function parseAPIResults($results){
//Takes results from getAPIResults, returns array.

    $pattern = '/\[(.|\n)+\]/';
    $resultsArray = preg_match($pattern, $results, $matches);

}

Firefox 6: 接続がリセットされました

Chrome 14: エラー 101 (net::ERR_CONNECTION_RESET): 接続がリセットされました。

IE 8: Internet Explorer で Web ページを表示できない

更新:
Apache/PHP がクラッシュしている可能性があります。スクリプトを実行したときの Apache エラー ログは次のとおりです。

[Sat Oct 01 11:41:40 2011] [通知] 親: 子プロセスがステータス 255 で終了しました -- 再起動しています。
[Sat Oct 01 11:41:40 2011] [お知らせ] Apache/2.2.11 (Win32) PHP/5.3.0 構成 -- 通常の運用を再開

Windows 7 で WAMP 2.0 を実行しています。

4

4 に答える 4

54

簡単な質問です。複雑な答え!

はい、このクラスの正規表現は、スタック オーバーフローによる未処理のセグメンテーション エラーで Apache/PHP を繰り返し (そして静かに) クラッシュさせます!

バックグラウンド:

PHPpreg_*ファミリの正規表現関数は、Philip Hazel による強力なPCRE ライブラリを使用します。このライブラリには、内部match()関数への再帰呼び出しを多数必要とする特定のクラスの正規表現があり、これが大量のスタック スペースを使用します (使用されるスタック スペースは、一致する対象の文字列のサイズに正比例します)。 . したがって、件名の文字列が長すぎると、スタック オーバーフローと対応するセグメンテーション違反が発生します。この動作については、pcrestackというタイトルのセクションの末尾にあるPCRE ドキュメントで説明されています。

PHP バグ 1: PHP セット:pcre.recursion_limit大きすぎます。

PCRE のドキュメントでは、再帰の深さを、リンクされたアプリケーションのスタック サイズを 500 で割った値にほぼ等しい安全な値に制限することで、スタック オーバーフローのセグメンテーション違反を回避する方法について説明しています。再帰の深さが推奨どおりに適切に制限されている場合、ライブラリはスタック オーバーフローが発生し、代わりにエラー コードで正常に終了します。PHP では、この最大再帰深度はpcre.recursion_limit構成変数で指定され、(残念ながら) デフォルト値は 100,000 に設定されています。この値は大きすぎます! pcre.recursion_limitさまざまな実行可能スタック サイズの安全な値の表を次に示します。

Stacksize   pcre.recursion_limit
 64 MB      134217
 32 MB      67108
 16 MB      33554
  8 MB      16777
  4 MB      8388
  2 MB      4194
  1 MB      2097
512 KB      1048
256 KB      524

httpd.exeしたがって、 256KB の (比較的小さい) スタック サイズを持つApache Web サーバー ( ) の Win32 ビルドの場合、 の正しい値はpcre.recursion_limit524 に設定する必要があります。これは、次の PHP コード行で実現できます。

ini_set("pcre.recursion_limit", "524"); // PHP default is 100,000.

このコードを PHP スクリプトに追加すると、スタック オーバーフローは発生しませんが、意味のあるエラー コードが生成されます。つまり、エラーコードを生成する必要があります。(しかし、残念ながら、別の PHP バグにより、そうでpreg_match()はありません。)

PHP バグ 2:preg_match()エラー時に FALSE を返さない。

の PHP ドキュメントにpreg_match()は、エラー時に FALSE を返すと書かれています。残念ながら、PHP バージョン 5.3.3 以下にはバグ ( #52732 ) があり、エラー時に がpreg_match()返されません(代わりに、不一致の場合に返される値と同じ値である が返されます)。このバグは、PHP バージョン 5.3.4 で修正されました。FALSEint(0)

解決:

WAMP 2.0 (PHP 5.3.0 を使用) を引き続き使用すると仮定すると、ソリューションでは上記の両方のバグを考慮する必要があります。これが私がお勧めするものです:

  • 安全な値に減らす必要がpcre.recursion_limitあります: 524.
  • preg_match()以外のものを返すたびに、PCRE エラーを明示的にチェックする必要がありますint(1)
  • preg_match()が返された場合int(1)、一致は成功しています。
  • preg_match()が返された場合int(0)、一致が失敗したか、エラーが発生しました。

以下は、再帰制限エラーの原因となるサブジェクト文字列の長さを決定するスクリプトの修正版 (コマンド ラインから実行するように設計されています) です。

<?php
// This test script is designed to be run from the command line.
// It measures the subject string length that results in a
// PREG_RECURSION_LIMIT_ERROR error in the preg_match() function.

echo("Entering TEST.PHP...\n");

// Set and display pcre.recursion_limit. (set to stacksize / 500).
// Under Win32 httpd.exe has a stack = 256KB and 8MB for php.exe.
//ini_set("pcre.recursion_limit", "524");       // Stacksize = 256KB.
ini_set("pcre.recursion_limit", "16777");   // Stacksize = 8MB.
echo(sprintf("PCRE pcre.recursion_limit is set to %s\n",
    ini_get("pcre.recursion_limit")));

function parseAPIResults($results){
    $pattern = "/\[(.|\n)+\]/";
    $resultsArray = preg_match($pattern, $results, $matches);
    if ($resultsArray === 1) {
        $msg = 'Successful match.';
    } else {
        // Either an unsuccessful match, or a PCRE error occurred.
        $pcre_err = preg_last_error();  // PHP 5.2 and above.
        if ($pcre_err === PREG_NO_ERROR) {
            $msg = 'Successful non-match.';
        } else {
            // preg_match error!
            switch ($pcre_err) {
                case PREG_INTERNAL_ERROR:
                    $msg = 'PREG_INTERNAL_ERROR';
                    break;
                case PREG_BACKTRACK_LIMIT_ERROR:
                    $msg = 'PREG_BACKTRACK_LIMIT_ERROR';
                    break;
                case PREG_RECURSION_LIMIT_ERROR:
                    $msg = 'PREG_RECURSION_LIMIT_ERROR';
                    break;
                case PREG_BAD_UTF8_ERROR:
                    $msg = 'PREG_BAD_UTF8_ERROR';
                    break;
                case PREG_BAD_UTF8_OFFSET_ERROR:
                    $msg = 'PREG_BAD_UTF8_OFFSET_ERROR';
                    break;
                default:
                    $msg = 'Unrecognized PREG error';
                    break;
            }
        }
    }
    return($msg);
}

// Build a matching test string of increasing size.
function buildTestString() {
    static $content = "";
    $content .= "A";
    return '['. $content .']';
}

// Find subject string length that results in error.
for (;;) { // Infinite loop. Break out.
    $str = buildTestString();
    $msg = parseAPIResults($str);
    printf("Length =%10d\r", strlen($str));
    if ($msg !== 'Successful match.') break;
}

echo(sprintf("\nPCRE_ERROR = \"%s\" at subject string length = %d\n",
    $msg, strlen($str)));

echo("Exiting TEST.PHP...");

?>

このスクリプトを実行すると、対象の文字列の現在の長さが継続的に読み取られます。をデフォルト値pcre.recursion_limitが高すぎるままにしておくと、実行可能ファイルがクラッシュする原因となる文字列の長さを測定できます。

コメント:

  • この質問への回答を調べる前に、PCRE ライブラリでエラーが発生したときpreg_match()に返されないPHP のバグを知りませんでした。このバグは確かに、 !FALSEを使用する多くのコードに疑問を投げかけています。preg_match(もちろん、私自身の PHP コードの目録を作成するつもりです。)
  • Windows では、Apache Web サーバー実行可能ファイル ( httpd.exe) は 256KB のスタックサイズでビルドされます。PHP コマンド ライン実行可能ファイル ( php.exe) は、8 MB のスタック サイズでビルドされます。の安全な値はpcre.recursion_limit、スクリプトが実行されている実行可能ファイルに従って設定する必要があります (それぞれ 524 と 16777)。
  • *nix システムでは、Apache Web サーバーとコマンド ライン実行可能ファイルの両方が通常 8 MB のスタック サイズでビルドされるため、この問題はそれほど頻繁には発生しません。
  • PHP 開発者は、のデフォルト値をpcre.recursion_limit安全な値に設定する必要があります。
  • PHP 開発者は、preg_match()バグ修正を PHP バージョン 5.2 に適用する必要があります。
  • CFF Explorerフリーウェア プログラムを使用して、Windows 実行可能ファイルのスタックサイズを手動で変更できます。このプログラムを使用して、Apachehttpd.exe実行可能ファイルのスタックサイズを増やすことができます。(これは XP で動作しますが、Vista と Win7 では問題が発生する可能性があります。)
于 2011-10-02T17:23:35.723 に答える
2

私は同じ問題に遭遇しました。ridgerunner が投稿した回答に感謝します。

PHP がクラッシュする理由を知ることは役に立ちますが、私にとっては、これで問題が実際に解決されるわけではありません。この問題を解決するには、メモリを節約するために正規表現を調整して、php がクラッシュしないようにする必要があります。

したがって、問題は正規表現を変更する方法です。上に投稿された PCRE マニュアルへのリンクは、あなたのものと非常によく似た正規表現の例の解決策をすでに説明しています。

では、正規表現を修正するにはどうすればよいでしょうか? まず、「. または改行」に一致させたいと言います。ご了承ください "。" ドットだけでなく任意の文字に一致する正規表現の特殊文字であるため、それをエスケープする必要があります。(ここで誤解していないことを願っています。これは意図されたものです。)

$pattern = '/\[(\.|\n)+\]/';

次に、括弧内の量指定子をコピーできます。

$pattern = '/\[(\.+|\n+)+\]/';

これは、式の意味を変更しません。ここで、通常の量指定子の代わりに所有量指定子を使用します。

$pattern = '/\[(\.++|\n++)++\]/';

したがって、これは元の正規表現と同じ意味を持つはずですが、クラッシュすることなく php で動作します。なんで?所有量指定子は文字を「食い尽くし」、バックトラックを許可しません。したがって、PCRE は再帰を使用する必要がなく、スタックがオーバーフローしません。括弧内でそれらを使用することは、代替案の定量化を頻繁に必要としないため、良い考えのようです。

要約すると、ベストプラクティスは次のようです。

  • 可能であれば、所有量指定子を使用してください。これは、+、*、?、{} の代わりに ++、*+、?+ {}+ を意味します。
  • 可能であれば、量指定子を代替ブラケット内に移動します

これらのルールに従って、私は自分の問題を解決できました。これが他の人の助けになることを願っています.

于 2012-11-17T00:27:55.763 に答える
1

私は同じ問題を抱えていたので、パターンを次のように変更する必要があります

$pattern = '|/your pattern/|s';

末尾の 's' は、基本的に文字列を 1 行として扱うことを意味します。

于 2012-12-07T13:46:04.120 に答える
0

preg_matchは、パターンで見つかった一致の数を返します。一致すると、php で致命的なエラーが発生します (print_r(1)たとえば、エラーが発生します)。print_r(0) (パターンを変更して一致がない場合) は 0 を出力せず、単に出力します。

あなたがしたいprint_r($matches)

余談ですが、あなたのパターンは適切にエスケープされていません。二重引用符を使用すると、括弧の前のバックスラッシュをエスケープする必要があることを意味します。

于 2011-10-01T14:48:29.593 に答える