RegExp dans la fonction prég match retour d'une erreur de navigateur

la fonction suivante casse avec le regexp que j'ai fourni dans la variable $pattern. Si je change le regexp, je vais bien, donc je pense que c'est le problème. Je ne vois pas le problème, cependant, et je ne reçois pas D'erreur PHP standard même si elles sont activées.

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

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

}

Firefox 6: la connexion a été réinitialisée

Chrome 14: erreur 101 (net:: ERR_CONNECTION_RESET): la connexion était réinitialiser.

IE 8: Internet Explorer ne peut pas afficher la page web

mise à jour:

Apache / PHP peut s'effondrer. Voici le journal des erreurs Apache à partir duquel j'exécute le script:

[Sam. Oct 01 11:41: 40 2011] [avis] Parent: processus concernant les enfants état 255 ... redémarrage.

[Sam. Oct 01 11:41: 40 2011] [avis] Apache / 2.2.11 (Win32) PHP / 5.3.0 configuré -- reprise normale opérations

lancer WAMP 2.0 sur Windows 7.

13
demandé sur NormundsP 2011-10-01 18:42:37

4 réponses

Simple question. Réponse complexe!

Oui, cette classe de regex va augmenter de façon répétée (et en silence) crash Apache/PHP avec un non gérée erreur de segmentation en raison d'un débordement de pile!

Contexte:

le PHP preg_* famille de fonctions regex utilisez le puissant PCRE library par Philip Hazel. Avec cette bibliothèque, il y a une certaine classe de regex qui nécessite beaucoup d'appels récursifs à son interne match() fonction et cela épuise beaucoup d'espace de pile, (et de l'espace de pile utilisé est directement proportionnelle à la taille de la chaîne en cours d'appariement). Ainsi, si la chaîne de caractères est trop longue, un débordement de la pile et une faille de segmentation correspondante se produiront. Ce comportement est décrit dans la documentation PCRE à la fin de la section intitulée: pcrestack.

PHP Bug 1: PHP sets:pcre.recursion_limit trop grand.

la documentation PCRE décrit comment éviter une faille de segmentation du débordement de la pile en limitant la profondeur de récursion à une valeur sûre à peu près égale à la taille de la pile de l'application liée divisée par 500. Lorsque la profondeur de récursion est correctement limitée comme recommandé, la bibliothèque ne génère pas de débordement de la pile et sort avec élégance avec un code d'erreur. Sous PHP, cette profondeur maximale de récursion est spécifiée avec le pcre.recursion_limit variable de configuration et (malheureusement) la valeur par défaut est fixée à 100 000. cette valeur est trop BIG! Voici un tableau des valeurs sûres de pcre.recursion_limit pour une variété de pile exécutable tailles:

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

ainsi, pour la compilation Win32 du serveur web Apache (httpd.exe), qui a une taille de pile (relativement petite) de 256 KO, La valeur correcte de pcre.recursion_limit devrait être réglé à 524. Ceci peut être accompli avec la ligne suivante de code PHP:

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

lorsque ce code est ajouté au script PHP, le débordement de la pile ne se produit pas, mais génère plutôt un débordement significatif code d'erreur. C'est, il SHOULD générer un code d'erreur! (Mais malheureusement, à cause D'un autre bug PHP,preg_match() n'est pas.)

PHP Bug 2:preg_match() ne retourne pas FALSE en cas d'erreur.

