5 votes

Java Pattern cause un débordement de la pile

Je suis en train d'utiliser une expression régulière pour extraire des paires clé-valeur à partir de chaînes d'entrée de longueur arbitraire et j'ai rencontré un cas dans lequel, pour une longue chaîne avec des motifs répétitifs, cela provoque un dépassement de pile.

Le code d'analyse des KV ressemble à ceci:

public static void parse(String input)
{
    String KV_REGEX = "((?:\"[^\"^ ]*\"|[^=,^ ])*) *= *((?:\"[^\"]*\"|[^=,^\\)^ ])*)";
    Pattern KV_PATTERN = Pattern.compile(KV_REGEX);

    Matcher matcher = KV_PATTERN.matcher(input);

    System.out.println("\nGroupes du matcher découverts:");

    while (matcher.find())
    {
        System.out.println(matcher.group(1) + ", " + matcher.group(2));
    }
}

Quelques exemples fictifs de sortie:

    String input1 = "2012-08-09 09:10:25,521 INFO com.a.package.SomeClass - Everything working fine {name=CentOS, family=Linux, category=OS, version=2.6.x}";
    String input2 = "2012-08-09 blah blah 09:12:38,462 Log for the main thread, PID=5872, version=\"7.1.8.x\", build=1234567, other=done";

L'appel à parse(input1) donne:

{name, CentOS
family, Linux
category, OS
version, 2.6.x}

L'appel à parse(input2) donne:

PID, 5872
version, "7.1.8.x"
build, 1234567
other, done

C'est bien (même s'il faut un peu de traitement de chaîne pour le premier cas). Cependant, en essayant d'analyser une très longue chaîne de classe (plus de 1 000 caractères), le dépassement de pile mentionné se produit, avec l'exception suivante (début):

Exception in thread "main" java.lang.StackOverflowError
    at java.util.regex.Pattern$BitClass.isSatisfiedBy(Pattern.java:2927)
    at java.util.regex.Pattern$8.isSatisfiedBy(Pattern.java:4783)
    at java.util.regex.Pattern$8.isSatisfiedBy(Pattern.java:4783)
    at java.util.regex.Pattern$8.isSatisfiedBy(Pattern.java:4783)
    at java.util.regex.Pattern$8.isSatisfiedBy(Pattern.java:4783)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3345)
    ...

La chaîne est trop longue pour la mettre ici, mais elle a la structure suivante, facilement reproductible et répétitive:

java.class.path=/opt/files/any:/opt/files/any:/opt/files/any:/opt/files/any

Toute personne qui veut reproduire le problème n'a qu'à ajouter :/opt/files/any quelques dizaines de fois à la chaîne ci-dessus. Après avoir créé une chaîne avec environ 90 copies de ":/opt/files/any" présentes dans la chaîne de classe, le dépassement de pile se produit.

Y a-t-il un moyen générique de modifier la chaîne KV_REGEX ci-dessus, afin que le problème ne se produise pas et que les mêmes résultats soient produits?

J'ai mis explicitement générique ci-dessus, par opposition aux astuces qui (par exemple) vérifient une longueur maximale de chaîne avant l'analyse.

La solution la plus grossière que j'ai pu imaginer, un vrai anti-pattern, est

public void safeParse(String input)
{
    try
    {
        parse(input);
    }
    catch (StackOverflowError e) // Ou même Throwable!
    {
        parse(input.substring(0, MAX_LENGTH));
    }
}

Amusant, ça marche dans quelques essais que j'ai faits, mais ce n'est pas quelque chose de assez recommandable. :-)

3voto

Keppil Points 28356

Votre regex semble trop compliquée, par exemple je pense que vous n'avez pas tout à fait compris comment les classes de caractères fonctionnent. Cela fonctionne mieux pour moi, je ne peux plus le faire déborder :

public static void parse(String input) {
    String KV_REGEX = "(\"[^\" ]*\"|[^{=, ]*) *= *(\"[^\"]*\"|[^=,) }]*)";
    Pattern KV_PATTERN = Pattern.compile(KV_REGEX);

    Matcher matcher = KV_PATTERN.matcher(input);

    System.out.println("\nGroupes de correspondance trouvés :");

    while (matcher.find()) {
        System.out.println(matcher.group(1) + ", " + matcher.group(2));
    }
}

Pour décomposer la regex, cela va correspondre à :

(\"[^\" ]*\"|[^{=, ]*) : Tout ce qui est compris entre les ", ou n'importe quel nombre de caractères non-{=,

*= * : zéro ou n'importe quel nombre d'espaces, suivi de =, suivi de zéro ou n'importe quel nombre d'espaces

(\"[^\"]*\"|[^=,) }]*) : Tout ce qui est compris entre les ", ou n'importe quel nombre de caractères non-=,) }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X