2012-08-09 19 views
5

Estoy usando una expresión regular para extraer pares clave-valor de cadenas de entrada arbitrariamente largas y me he encontrado con un caso en el que, para una cadena larga con patrones repetitivos, causa un desbordamiento de pila.El patrón Java causa desbordamiento de pila

El código KV-análisis se ve algo como esto:

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("\nMatcher groups discovered:"); 

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

Algunos ejemplos ficticios de salida:

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"; 

Calling parse(input1) produce:

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

Calling parse(input2) produce:

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

Esto está bien (incluso con un poco de procesamiento de cadena requerido para el primer caso). Sin embargo, cuando se trata de analizar un muy largo (más de 1.000 caracteres) cadena de ruta de clases, se produce el desbordamiento de la clase antes mencionada, con la siguiente excepción (inicio):

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 cadena es demasiado larga para poner aquí, pero tiene la siguiente estructura, fácilmente reproducible y repetitivo:

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

Cualquier persona que quiera reproducir el problema sólo tiene que añadir :/opt/files/any unas pocas docenas de veces a la cadena anterior. Después de crear una cadena con aproximadamente 90 copias de ":/opt/files/any" presente en la cadena classpath, se produce el desbordamiento de la pila.

¿Existe una forma genérica de que la cadena KV_REGEX anterior se pueda modificar para que el problema no se produzca y se obtengan los mismos resultados?

Puse explícitamente encima de genérico, a diferencia de los piratas informáticos que (por ejemplo) comprueban para una longitud de cadena máxima antes de analizar.

La revisión más bruto que podía llegar a, un verdadero anti-patrón, es

public void safeParse(String input) 
{ 
    try 
    { 
     parse(input); 
    } 
    catch (StackOverflowError e) // Or even Throwable! 
    { 
     parse(input.substring(0, MAX_LENGTH)); 
    } 
} 

Curiosamente, funciona en algunas carreras que lo probé, pero no es algo lo suficientemente buen gusto para recomendar . :-)

+2

Felicitaciones por haber superado los límites. – kosa

+0

¡Gracias! ¡Aceptaría una solución para una recompensa en cualquier momento! :-) ¿Cuál fue exactamente el límite roto? – PNS

+1

¿Qué se supone que esta parte coincide? No parece correcto en absoluto. '[^ =,^\\) ^]'. – Keppil

Respuesta

3

Su expresión regular se ve demasiado complicada, por ejemplo, creo que aún no ha entendido cómo funcionan las clases de caracteres.Esto funciona mejor para mí, no puedo hacer que sea más desborde:

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("\nMatcher groups discovered:"); 

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

para romper las expresiones regulares, esto va a coincidir:

(\"[^\" ]*\"|[^{=, ]*): Cualquier cosa cerrado con " s, o cualquier número de no {=, caracteres

*= *: de cero a cualquier número de espacios, seguido por =, seguido de cero a cualquier número de espacios

(\"[^\"]*\"|[^=,) }]*): Cualquier cosa incluida con " s, o cualquier cantidad de caracteres que no sean =,) }

+0

Esto de hecho se ve mejor. Lo intentaré con mis casos mucho más complejos en la primera oportunidad. ¡Gracias! :-) – PNS

Cuestiones relacionadas