JeuWeb - Crée ton jeu par navigateur
[Résolu] RegEx PHP/apache - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : [Résolu] RegEx PHP/apache (/showthread.php?tid=143)



[Résolu] RegEx PHP/apache - Xenos - 30-04-2013

Bonsoir à tous

(Pour les feignasses, allez directement en fin de message)

Pour poster ce topic, j'avais le choix entre "Demande d'aide" et "Débats/Discussions". J'ai résolu mon problème, donc c'est pas vraiment une demande d'aide, d'où le fait que je poste ici. mais même si mon problème (concernant les regex PCRE, apache et php comme vous vous en doutez) est résolu, je reconnais ne pas trop bien savoir pourquoi...


Voici une chaîne de caractères:
Citation :/*
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
*/

(On m'a dit de bien commenter mes codes, alors, c'est ce que je fais)
Cette chaine de caractères fait partie d'un fichier, et mon but était de supprimer les commentaires de ce fichier (tout ce qui est entre /* et */, donc, supprimer tous ces "a")

Pour ce faire, dans mon PHP, je charge la chaine de caractères (le fichier entier). Puis je passe cette chaine dans une regex, preg_replace:
Code PHP :
<?php 
preg_replace
('#(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#m', '', $css); // Supprime les commentaires

Et là, tout va bien (ouf!). La regex marche, apache fume son calumet et je suis content. Mais voilà qu'un cow-boy vient rajouter une lettre dans le commentaire:
Citation :/*
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
*/
Rien d'autre dans le fichier ne change... Et apache déterre la hache de guerre!

Citation :L'exception unknown software exception (0xc00000fd) s'est produite dans l'application.
En passant, j'adore quand l'ordi me dit que le problème est inconnu, c'est toujours une joie à débugger

Dans les logs Apache, je trouve:
Citation :[Tue Apr 30 20:45:04.640625 2013] [mpm_winnt:notice] [pid 1724:tid 1148] AH00428: Parent: child process exited with status 3221225725 -- Restarting.

Google ne m'a franchement pas aidé sur ce coup-là.
Il m'a bien fallu 2 heures pour court-circuiter des morceaux de code et trouver que l'erreur venait de cette fameuse regex:

Citation :preg_replace('#(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#m', '', $css); // Supprime les commentaires bugg (stack overflow?)

Et là se pose la colle: j'ai deux solutions à ce problème, mais je ne comprend pas pourquoi elles marchent (solutions trouvées au pif et à l'instinct, avec l'aide de la doc php pour la 1ere):
passer en mode 'ungreedy' pour rendre la regex non gourmande
Citation :preg_replace('#(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#mU', '', $css); // Supprime les commentaires
Ca marche (apache ne crash pas), et je ne sais pas pourquoi
Ou changer de regex (ce que j'ai fait)
Citation :preg_replace('#(/\*([^\*]*(\*[^/])?)*\*/)|(@@.*$)#m', '', $css); // Supprime les commentaires
Cela marche aussi très bien.

Alors pourquoi la 1ere regex ne marchait-elle pas et pourquoi faisait-elle planter Apache? j'ai essayer de passer les parenthèses en non-capturantes:
Citation :preg_replace('#(/\*(?:[^\*]|\*[^/])*\*/)|(@@.*$)#m', '', $css); // Supprime les commentaires
Mais cela n'avait pas marché non plus... Alors je m'interroge:

quelle différence y-a-t-il entre ces regex? Pourquoi la 1ere crash apache?
Code :
preg_replace('#(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#m', '', $css);
preg_replace('#(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#mU', '', $css);
preg_replace('#(/\*([^\*]*(\*[^/])?)*\*/)|(@@.*$)#m', '', $css);

A mon avis, c'est un problème de performance (j'ai vu que l'erreur de crash apache était de type "stack overflow"), mais d'où vient cette différence de performances?


RE: RegEx PHP/apache - Xenos - 30-04-2013

(Remarque: La PCRE que je cherchais à faire est en fait:

Citation :#(/\*(?:[^\*]*(?:\*+[^/*])?)*(?:\*)+/)|(@@.*$)#m

sinon, la séquence "**/" ne sera pas prise comme une fermeture de commentaire et le \*+ permet de prendre en compte un commentaire avec plusieurs étoiles comme "/* te**st */")


RE: RegEx PHP/apache - niahoo - 30-04-2013

tu peux aussi utiliser le tokenizer de PHP


RE: RegEx PHP/apache - Xenos - 01-05-2013

C'est vrai, mais je ne le connaissais pas (déjà), et même le connaissant, je le pense trop tordu et lourd pour être fonctionnel (si j'ai besoin de tokens supplémentaires). Bon, après, ma regexp, niveau lourdeur, elle se posait là... Mais je vais finalement me tourner vers les options PCRE:

Citation :#(/\*.*\*/)|(@@.*$)#msU

