web-dev-qa-db-ja.com

PHP evalおよびキャプチャエラー(可能な限り)

免責事項;私は、パフォーマンスの問題、セキュリティ、移植性などを含むがこれらに限定されない、evalの落とし穴と「悪」を完全に認識しています

問題

PHP評価に関するマニュアルを読む...

評価されたコードでreturnが呼び出されない限り、eval()はNULLを返します。呼び出された場合、returnに渡された値が返されます。評価されたコードに解析エラーがある場合、eval()はFALSEを返し、次のコードの実行は正常に続行されます。 set_error_handler()を使用してeval()の解析エラーをキャッチすることはできません。

要するに、falseを返す以外にエラーキャプチャはありません。これは非常に役立ちますが、もっとうまくできると確信しています。

理由

私が取り組んでいるサイトの機能の一部は、式の実行に依存しています。サンドボックスや実行モジュールのパスを通過したくないので、evalの使用を終了しました。 「クライアントが悪くなった場合はどうなりますか?」と叫ぶ前に。クライアントがかなり信頼されていることを知っています。彼は自分のサイトを壊したくはありません。この機能にアクセスできる人は、evalに関係なく、ほとんどサーバーを所有しています。

クライアントはExcelのような式を知っており、小さな違いを説明するのに問題はありませんが、何らかの形の警告があることはほとんど標準的な機能です。

これは私がこれまでに持っているものです:

define('CR',chr(13));
define('LF',chr(10));

function test($cond=''){
    $cond=trim($cond);
    if($cond=='')return 'Success (condition was empty).'; $result=false;
    $cond='$result = '.str_replace(array(CR,LF),' ',$cond).';';
    try {
        $success=eval($cond);
        if($success===false)return 'Error: could not run expression.';
        return 'Success (condition return '.($result?'true':'false').').';
    }catch(Exception $e){
        return 'Error: exception '.get_class($e).', '.$e->getMessage().'.';
    }
}

メモ

  • この関数は、どのような場合でもメッセージ文字列を返します
  • コード式は、PHPタグがなく、終了セミコロンがない、1行のPHPである必要があります。
  • 新しい行はスペースに変換されます
  • 結果を含む変数が追加されます(式はtrueまたはfalseのいずれかを返す必要があり、evalの戻り値と競合しないように、一時変数が使用されます)。

では、ユーザーをさらに支援するために何を追加しますか?考えられるエラー/問題をより正確に特定できる可能性のあるさらなる解析機能はありますか?

クリス。

25
Christian

私は私の質問に対する良い代替案/答えを見つけました。

まず、error_reporting(E_ALL);を設定すると、nikicの提案が機能することから始めましょう。通知はPHP出力に表示され、OBのおかげでキャプチャできます。

次に、私はこの非常に便利なコードを見つけました:

/**
 * Check the syntax of some PHP code.
 * @param string $code PHP code to check.
 * @return boolean|array If false, then check was successful, otherwise an array(message,line) of errors is returned.
 */
function php_syntax_error($code){
    if(!defined("CR"))
        define("CR","\r");
    if(!defined("LF"))
        define("LF","\n") ;
    if(!defined("CRLF"))
        define("CRLF","\r\n") ;
    $braces=0;
    $inString=0;
    foreach (token_get_all('<?php ' . $code) as $token) {
        if (is_array($token)) {
            switch ($token[0]) {
                case T_CURLY_OPEN:
                case T_DOLLAR_OPEN_CURLY_BRACES:
                case T_START_HEREDOC: ++$inString; break;
                case T_END_HEREDOC:   --$inString; break;
            }
        } else if ($inString & 1) {
            switch ($token) {
                case '`': case '\'':
                case '"': --$inString; break;
            }
        } else {
            switch ($token) {
                case '`': case '\'':
                case '"': ++$inString; break;
                case '{': ++$braces; break;
                case '}':
                    if ($inString) {
                        --$inString;
                    } else {
                        --$braces;
                        if ($braces < 0) break 2;
                    }
                    break;
            }
        }
    }
    $inString = @ini_set('log_errors', false);
    $token = @ini_set('display_errors', true);
    ob_start();
    $code = substr($code, strlen('<?php '));
    $braces || $code = "if(0){{$code}\n}";
    if (eval($code) === false) {
        if ($braces) {
            $braces = PHP_INT_MAX;
        } else {
            false !== strpos($code,CR) && $code = strtr(str_replace(CRLF,LF,$code),CR,LF);
            $braces = substr_count($code,LF);
        }
        $code = ob_get_clean();
        $code = strip_tags($code);
        if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code)) {
            $code[2] = (int) $code[2];
            $code = $code[2] <= $braces
                ? array($code[1], $code[2])
                : array('unexpected $end' . substr($code[1], 14), $braces);
        } else $code = array('syntax error', 0);
    } else {
        ob_end_clean();
        $code = false;
    }
    @ini_set('display_errors', $token);
    @ini_set('log_errors', $inString);
    return $code;
}

必要なことを簡単に実行できるようです(イェーイ)!

14
Christian

PHP 7 eval()は構文エラーの ParseError 例外を生成するため:

try {
    $result = eval($code);
} catch (ParseError $e) {
    // Report error somehow
}

In PHP 5 eval()は、実行を中止しないように特別な場合の解析エラーを生成します(解析エラーが通常行うように)。ただし、エラーハンドラーを介してキャッチすることもできません。 。display_errors=1:を想定して、印刷されたエラーメッセージをキャッチする可能性があります。

ob_start();
$result = eval($code);
if ('' !== $error = ob_get_clean()) {
    // Report error somehow
}
19
NikiC

Eval()内の解析エラーをテストする方法:

$result = @eval($evalcode . "; return true;");

$result == falseの場合、$evalcodeには解析エラーがあり、「returntrue」の部分は実行されません。明らかに$evalcodereturnそれ自体は何かであってはなりませんが、このトリックを使用すると、式の解析エラーを効果的にテストできます...。

7
Davide Moretti

次のようなことも試すことができます。

$filePath = '/tmp/tmp_eval'.mt_Rand();
file_put_contents($filePath, $evalCode);
register_shutdown_function('unlink', $filePath);
require($filePath);

したがって、$ evalCodeのエラーは、エラーハンドラーによって処理されます。

2
barbushin

朗報:PHP 7eval()現在*評価されたコードが無効な場合、ParseError例外をスローします。

try
{
    eval("Oops :-o");
}
catch (ParseError $err)
{
    echo "YAY! ERROR CAPTURED: $err";
}

*まあ、それからかなり長い間...;)

2
Sz.