Saltar al contenido principal

Probar expresiones regulares

Esta herramienta compara un patrón con un texto en tu navegador, dentro de un Web Worker con un tiempo límite de dos segundos. No es precaución por precaución: JavaScript no ofrece forma de interrumpir una expresión regular en ejecución, así que un patrón como (a+)+$ contra una entrada larga retiene su hilo indefinidamente, y matar el worker es la única defensa que funciona de verdad.

Solo local
//gm
Texto de prueba
0 B1 líneaLín. 1, Col. 1
Coincidencias

¿Cómo funciona Probar expresiones regulares?

La mayoría de los probadores de regex ejecutan el patrón en el hilo principal de la página. Eso funciona hasta que alguien pega un patrón que hace backtracking, momento en el que la pestaña deja de responder y el único remedio es cerrarla.

Backtracking catastrófico, y por qué no hay forma de detenerlo

El motor de regex de JavaScript hace backtracking: cuando una coincidencia falla reintenta cada camino alternativo. Para (a+)+b contra una racha de caracteres a que termina en otra cosa, los caminos son todas las formas de dividir la racha entre los dos cuantificadores — lo cual es exponencial. Treinta caracteres son alrededor de mil millones de intentos. No hay opción de tiempo límite, ni señal de aborto, ni punto donde ceder el control, ni excepción; el motor sencillamente no devuelve.

Esta es la clase de vulnerabilidad ReDoS, y tumba sistemas reales. La caída global de Cloudflare en 2019 fue un regex en una regla WAF; la caída de Stack Overflow en 2016 fue un patrón que hacía coincidir espacios en blanco en una publicación con una racha larga de ellos. Ambas fueron en un servidor, donde la consecuencia es que se bloquea cada petición en lugar de una sola pestaña.

Un worker se puede matar, y ese es todo el diseño

Así que la coincidencia se ejecuta en un Web Worker y un temporizador lo termina a los dos segundos. Ese es el único mecanismo disponible: nada puede detener un regex a mitad de ejecución, pero un hilo sí se puede destruir. Cuando salta el tiempo límite recibes un mensaje claro que nombra la causa probable en lugar de una pestaña congelada — que también es una señal útil, porque un patrón que aquí agota el tiempo es un patrón que colgaría un servidor.

El análisis estático nombra formas, no veredictos

El panel de riesgo busca cuantificadores anidados, alternancias cuantificadas con ramas que se solapan y cuantificadores adyacentes sobre los mismos caracteres. Eso explica casi todos los incidentes reales. Lo que deliberadamente no hace es declarar un patrón seguro: decidir si un regex arbitrario tiene un comportamiento exponencial en el peor caso es un problema de análisis de verdad, y una herramienta que prometiera seguridad estaría haciendo una promesa que no puede cumplir.

Las coincidencias de longitud cero necesitan un empujón

Un bucle de coincidencia global sobre un patrón que puede no coincidir con nada — a*, (?:), \b — nunca avanza lastIndex y se queda en bucle para siempre. Toda implementación correcta lo incrementa a mano tras una coincidencia vacía, y esta lo hace; es un detalle pequeño que rompe un número sorprendente de herramientas.

El desglose es por token

El patrón se divide en sus tokens con una nota en lenguaje llano para cada uno, porque el problema habitual con un regex que escribió otra persona no es que esté mal, sino que es ilegible. Las rachas de caracteres literales se agrupan en lugar de listarse una por una.

Patrón

(\w+)@(\w+\.\w+)

Coincidencias

1  [email protected]     at 58
   $1 ada  $2 example.com
2  [email protected]   at 271
   $1 grace  $2 example.org

¿Qué opciones y casos límite admite Probar expresiones regulares?

