mirror of
https://github.com/github/codeql.git
synced 2026-07-23 12:12:05 +02:00
570 lines
22 KiB
C++
570 lines
22 KiB
C++
// Minimal std::regex/std::basic_string stubs, mirroring the ones in
|
|
// cpp/ql/test/library-tests/regex/test.cpp. Real headers are not available
|
|
// in the extractor sandbox.
|
|
|
|
namespace std {
|
|
|
|
template <class CharT>
|
|
class basic_string {
|
|
public:
|
|
basic_string() {}
|
|
basic_string(const CharT*) {}
|
|
const CharT* c_str() const { return 0; }
|
|
const CharT* data() const { return 0; }
|
|
unsigned long size() const { return 0; }
|
|
};
|
|
|
|
typedef basic_string<char> string;
|
|
|
|
namespace regex_constants {
|
|
enum syntax_option_type {
|
|
ECMAScript = 1, basic = 2, extended = 4, awk = 8, grep = 16, egrep = 32,
|
|
icase = 256, nosubs = 512, optimize = 1024, collate = 2048, multiline = 4096
|
|
};
|
|
enum match_flag_type { match_default = 0 };
|
|
}
|
|
|
|
template <class CharT>
|
|
class basic_regex {
|
|
public:
|
|
typedef regex_constants::syntax_option_type flag_type;
|
|
basic_regex(const CharT* p) {}
|
|
basic_regex(const CharT* p, flag_type f) {}
|
|
basic_regex(const basic_string<CharT>& p) {}
|
|
};
|
|
|
|
typedef basic_regex<char> regex;
|
|
|
|
template <class CharT>
|
|
bool regex_match(const basic_string<CharT>& s, const basic_regex<CharT>& re) { return false; }
|
|
template <class CharT>
|
|
bool regex_search(const basic_string<CharT>& s, const basic_regex<CharT>& re) { return false; }
|
|
template <class CharT>
|
|
bool regex_search(const CharT* s, const basic_regex<CharT>& re) { return false; }
|
|
template <class CharT>
|
|
basic_string<CharT> regex_replace(const basic_string<CharT>& s, const basic_regex<CharT>& re,
|
|
const basic_string<CharT>& fmt) { return basic_string<CharT>(); }
|
|
|
|
// Iterator stubs - just enough for a construction to be recognized as
|
|
// "regex used as regex" by RegexFlowConfigs.
|
|
template <class BidirIt, class CharT>
|
|
class regex_iterator {
|
|
public:
|
|
regex_iterator() {}
|
|
regex_iterator(BidirIt first, BidirIt last, const basic_regex<CharT>& re) {}
|
|
};
|
|
|
|
template <class BidirIt, class CharT>
|
|
class regex_token_iterator {
|
|
public:
|
|
regex_token_iterator() {}
|
|
regex_token_iterator(BidirIt first, BidirIt last, const basic_regex<CharT>& re) {}
|
|
};
|
|
|
|
typedef regex_iterator<const char*, char> cregex_iterator;
|
|
typedef regex_token_iterator<const char*, char> cregex_token_iterator;
|
|
|
|
// Stubs for a couple of C standard-library sources modeled by
|
|
// `semmle.code.cpp.security.FlowSources` so that we can exercise
|
|
// non-`argv` sources in the polynomial-ReDoS test suite.
|
|
char* getenv(const char* name);
|
|
typedef unsigned long size_t;
|
|
struct FILE;
|
|
size_t fread(void* ptr, size_t sz, size_t n, FILE* stream);
|
|
|
|
} // namespace std
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Length-restricted helper stubs. These deliberately match the name-based
|
|
// heuristic in PolynomialReDoSQuery::LengthRestrictedFunction so that values
|
|
// returned from them act as barriers.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
struct Request {
|
|
std::string getHeader(const char* name) const { return std::string(); }
|
|
std::string getRequestUri() const { return std::string(); }
|
|
std::string getRequestUrl() const { return std::string(); }
|
|
std::string getUserAgent() const { return std::string(); }
|
|
std::string getPath() const { return std::string(); } // matches "%get%path%"
|
|
std::string getUserName() const { return std::string(); } // matches "get%user%"
|
|
std::string getQueryString() const { return std::string(); } // matches "%querystring%"
|
|
};
|
|
|
|
struct Cookie {
|
|
std::string getValue() const { return std::string(); } // Cookie::get% matches
|
|
};
|
|
|
|
// A non-restricted source: a plain member function unrelated to headers/cookies.
|
|
struct Widget {
|
|
std::string readAll() const { return std::string(); }
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Tests
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// argv is a LocalFlowSource (and hence a FlowSource).
|
|
int main(int argc, char** argv) { // $ Source
|
|
std::string input(argv[1]);
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 1. Original coverage (kept for regression stability).
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: polynomial-backtracking pattern applied to user input.
|
|
{
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(input, re, std::string("")); // $ Alert
|
|
}
|
|
|
|
// Note: passing `argv[1]` (a raw `char*`) inline to `regex_search`
|
|
// is not currently modeled by the Phase 2 `regexMatchedAgainst`
|
|
// predicate (which only tracks values reaching a `std::regex`
|
|
// variable), so no alert is expected here.
|
|
{
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_search(argv[1], re);
|
|
}
|
|
|
|
// BAD: quadratic \\d+E?\\d+ pattern applied to user input.
|
|
{
|
|
std::regex re("^0\\.\\d+E?\\d+$");
|
|
std::regex_search(input, re); // $ Alert
|
|
}
|
|
|
|
// GOOD: pattern is linear (a fixed prefix, no ambiguous repetition).
|
|
{
|
|
std::regex re("^abc.*$");
|
|
std::regex_search(input, re);
|
|
}
|
|
|
|
// GOOD: input is length-restricted (a header-like getter).
|
|
{
|
|
Request req;
|
|
std::string h = req.getHeader("X-Foo");
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(h, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: input is not user-controlled.
|
|
{
|
|
std::string s("literal");
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 2. Additional superlinear patterns applied to user input (BAD).
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: (a|b)*a$ - quadratic on strings of a's followed by a non-match.
|
|
{
|
|
std::regex re("^(a|b)*a$");
|
|
std::regex_match(input, re);
|
|
}
|
|
|
|
// BAD: \s+X? repeated - quadratic on strings of whitespace.
|
|
{
|
|
std::regex re("\\s+X?\\s+$");
|
|
std::regex_search(input, re); // $ Alert
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 3. Coverage of every modeled match API.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: std::regex_match with user input.
|
|
{
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_match(input, re); // $ Alert
|
|
}
|
|
|
|
// BAD: std::regex_search with user input.
|
|
{
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_search(input, re); // $ Alert
|
|
}
|
|
|
|
// BAD: std::regex_replace with user input (already covered above; kept for
|
|
// symmetry with the other APIs).
|
|
{
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(input, re, std::string("")); // $ Alert
|
|
}
|
|
|
|
// BAD: std::regex_iterator constructed with a superlinear regex - the
|
|
// literal is recognized as a std::regex pattern by Phase 2, so the
|
|
// pattern's terms are analyzed by the polynomial ReDoS query.
|
|
// Note: the iterator constructor does not itself take a `std::string`
|
|
// subject, so no cpp/polynomial-redos alert is expected here; this case
|
|
// exists to confirm the pattern literal is still parsed (a
|
|
// cpp/redos alert would fire if the pattern were exponential).
|
|
{
|
|
std::regex re("^\\s+|\\s+$");
|
|
(void)std::cregex_iterator(input.data(), input.data() + input.size(), re);
|
|
}
|
|
|
|
// Same for std::regex_token_iterator.
|
|
{
|
|
std::regex re("^\\s+|\\s+$");
|
|
(void)std::cregex_token_iterator(input.data(), input.data() + input.size(), re);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 4. Additional length-restricted / barrier true-negatives (GOOD).
|
|
// -------------------------------------------------------------------------
|
|
|
|
// GOOD: request URI is treated as length-restricted.
|
|
{
|
|
Request req;
|
|
std::string s = req.getRequestUri();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: request URL is treated as length-restricted.
|
|
{
|
|
Request req;
|
|
std::string s = req.getRequestUrl();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: user agent is treated as length-restricted.
|
|
{
|
|
Request req;
|
|
std::string s = req.getUserAgent();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: Request::getPath matches the request/path member-getter heuristic.
|
|
{
|
|
Request req;
|
|
std::string s = req.getPath();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: Request::getUserName matches the request/user member-getter heuristic.
|
|
{
|
|
Request req;
|
|
std::string s = req.getUserName();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: query string is treated as length-restricted.
|
|
{
|
|
Request req;
|
|
std::string s = req.getQueryString();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: Cookie::getValue matches the "cookie" class + "get%" prefix heuristic.
|
|
{
|
|
Cookie c;
|
|
std::string s = c.getValue();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// GOOD: integer types are treated as small-fixed-size and act as barriers.
|
|
// (No direct string flow, but demonstrates isSmallFixedSizeType.)
|
|
{
|
|
int n = argc; // integer, small fixed size
|
|
(void)n;
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_search(input, re); // $ Alert // BAD (already reported above)
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 5. Non-user-controlled subjects with a superlinear pattern (GOOD).
|
|
// -------------------------------------------------------------------------
|
|
|
|
// GOOD: subject is a compile-time literal, not user-controlled.
|
|
{
|
|
std::string s("static-value");
|
|
std::regex re("^(a|b)*a$");
|
|
std::regex_match(s, re);
|
|
}
|
|
|
|
// GOOD: subject comes from an ordinary (non-source) function.
|
|
{
|
|
Widget w;
|
|
std::string s = w.readAll();
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string(""));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 6. Flag / dialect gating.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: icase does not suppress the polynomial alert.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::icase);
|
|
std::regex_replace(input, re, std::string("")); // $ Alert
|
|
}
|
|
|
|
// GOOD: BRE grammar (`basic`) is now modeled by the parser
|
|
// (`BreRegExp`, Phase D). Under BRE, the pattern `^\s+|\s+$` parses
|
|
// as: `^` anchor, literal `\s`, literal `+`, literal `|`, literal
|
|
// `\s`, literal `+`, `$` anchor - there is no `+` quantifier in BRE
|
|
// (bare `+` is a literal) and no `|` alternation, so the polynomial
|
|
// shape disappears and the pattern is not flagged. (See Section 12
|
|
// below for the proper BRE spelling with `\{1,\}` intervals.)
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::basic);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// BAD: extended selects the ERE grammar (modeled by `EreRegExp` since
|
|
// Phase C) and is backtracking-eligible, so the polynomial engine
|
|
// flags the superlinear pattern on user input - same as the
|
|
// ECMAScript case.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::extended);
|
|
std::regex_replace(input, re, std::string("")); // $ Alert
|
|
}
|
|
// GOOD: awk parses as ERE (same as `extended`) but is excluded from
|
|
// the ReDoS queries by `isBacktrackingEngine`, *not* by any
|
|
// parsing/grammar difference.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::awk);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// GOOD: grep selects BRE (`BreRegExp`, Phase D) - the pattern is
|
|
// parsed, but `grep` is excluded from the polynomial-ReDoS query by
|
|
// `isBacktrackingEngine`.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::grep);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// GOOD: egrep parses as ERE (same as `extended`) but is excluded from
|
|
// the ReDoS queries by `isBacktrackingEngine`, *not* by any
|
|
// parsing/grammar difference.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::egrep);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 7. Additional superlinear patterns (ported from Java/JS polynomial suites).
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: (\d+)*$ - the outer `*` allows quadratic backtracking on non-matching
|
|
// digit-heavy input (Java polynomial-ReDoS test `Test.java`).
|
|
{
|
|
std::regex re("(\\d+)*$");
|
|
std::regex_search(input, re); // $ Alert
|
|
}
|
|
|
|
// BAD: .*.*=.* - classic polynomial pattern, cf. JS
|
|
// `polynomial-redos/tst.js`.
|
|
{
|
|
std::regex re(".*.*=.*");
|
|
std::regex_search(input, re); // $ Alert
|
|
}
|
|
|
|
// GOOD (observed): `^(\w+\s?)*$` - a "trim-and-split" style pattern that
|
|
// the shared engine's polynomial analysis does not currently flag as
|
|
// super-linear (the optional `\s?` inside the outer `*` does not
|
|
// produce a polynomial-backtracking pivot term). The Java and
|
|
// JavaScript polynomial-ReDoS queries behave the same way on this
|
|
// shape; documented here as a negative case rather than divergence.
|
|
{
|
|
std::regex re("^(\\w+\\s?)*$");
|
|
std::regex_match(input, re);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 8. Source variety - non-`argv` C++ threat-model sources.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: `std::getenv` is modeled as a LocalFlowSource.
|
|
{
|
|
const char* env = std::getenv("USER_REGEX_INPUT"); // $ Source
|
|
std::string s(env);
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string("")); // $ Alert
|
|
}
|
|
|
|
// BAD: `std::fread` is modeled as a RemoteFlowSource (bytes read from a
|
|
// stream / file). This exercises the RemoteFlowSource half of the
|
|
// `FlowSource` union used by `PolynomialRedosConfig::isSource`.
|
|
{
|
|
char buf[256];
|
|
std::fread(buf, 1, sizeof(buf), (std::FILE*)0); // $ Source
|
|
std::string s(buf);
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(s, re, std::string("")); // $ Alert
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 9. Sanitized / length-checked user input (GOOD).
|
|
//
|
|
// The polynomial ReDoS query treats calls to `LengthRestrictedFunction`s
|
|
// as barriers, so user input that has been laundered through such a
|
|
// getter (here: `Request::getHeader`, which is a header-style getter)
|
|
// should not produce an alert even though the pattern is superlinear
|
|
// and the *original* value was user-controlled.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// GOOD: `argv[1]` is user-controlled but is passed through a
|
|
// header-style barrier before reaching the regex match.
|
|
{
|
|
Request req;
|
|
std::string sanitized = req.getHeader(argv[1]);
|
|
std::regex re("^\\s+|\\s+$");
|
|
std::regex_replace(sanitized, re, std::string(""));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 10. Overlap sanity: an exponential pattern on user input.
|
|
//
|
|
// The shared engine splits reporting between `cpp/redos` (exponential)
|
|
// and `cpp/polynomial-redos` (polynomial). Many "exponential" nested-
|
|
// quantifier patterns also contain a polynomial-backtracking sub-term,
|
|
// so BOTH queries fire - matching the behaviour observed in the Java
|
|
// and JavaScript suites. `(a+)+b` is such a pattern: `cpp/redos`
|
|
// reports the outer nested quantifier, and `cpp/polynomial-redos`
|
|
// reports the inner `a+` term.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD (also reported by cpp/redos): the inner `a+` is a polynomial
|
|
// backtracking term on user input.
|
|
{
|
|
std::regex re("(a+)+b");
|
|
std::regex_match(input, re); // $ Alert
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 11. Non-backtracking POSIX tool-style grammars (`awk`, `grep`, `egrep`)
|
|
// combined with other flags via bitwise-OR.
|
|
//
|
|
// Regression guard for the `isBacktrackingEngine` gate: `awk`,
|
|
// `egrep`, and `grep` are all parsed (`awk`/`egrep` as ERE via
|
|
// `EreRegExp` in Phase C; `grep` as BRE via `BreRegExp` in Phase D)
|
|
// - the same trees the `extended`/`basic` positive cases would be
|
|
// analysed with - so suppression here is exclusively due to
|
|
// `isBacktrackingEngine`, not any parsing/grammar difference.
|
|
//
|
|
// The equivalent default-grammar (ECMAScript) patterns above fire,
|
|
// showing that the gate - not some unrelated exclusion - suppresses
|
|
// these cases.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// GOOD: awk grammar - non-backtracking.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::awk);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// GOOD: grep grammar combined with icase - non-backtracking.
|
|
{
|
|
std::regex re("^\\s+|\\s+$",
|
|
(std::regex_constants::syntax_option_type)
|
|
(std::regex_constants::grep | std::regex_constants::icase));
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// GOOD: egrep grammar combined with icase - non-backtracking.
|
|
{
|
|
std::regex re("^\\s+|\\s+$",
|
|
(std::regex_constants::syntax_option_type)
|
|
(std::regex_constants::egrep | std::regex_constants::icase));
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 12. End-to-end ERE-grammar coverage for cpp/polynomial-redos.
|
|
//
|
|
// These cases exercise the ERE parser (Phase C, `EreRegExp`) end-to-end
|
|
// through the shared backtracking engine with a user-controlled
|
|
// subject. All three flags below (`extended`, `egrep`, `awk`) select
|
|
// the *same* grammar (ERE) and therefore produce structurally-
|
|
// identical parse trees for the same pattern string - the only axis
|
|
// they differ on is `isBacktrackingEngine`:
|
|
//
|
|
// * `extended` -> ERE + backtracking-eligible -> source->sink alert.
|
|
// * `egrep` / `awk` -> ERE + non-backtracking -> NO alert.
|
|
//
|
|
// The pattern `^\s+|\s+$` (the same superlinear trim pattern used by
|
|
// the existing polynomial cases above) uses only ERE-legal constructs
|
|
// (`\s` is an ERE-legal escape), so the ERE parse tree is
|
|
// structurally equivalent to the ECMAScript one. The taint source
|
|
// (`argv[1]` -> `input`) and the match harness (`std::regex_replace`)
|
|
// are reused verbatim so that only the grammar flag differs.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: ERE grammar via `extended` - parsed as ERE and backtracking-eligible.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::extended);
|
|
std::regex_replace(input, re, std::string("")); // $ Alert
|
|
}
|
|
// GOOD: same pattern/flow under `egrep` - parses identically as ERE
|
|
// but excluded by `isBacktrackingEngine`.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::egrep);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// GOOD: same pattern/flow under `awk` - parses identically as ERE but
|
|
// excluded by `isBacktrackingEngine`.
|
|
{
|
|
std::regex re("^\\s+|\\s+$", std::regex_constants::awk);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// GOOD: `egrep | icase` bitwise-OR combination - still ERE-parsed and
|
|
// still excluded by `isBacktrackingEngine`.
|
|
{
|
|
std::regex re("^\\s+|\\s+$",
|
|
(std::regex_constants::syntax_option_type)
|
|
(std::regex_constants::egrep | std::regex_constants::icase));
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 13. End-to-end BRE-grammar coverage for cpp/polynomial-redos.
|
|
//
|
|
// These cases exercise the BRE parser (Phase D, `BreRegExp`) end-to-end
|
|
// through the shared backtracking engine with a user-controlled
|
|
// subject. Both flags below (`basic`, `grep`) select the *same* grammar
|
|
// (BRE) and therefore produce structurally-identical parse trees for
|
|
// the same pattern string - the only axis they differ on is
|
|
// `isBacktrackingEngine`:
|
|
//
|
|
// * `basic` -> BRE + backtracking-eligible -> source->sink alert.
|
|
// * `grep` -> BRE + non-backtracking -> NO alert.
|
|
//
|
|
// The pattern must be expressed in BRE's inverted metacharacter
|
|
// convention: `\{1,\}` is the "one-or-more" interval quantifier (BRE
|
|
// has no `+`) and there is no `|` alternation, so the ECMAScript
|
|
// `\s+.*\s+` polynomial shape is spelled here as
|
|
// `[[:space:]]\{1,\}.*[[:space:]]\{1,\}` - a superlinear pattern
|
|
// matched against the tainted subject.
|
|
// -------------------------------------------------------------------------
|
|
|
|
// BAD: BRE grammar via `basic` - parsed as BRE and backtracking-eligible.
|
|
{
|
|
std::regex re("[[:space:]]\\{1,\\}.*[[:space:]]\\{1,\\}$",
|
|
std::regex_constants::basic);
|
|
std::regex_replace(input, re, std::string("")); // $ Alert
|
|
}
|
|
// GOOD: same pattern/flow under `grep` - parses identically as BRE
|
|
// but excluded by `isBacktrackingEngine`.
|
|
{
|
|
std::regex re("[[:space:]]\\{1,\\}.*[[:space:]]\\{1,\\}$",
|
|
std::regex_constants::grep);
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
// GOOD: `grep | icase` bitwise-OR combination - still BRE-parsed and
|
|
// still excluded by `isBacktrackingEngine`.
|
|
{
|
|
std::regex re("[[:space:]]\\{1,\\}.*[[:space:]]\\{1,\\}$",
|
|
(std::regex_constants::syntax_option_type)
|
|
(std::regex_constants::grep | std::regex_constants::icase));
|
|
std::regex_replace(input, re, std::string(""));
|
|
}
|
|
|
|
return 0;
|
|
}
|