Invalid Regular Expression

ID

php.bad_regex_syntax

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Php

Tags

regex, reliability

Description

Reports a preg_* pattern that PCRE cannot compile: an unbalanced group or character class, a *, + or ? with no atom to repeat, quantifier bounds in the wrong order, or an out-of-order character-class range.

Rationale

PCRE compiles the pattern on every call. A pattern it rejects therefore never matches anything — but the call does not stop the script:

// Bad
if (preg_match('/(unclosed/', $input)) {
    $this->accept($input);
}
  • preg_match emits a warning and returns false, which is falsy, so the branch is never taken.

  • A validation guard written this way accepts nothing, and a filter written this way filters nothing — silently, on every run, for whichever value of $input arrives.

  • The warning is normally invisible in production, where display_errors is off.

Remediation

Fix the pattern. Escape a metacharacter that was meant literally, close what was opened, and put quantifier and range bounds in ascending order.

// Good
if (preg_match('/\(unclosed/', $input)) {      // a literal parenthesis
    $this->accept($input);
}

preg_match('/x{1,3}/', $s);                    // ascending bounds
preg_match('/[a-z]/', $s);                     // ascending range

What is not reported

The PCRE grammar is not the JDK one, and several constructs that other engines reject are ordinary PHP. None of these is reported:

  • possessive quantifiers — /a++b/;

  • an unscoped inline flag group — /(?i)abc/;

  • the absolute anchors \A, \Z and \z;

  • a brace in a position where no quantifier can go — /{2}abc/ matches the text {2}abc;

  • a POSIX character class — /+/.

Two further carve-outs keep the rule quiet on valid patterns. A straight after a bare ( is left alone, because ( opens a control verb such as (*SKIP). And character-class balance is not judged at all once a [ appears inside a class, because the shared tokenizer reads that as a nested class while PCRE reads it as a literal bracket.

A dangling backslash is not checked because it cannot reach PCRE: PHP’s delimiter scan honours backslash escapes, so a backslash before the closing delimiter escapes it and the literal is reported by php.regular_expr_valid_delimiters instead.

© 2026 Xygeni. All rights reserved