Skip to main content

Dialects

Describes the regular expression dialects that recheck supports, and what is common to all of them.

Each dialect has its own page on how it is checked: JavaScript / Python / Ruby / Java / .NET / Perl / PCRE.

Choosing a Dialect​

recheck checks a regular expression in the syntax and the semantics of one dialect.

The dialect parameter chooses it (the default is javascript). It is the name of the dialect, or an object of the name and the dialect's settings (e.g. { "name": "python", "type": "bytes" }).

The following shows how to check a pattern in the Python dialect.

$ recheck --dialect=python '/^(a|a)*$/'
$ recheck --dialect=python --python-type=bytes '/^(a|a)*$/'
note

The ESLint plugin (eslint-plugin-redos) checks JavaScript regular expressions only, so it has no dialect option.

Supported Dialects​

dialectEngine that recheck followsMatching unit
javascriptECMAScript 2026 RegExp (V8)utf16, or utf16-code-point with u or v
pythonPython 3.15 recode-point (str), or byte (bytes)
rubyRuby 4.0 Regexp (Onigmo)scalar-value (UTF-8), or byte (ASCII-8BIT)
javaJava 27 java.util.regex.Patternutf16-code-point
dotnet.NET 10 System.Text.RegularExpressions.Regexutf16
perlPerl 5.44 regular expressionscode-point (utf8), or byte (no utf8)
pcrePCRE2 10.49 as PHP's preg_* functions use itbyte, or scalar-value with u or (*UTF)

Matching Units​

A (matching) unit is what a pattern reads as one character: . matches exactly one unit.

For example, "😀" (U+1F600) is one code point, two UTF-16 code units (\ud83d\ude00), and four bytes in UTF-8 (\xf0\x9f\x98\x80). Which of them . matches depends on the dialect, and on its flags or the type of the input.

In JavaScript, a pattern reads UTF-16 code units, and the u or v flag makes it read code points.

"😀".match(/./)[0]; // => "\ud83d" (one UTF-16 code unit without `u`)
"😀".match(/./u)[0]; // => "😀" (one code point with `u`)

In Python, a pattern reads code points when it is a str, and bytes when it is a bytes, which also takes bytes as the input.

re.match(".", "😀").group() # => '😀' (one code point in a `str` pattern)
re.match(b".", "😀".encode()).group() # => b'\xf0' (one byte in a `bytes` pattern)

The matching units are of the following kinds, where the kinds of code points differ in what the surrogates are.

  • utf16: UTF-16 code units.
  • utf16-code-point: code points read from UTF-16, where a lone surrogate is a code point, but a high surrogate followed by a low surrogate is one code point.
  • code-point: code points, where the surrogates are ordinary code points in any order.
  • scalar-value: Unicode scalar values, which are the code points other than the surrogates.
  • byte: bytes.

The table in Supported Dialects shows the matching units of each dialect.

Syntax Limitations​

recheck reads the whole syntax of the supported dialects. It also reproduces, as far as possible, the errors that the engine reports after reading the syntax. A pattern that the engine rejects is reported as unknown with an invalid error.

Some constructs are recognized, but the checkers do not handle them. A pattern having one of them is reported as unknown with an unsupported error naming the construct (e.g. a(?R)? gives unsupported syntax (RCall) at 1). Each dialect's page lists the constructs that it does not support (e.g. .NET's balancing groups).

The following are not supported in any dialect.

  • Nesting deeper than 150 levels, where each group, look-around condition of a conditional, class, operand of a class set operation, and repeat of a repeat is one level (e.g. (?: repeated 151 times gives Too deeply nested (limit 150) at 450).

Approximations​

recheck models the matching of a naive backtracking engine. Most engines have optimizations that make some vulnerable patterns fast, and recheck does not model them (see each dialect's page).

The following is what we measured with ^(a|a)*$ against 'a'.repeat(n) + '!'.

  • V8 (Node.js 26) and Python 3.15 take exponential time: about 2 seconds for n = 24.
  • Ruby 4.0, Java 27, .NET 10 and Perl 5.44 finish in less than 1 ms even for n = 28.
  • PHP 8.5 (PCRE2 10.49 with the default pcre.backtrack_limit) gives up with the backtrack limit error for n = 20.

So recheck reports ^(a|a)*$ as vulnerable in every dialect, but the attack is not effective on some engines. Runtime-Attack Verification runs the attack string on the real engine, and it tells whether the attack works there.

The following approximations are common to the dialects.

  • The Unicode data (properties, case mappings) is of Unicode 18.0, while the engines use Unicode 16 or 17.
  • Look-arounds (including non-atomic ones), back-references, atomic groups, possessive quantifiers, conditionals and the backtracking control verbs other than (*FAIL) and (*MARK) are handled by the fuzz checker, not by the automaton checker.