Ca change pas que je ne sais toujours pas pourquoi la regexp #(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#m crash apache si les commentaires /* aaaa */ dans la chaine à parser sont trop longs.


RE: RegEx PHP/apache - niahoo - 01-05-2013

ça fait quoi que ce soit lourd ? je suppose que tu ne t'amuses pas à traiter du code source et à enlever les commentaires 150 fois par secondes Smile


<?php
/*
* T_ML_COMMENT does not exist in PHP 5.
* The following three lines define it in order to
* preserve backwards compatibility.
*
* The next two lines define the PHP 5 only T_DOC_COMMENT,
* which we will mask as T_ML_COMMENT for PHP 4.
*/
if (!defined('T_ML_COMMENT')) {
define('T_ML_COMMENT', T_COMMENT);
} else {
define('T_DOC_COMMENT', T_ML_COMMENT);
}

$source = file_get_contents('example.php');
$tokens = token_get_all($source);

foreach ($tokens as $token) {
if (is_string($token)) {
// simple 1-character token
echo $token;
} else {
// token array
list($id, $text) = $token;

switch ($id) {
case T_COMMENT:
case T_ML_COMMENT: // we've defined this
case T_DOC_COMMENT: // and this
// no action on comments
break;

default:
// anything else -> output "as is"
echo $text;
break;
}
}
}
?>

et à mon avis, le compilateur PHP te mènera beaucoup plus loin et plus rapidement que les regex !


RE: RegEx PHP/apache - Xenos - 01-05-2013

C'est vrai que je n'ai aucune preuve de la légèreté des regex face au tokenizer, mais niveau code, ca prend moins de place :p
Et comme je ne cherche pas les fonctionnalités supplémentaires du tokenizer (je veux juste virer ce qui se trouve entre /* */, même dans une chaîne de caractères), je n'ai pas envie d'aller ajouter du code.


Le problème n'est pas "comment je peux faire pour virer les commentaires d'un fichier", mais plutôt "pourquoi cette regex #(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#m plante le serveur apache si j'ai une entrée avec /* plein de lettres */?"


RE: RegEx PHP/apache - Xenos - 02-05-2013

Finalement, si j'en crois stack overflow (quel beau site!), le problème semble être du à la façon dont Apache est compilé pour Windows.

Si j'ai bien compris, mais j'en suis pas certain à 100%:
"Apache is compiled with a small stacksize", et une regex comme "#(/\*([^\*]|\*[^/])*\*/)|(@@.*$)#m" dépasse ce stack.
Il semblerait que ce soucis de stack viennent du mode "greedy" (gourmand) des regex: les quantificateurs + et * des regex PHP sont gourmands c'est à dire que pour X* ou X+, php essaye de mettre un maximum de caractères possibles dans le X (par exemple, dans .*x, php essayera de mettre le plus de caractères possibles dans le ., et de faire suivre l'ensemble de ces caractères par un x).

Mais ce caractère gourmand pose soucis: pendant l'évaluation de la regex, php peut être "trop" gourmand, et la regex ne marche plus. PHP "revient" alors en arrière, et devient moins gourmand. Par exemple, si ma chaine est "0123456789abcdef", pour la regex "#.*\w+#", php fait le travail suivant:
- .*, je mets un max de caractères dedans: 0123456789abcdef (le caractère après "f" est la fin de chaine, non-couvert par ".", donc je ne peux pas le manger)
- le pattern demande maintenant \w+... zut, mon caractère courant, la fin de chaine ($), n'est pas bon...
- je "démange" un caractère: 0123456789abcde, et je suis actuellement sur f
- le pattern demande maintenant \w+... oui! f est un \w, je le mange: 0123456789abcdef, et je suis sur le caractère $
- le pattern est fini, la regex est machée.

Dans ce mécanisme, le "démangeage" implique de stocker les caractères sur une pile (le stack). Or, si on n'a pas réservé assez de palce sur cette pile (lors de la compilation d'Apache), boum! C'est le drame, stack overflow (littéralement).

Du coup, avec une regex non-gourmande (option "U"), le problème disparait (pas de "démangeage" possible, pas de stack overflow). Le problème disparait aussi si la regex est mieux structurée (là, j'ai pas vraiment de précision à donner sur ce point).
Après, j'en suis vraiment, mais alors vraiment pas certain.

En tous cas, un serveur Apache qui crashe sous windows avec une erreur de type "unknown exception" ou similaire (pas de message d'erreur explicite) peut venir d'une regex dont l'entrée est trop longue. Le remède est donc soit de réduire l'entrée, soit de changer la regex, soit de changer d'OS.

Résolu.


RE: [Résolu] RegEx PHP/apache - niahoo - 03-05-2013

je crois pas que PHP utilise la stack de apache, il a sa propre gestion de la mémoire. (quasiment sûr)