Skip to content

Commit c1cb146

Browse files
authored
Add POSIX BRE grammar support: BreRegExp + shared-core refactor
1 parent bc5cecf commit c1cb146

3 files changed

Lines changed: 429 additions & 52 deletions

File tree

cpp/ql/lib/semmle/code/cpp/regex/RegexFlowConfigs.qll

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
* The `regexMatchedAgainst` predicate mirrors the intent of the Java
1414
* `RegexFlowConfigs.qll` library.
1515
*
16-
* Only ECMAScript-grammar regexes are considered analyzable by the Phase 1
17-
* parser; literals explicitly constructed with a non-ECMAScript grammar flag
18-
* (`basic`, `extended`, `awk`, `grep`, `egrep`) are excluded.
16+
* All standard `std::regex` grammars are now modelled: ECMAScript (default),
17+
* POSIX BRE (`basic`/`grep`), and POSIX ERE (`extended`/`egrep`/`awk`).
18+
* Grammar selection and ReDoS-eligibility are independent axes — see
19+
* `isBacktrackingEngine` for the latter.
1920
*/
2021

2122
import cpp
@@ -368,10 +369,9 @@ predicate hasMultilineFlag(StringLiteral regex) {
368369
* Holds if `regex` is constructed with an explicit non-ECMAScript grammar
369370
* flag (`basic`, `extended`, `awk`, `grep`, or `egrep`).
370371
*
371-
* This predicate remains a purely flag-level classification and does not
372-
* gate the parser directly — the parser now uses `regexGrammar` to decide
373-
* which dialect to apply. ECMAScript and ERE (`extended`/`egrep`/`awk`)
374-
* are both modelled; only BRE (`basic`/`grep`) is currently excluded.
372+
* This predicate is purely informational: nothing is excluded from analysis
373+
* by grammar anymore, since every grammar the standard defines now has a
374+
* concrete parser subclass (`EcmaRegExp`, `EreRegExp`, `BreRegExp`).
375375
*/
376376
predicate hasNonEcmaScriptGrammarFlag(StringLiteral regex) {
377377
exists(string g | g = ["basic", "extended", "awk", "grep", "egrep"] |
@@ -388,18 +388,17 @@ predicate hasNonEcmaScriptGrammarFlag(StringLiteral regex) {
388388
*
389389
* - `Ecma()` — ECMAScript, the default grammar used by `std::regex`.
390390
* Selected either implicitly (no explicit grammar flag) or
391-
* explicitly via `std::regex_constants::ECMAScript`.
391+
* explicitly via `std::regex_constants::ECMAScript`. Modelled
392+
* by `EcmaRegExp`.
392393
* - `Bre()` — POSIX Basic Regular Expressions (selected via the `basic`
393-
* or `grep` flags). Not yet modelled by the parser; regexes
394-
* in this grammar are excluded from analysis.
394+
* or `grep` flags). Modelled by `BreRegExp`.
395395
* - `Ere()` — POSIX Extended Regular Expressions (selected via the
396396
* `extended`, `egrep`, or `awk` flags). Modelled by
397397
* `EreRegExp`.
398398
*
399-
* The `Ecma()` and `Ere()` cases are both exercised by the parser today.
400-
* The `Bre()` case is scaffolding for a future phase and is not exercised;
401-
* regexes classified as `Bre()` are excluded by the `RegExp` characteristic
402-
* predicate.
399+
* All three cases are exercised by the parser today; every grammar has a
400+
* concrete subclass, so `hasConcreteGrammar` holds for every regex the
401+
* parser sees.
403402
*/
404403
newtype TRegexGrammar =
405404
Ecma() or
@@ -415,9 +414,8 @@ newtype TRegexGrammar =
415414
* - `extended` / `egrep` / `awk` → `Ere()`
416415
* - anything else (default, explicit `ECMAScript`, or unresolved) → `Ecma()`
417416
*
418-
* The parser gates on `regexGrammar in [Ecma(), Ere()]`, so both branches
419-
* are exercised by the parser today. Regexes classified as `Bre()` are
420-
* excluded from analysis until a future phase adds BRE support.
417+
* Every case now has a concrete parser subclass, so `hasConcreteGrammar`
418+
* holds for the result of this predicate.
421419
*/
422420
TRegexGrammar regexGrammar(StringLiteral regex) {
423421
if containsRegexFlag(getConstructionFlagArg(regex), ["basic", "grep"])
@@ -428,6 +426,17 @@ TRegexGrammar regexGrammar(StringLiteral regex) {
428426
else result = Ecma()
429427
}
430428

429+
/**
430+
* Holds if `grammar` has a concrete `RegExp` subclass and can therefore be
431+
* admitted by the parser's characteristic predicate. All three grammars
432+
* (`Ecma()`, `Ere()`, `Bre()`) are modelled today, so this holds for every
433+
* grammar the standard defines. Kept as a helper so that any future grammar
434+
* scaffolding can be admitted by adding a single disjunct here.
435+
*/
436+
predicate hasConcreteGrammar(TRegexGrammar grammar) {
437+
grammar = Ecma() or grammar = Ere() or grammar = Bre()
438+
}
439+
431440
/**
432441
* Holds if `regex` is constructed with an explicit ECMAScript grammar flag.
433442
* This is the default, and also the case that the Phase 1 parser handles.

0 commit comments

Comments
 (0)