Referencia técnica y opciones
ParámetroTipoValor predeterminadoComportamiento y casos límite
g — globalbanderaactivadaEncuentra todas las coincidencias y no solo la primera. Sin esta bandera se devuelve un único resultado, haya los que haya.
i — ignorar mayúsculas y minúsculasbanderadesactivadaSin distinguir mayúsculas de minúsculas. Junto con la bandera u aplica también el plegado de mayúsculas y minúsculas de Unicode, que cubre más que ASCII.
m — multilíneabanderaactivada^ y $ coinciden en cada salto de línea, y no solo en los extremos del texto. Es casi siempre lo que quieres con entradas con forma de log.
s — dotAllbanderadesactivadaHace que . coincida también con un salto de línea. Sin ella, .* se detiene al final de la línea, que es el motivo habitual por el que falla una coincidencia multilínea.
u — UnicodebanderadesactivadaActiva los escapes \u{…} y las clases de propiedades \p{…}, y hace que el motor trabaje en puntos de código en lugar de unidades UTF-16 — que es lo que permite que . coincida con un emoji entero.
y — stickybanderadesactivadaCoincide solo en lastIndex, sin rastrear hacia delante. Se usa en tokenizadores, donde un hueco es un error de sintaxis y no algo que saltarse.
Tiempo de esperamilisegundos2000Pasado ese tiempo, el worker se termina. No hay forma de distinguir de antemano un patrón lento de uno exponencial, así que el tiempo transcurrido es la única señal disponible.
Límite de coincidenciasrecuento5000A partir de ahí los resultados se truncan, y el truncamiento se avisa. Renderizar cincuenta mil filas no le sirve a nadie.
Reemplazo$1, $<name>, $&—Grupos numerados, grupos con nombre y la coincidencia completa. $$ es un signo de dólar literal, que es el que todo el mundo olvida.

Preguntas frecuentes

¿Por qué se agotó el tiempo con mi patrón?

Casi con seguridad está haciendo backtracking catastrófico. Busca un cuantificador dentro de un grupo cuantificado — (a+)+, (\d*)* — o una alternancia cuantificada cuyas ramas pueden coincidir con el mismo texto, como (a|ab)+. El motor prueba todas las formas de dividir la entrada entre ellas, lo cual es exponencial en la longitud de la entrada. El panel de riesgo nombra la forma si encuentra alguna.

¿Cómo arreglo un patrón que hace backtracking?

Elimina la ambigüedad sobre qué parte coincide con qué. Sustituye (a+)+ por a+. Haz que las ramas de la alternancia sean mutuamente excluyentes, de modo que solo una pueda coincidir en cada posición. Ancla el patrón, para que un fallo no reintente desde cada carácter. Acota las repeticiones abiertas a lo que los datos realmente contienen. Y prefiere una clase de caracteres específica antes que un comodín: [^"]* hace mucho menos backtracking que .*.

¿ReDoS es un riesgo real o teórico?

Real, y caro. La caída global de Cloudflare en julio de 2019 fue una única expresión regular en una regla WAF; Stack Overflow cayó en 2016 por un patrón que hacía coincidir espacios en blanco al final cuando una publicación contenía una racha larga de ellos. En un servidor el modo de fallo es peor que aquí — cada petición hace cola detrás de la atascada en el motor, así que una sola entrada maliciosa tumba el servicio.

¿Por qué mi .* no cruza líneas?

Porque . no coincide con un salto de línea a menos que esté activada la flag s. Es la sorpresa de regex más común en JavaScript, y es fácil confundirla con la flag m — pero m cambia el significado de ^ y $, no el comportamiento de . Para una coincidencia que cruce líneas quieres s; para coincidencias ancladas a línea quieres m; son independientes y a menudo hacen falta ambas.

¿Debería validar una dirección de correo con un regex?

Solo a grandes rasgos. La gramática de RFC 5322 para una dirección llega a varios cientos de caracteres como patrón, acepta cosas que nadie espera — partes locales entre comillas, comentarios, dominios con IP literal — y aun así no puede decirte si el buzón existe. El patrón útil comprueba que haya una @ con algo a cada lado y un punto en el dominio; la validación de verdad es enviar un correo de confirmación.

¿Se sube mi patrón o el texto?

No. La coincidencia se ejecuta en un Web Worker dentro de tu navegador — un hilo separado en la misma pestaña, no un servidor. Puedes comprobarlo en el panel Network. Importa porque el texto de prueba de la gente suele ser un log de producción, y los logs de producción contienen direcciones de correo, tokens e identificadores de clientes.