Testeur d'expressions régulières
Testez un modèle par rapport à du texte réel, avec des correspondances mises en évidence et un arrêt brutal en cas de retour en arrière incontrôlé.
Pourquoi un testeur d’expressions régulières a besoin d’un worker
L’essentiel de la mécanique de cette page existe pour un seul mode de défaillance. Prenez /(a+)+b/ appliqué à trente a sans le moindre b. Le moteur doit décider comment répartir ces trente caractères entre le quantificateur intérieur et l’extérieur, et il y a 2³⁰ façons de le faire. Toutes sont essayées avant de pouvoir déclarer la correspondance impossible.
Le point capital, c’est qu’une expression régulière en cours d’exécution ne peut pas être interrompue. JavaScript est mono-thread : une garde par setTimeout ne peut donc se déclencher qu’une fois l’expression revenue — ce qui, pour cette entrée, se situe quelque part après la mort thermique de l’univers. L’onglet est perdu.
Le motif s’exécute donc dans un Web Worker, sur son propre fil, et la page principale tient un minuteur. Quand le minuteur l’emporte, worker.terminate() tue le fil net. C’est le seul mécanisme qui fonctionne réellement, et c’est pourquoi ce testeur vous dit que votre motif est dangereux au lieu de se transformer en onglet figé.
Cela mérite d’être intégré, car le même motif dans du code de production constitue un déni de service : une entrée fournie par l’utilisateur confrontée à une expression vulnérable, et une seule requête immobilise un cœur de processeur indéfiniment. Cette classe de bug s’appelle ReDoS.
Les formes à éviter
| Dangereux | Pourquoi | Plus sûr |
|---|---|---|
(a+)+ | Quantificateurs imbriqués — découpes exponentielles | a+ |
(\w|\s)* | Alternance à l’intérieur d’un quantificateur, branches qui se recouvrent | [\w\s]* |
(\d+)*$ | Groupe quantifié avec une ancre qui force l’exploration complète | \d*$ |
.*.*= | Deux jokers sans borne se disputant le même texte | [^=]*= |
Le facteur commun, c’est l’ambiguïté : plus d’une manière, pour le motif, de consommer les mêmes caractères. Une classe de caractères fait le travail d’une alternance sans l’ambiguïté — d’où le fait que la colonne « plus sûr » consiste surtout à remplacer | par [].
Gourmand, paresseux, et le bug que tout le monde écrit une fois
Les quantificateurs sont gourmands par défaut : .+ prend tout ce qu’il peut et ne rend des caractères qu’à contrecœur. Face à <b>bold</b> :
| Motif | Correspond à |
|---|---|
<.+> | <b>bold</b> — le tout |
<.+?> | <b> — paresseux, il s’arrête au premier > |
<[^>]+> | <b> — et plus rapide, car il n’y a rien à revenir défaire |
C’est la troisième forme qu’il faut choisir. Une classe de caractères négative ne peut pas dépasser sa cible : il n’y a donc aucun retour arrière à défaire — elle est à la fois plus claire sur l’intention et moins coûteuse à exécuter que la version paresseuse.
lastIndex, ou pourquoi une correspondance sur deux disparaît
Un RegExp portant le drapeau g a un état. Il conserve un lastIndex qui avance après chaque exec ou test : réutiliser le même objet d’un appel à l’autre reprend donc là où il s’était arrêté :
const re = /\d+/g;
re.test('123'); // true, lastIndex is now 3
re.test('123'); // false! it resumed from index 3Créez l’expression à l’intérieur de la boucle, remettez lastIndex = 0, ou utilisez matchAll, qui s’en charge pour vous. Le testeur ci-dessus crée un objet neuf à chaque exécution : ce que vous voyez ici est donc le comportement du premier appel.
Là où les expressions régulières de JavaScript diffèrent
Les résultats obtenus ici correspondent exactement à ceux de Node et du code navigateur, puisque c’est le même moteur. Face à d’autres langages, les différences qui surprennent :
- Ni groupes atomiques ni quantificateurs possessifs. PCRE dispose de
(?>...)et dea++pour empêcher tout retour arrière ; JavaScript n’a ni l’un ni l’autre, et c’est pourquoi il y est plus facile d’écrire un ReDoS. - Pas de récursion. Vous ne pouvez pas reconnaître des parenthèses équilibrées avec une expression régulière JavaScript. Si c’est le besoin, il vous faut un analyseur syntaxique.
- La recherche arrière est arrivée tard.
(?<=...)est prise en charge par les navigateurs actuels mais pas par les anciennes versions de Safari : vérifiez vos cibles. - RE2, en Go, est une autre machine. Temps linéaire garanti, ni rétroréférences ni recherches autour. Si vous vous êtes déjà demandé pourquoi Go a retiré ces fonctionnalités, cette page en est la raison.
À voir aussi
Si le texte que vous confrontez au motif est du JSON, le formater d’abord rend généralement le motif inutile — sur des données structurées, un analyseur bat une expression régulière à tous les coups. Pour planifier plutôt que reconnaître, l’outil voisin est l’analyseur cron.
Questions sur les expressions régulières
Pourquoi mon schéma s'est-il arrêté après deux secondes ?
Parce que c’était un retour en arrière catastrophique. Certaines formes - des quantificateurs imbriqués comme (a+)+ ou (\w|\s)* - obligent le moteur à essayer un nombre exponentiel de façons de correspondre, de sorte qu'une chaîne de quarante caractères peut prendre plus de temps que l'univers n'a existé. Il n'y a aucun moyen d'interrompre une expression régulière en cours d'exécution à partir de JavaScript, c'est pourquoi le modèle s'exécute ici dans un Web Worker : le travailleur peut être arrêté et la page reste réactive. Un délai d'attente sur le thread principal ne se déclencherait qu'une fois l'expression régulière terminée, ce qui n'est jamais le cas pour ces modèles.
Est-ce que cela utilise le même moteur d'expression régulière que mon code ?
Il utilise celui de votre navigateur, qui est le moteur JavaScript. Les résultats correspondent donc exactement au code du nœud et du navigateur. Ils ne correspondront pas toujours à PCRE, Python, Go ou Java. JavaScript n'a pas de groupes atomiques, pas de quantificateurs possessifs et pas de récursion ; lookbehind est pris en charge dans les navigateurs actuels mais est arrivé tardivement. Le RE2 de Go est une conception entièrement différente et n'a délibérément aucune référence arrière, ce qui est précisément ainsi qu'il évite le problème de retour en arrière ci-dessus.
Pourquoi mon expression régulière globale ne trouve-t-elle qu'une correspondance sur deux ?
Un objet RegExp avec l'indicateur g porte un lastIndex qui avance à chaque appel, donc la réutilisation du même objet dans des appels d'exécution ou de test distincts reprend là où elle s'était arrêtée. Il s’agit de l’une des parties de l’API les plus déroutantes. Soit créez l'expression régulière à chaque fois, soit réinitialisez lastIndex à 0 avant utilisation, soit utilisez matchAll qui le gère pour vous.
Quelle est la différence entre un quantificateur gourmand et un quantificateur paresseux ?
Un quantificateur gourmand prend autant qu'il peut et ne restitue les caractères que lorsqu'il est forcé ; un paresseux (écrit avec une fin ?) prend le moins possible et ne se développe que lorsque cela est nécessaire. Par rapport à <b>gras</b>, le motif <.+> correspond à la chaîne entière, car .+ avale tout et revient en arrière juste assez pour trouver un > final. L'écriture de <.+?> correspond uniquement à <b>. C’est la raison la plus courante pour laquelle un modèle correspond bien plus que prévu.
Est-il sécuritaire de coller des données réelles ici ?
Oui. Le modèle et le texte sont remis à un travailleur à l'intérieur de cette page et y sont évalués. Rien n’est envoyé à un serveur – ce qui permet également d’annuler un modèle d’emballement, puisqu’il n’y a aucune demande à attendre.
Dernière vérification . Vous avez repéré quelque chose de dépassé ? Dites-le-nous.