la documentation PHP pour preg_match() dit QU'il retourne FALSE sur erreur. Malheureusement, les versions 5.3.3 et suivantes de PHP ont un bug (#52732) où preg_match() ne renvoie PAS FALSE en cas d'erreur (au lieu renvoie int(0), qui est la même valeur renvoyée dans le cas d'un non-match). Ce bug a été corrigé dans la version 5.3.4 de PHP.

Solution:

en supposant que vous continuiez à utiliser WAMP 2.0 (avec PHP 5.3.0) la solution doit prendre en considération les deux bogues ci-dessus. Voici ce que je recommande:

  • besoin de réduire pcre.recursion_limit à une valeur sûre: 524.
  • besoin de vérifier explicitement une erreur PCRE chaque fois que preg_match() renvoie autre chose que int(1).
  • si preg_match() retourne int(1), alors le match fut un succès.
  • Si preg_match() retourne int(0), le match n'était pas réussi, ou il y avait une erreur.

Voici une version modifiée de votre script (conçue pour être exécutée à partir de la ligne de commande) qui détermine la longueur de la chaîne de caractères qui entraîne l'erreur de limite de récursion:

<?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...");

?>

quand vous exécutez ce script, il fournit une lecture continue de la longueur actuelle du sujet chaîne. Si le pcre.recursion_limit est laissé à sa valeur par défaut trop élevée, ce qui vous permet de mesurer la longueur de chaîne de caractères qui provoque le plantage de l'exécutable.

Commentaires:

  • avant d'étudier la réponse à cette question, je ne savais pas où preg_match() ne retourne FALSE lorsqu'une erreur se produit dans la bibliothèque PCRE. Ce bug remet certainement en question beaucoup de code qui utilise preg_match! (Je vais certainement faire un inventaire de mon propre PHP code.)
  • sous Windows, l'exécutable du serveur web Apache (httpd.exe) est construit avec une stacksize de 256KB. L'exécutable en ligne de commande PHP (php.exe) est construit avec une stacksize de 8MB. La valeur sûre pour les pcre.recursion_limit doit être défini en fonction de l'exécutable sous lequel le script est exécuté (524 et 16777 respectivement).
  • sous les systèmes * nix, le serveur web Apache et les exécutables en ligne de commande sont tous les deux généralement construits avec une taille de stacksize de 8 Mo, donc ce problème n'est pas rencontré souvent.
  • les développeurs PHP devraient définir la valeur par défaut de pcre.recursion_limit une valeur sûre.
  • les développeurs PHP devraient appliquer le preg_match() bugfix à PHP version 5.2.
  • la stacksize D'un exécutable Windows peut être modifiée manuellement en utilisant le CFF Explorer programme freeware. Vous pouvez utiliser ce programme pour augmenter la taille de stacksize de L'Apache httpd.exe exécutable. (Cela fonctionne sous XP mais Vista et Win7 pourrait plaindre.)
51
répondu ridgerunner 2011-10-02 18:37:41

j'ai rencontré le même problème. Merci beaucoup pour la réponse posté par ridgerunner.

bien qu'il soit utile de savoir pourquoi php plante, pour moi cela ne résout pas vraiment le problème. Pour résoudre le problème, je dois ajuster mon regex afin de sauvegarder la mémoire pour que php ne se bloque plus.

la question est donc de savoir comment modifier la regex. le lien vers le manuel PCRE posté ci-dessus décrit déjà une solution pour un exemple regex qui est assez similaire à la vôtre.

alors comment réparer votre regex? Tout d'abord, vous dites que vous voulez correspondre à "un . ou un retour à la ligne". Notez que "."est un caractère spécial dans un regex qui ne correspond pas seulement à un point mais à n'importe quel caractère, donc vous devez échapper à cela. (J'espère que je ne vous ai pas mal compris ici et que c'était voulu.)

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

Ensuite, on peut copier le quantificateur entre parenthèses:

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

Cela ne change pas le sens de l'expression. Maintenant, nous utilisons des quantificateurs possessifs au lieu de normal:

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

donc cela devrait avoir la même signification que votre regex original, mais travailler en php sans le casser. Pourquoi? Les quantificateurs possessifs "manger" les personnages et ne permettent pas de revenir en arrière. Par conséquent, PCRE ne doit pas utiliser la récursion et la pile ne débordera pas. Les utiliser entre parenthèses semble être une bonne idée car nous n'avons pas besoin de la quantification de l'alternative si souvent.

pour résumer, les meilleures pratiques semblent être:

  • utilisez des quantificateurs possessifs dans la mesure du possible. Cela signifie: ++, *+, ?+ {}+ plutôt +, *, ?, {}.
  • déplacer les quantificateurs à l'intérieur des crochets de remplacement lorsque c'est possible

en suivant ces règles, j'ai pu résoudre mon propre problème, et j'espère que cela aidera quelqu'un d'autre.

2
répondu Christopher K. 2012-11-17 00:27:55

j'ai eu le même problème et que vous avez besoin pour change le modèle de quelque chose comme

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

le' s ' à la fin signifie essentiellement traiter la chaîne comme une seule ligne.

1
répondu user1844937 2012-12-07 13:46:04

preg_match retourne le nombre de correspondances trouvées pour le motif. Lorsque vous avez une correspondance, cela provoque une erreur fatale en php (print_r(1), par exemple, provoque l'erreur). print_r(0) (pour quand vous changer le motif et sans correspondance) ne l'est pas et se contente d'afficher 0.

print_r($matches)

de part et d'autre, votre schéma n'est pas correctement échappé. L'utilisation de guillemets doubles signifie que vous devez échapper les barres obliques en face de vos crochets.

0
répondu jasonbar 2011-10-01 14:48:29