Convert ReDoS query tests to inline test expectations

This commit is contained in:
copilot-swe-agent[bot]
2026-07-17 18:27:51 +00:00
committed by GitHub
parent 09938f0531
commit 4be7ff07c3
4 changed files with 59 additions and 57 deletions

View File

@@ -1 +1,2 @@
query: Security/CWE/CWE-1333/ReDoS.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -71,58 +71,58 @@ int main(int argc, char** argv) {
// -------------------------------------------------------------------------
// BAD: (a*)*b
{ std::regex re("(a*)*b"); run(re, input); }
{ std::regex re("(a*)*b"); run(re, input); } // $ Alert
// BAD: (a+)*b
{ std::regex re("(a+)*b"); run(re, input); }
{ std::regex re("(a+)*b"); run(re, input); } // $ Alert
// BAD: (a*)+b
{ std::regex re("(a*)+b"); run(re, input); }
{ std::regex re("(a*)+b"); run(re, input); } // $ Alert
// BAD: (a+)+b
{ std::regex re("(a+)+b"); run(re, input); }
{ std::regex re("(a+)+b"); run(re, input); } // $ Alert
// BAD: (a*)+
{ std::regex re("^(a*)+$"); run(re, input); }
{ std::regex re("^(a*)+$"); run(re, input); } // $ Alert
// BAD: ((a+a?)*)+b+ (Ruby bad48)
{ std::regex re("(((a+a?)*)+b+)"); run(re, input); }
{ std::regex re("(((a+a?)*)+b+)"); run(re, input); } // $ Alert
// BAD: (a?a?)*b (Ruby bad71)
{ std::regex re("(a?a?)*b"); run(re, input); }
{ std::regex re("(a?a?)*b"); run(re, input); } // $ Alert
// BAD: (a?)+b via non-capturing alt (Ruby bad73)
{ std::regex re("(?:a|a?)+b"); run(re, input); }
{ std::regex re("(?:a|a?)+b"); run(re, input); } // $ Alert
// BAD: foo([\w-]*)+bar (Ruby bad69)
{ std::regex re("foo([\\w-]*)+bar"); run(re, input); }
{ std::regex re("foo([\\w-]*)+bar"); run(re, input); } // $ Alert
// BAD: ((ab)*)+c (Ruby bad70)
{ std::regex re("((ab)*)+c"); run(re, input); }
{ std::regex re("((ab)*)+c"); run(re, input); } // $ Alert
// -------------------------------------------------------------------------
// 2. Anchored nested-quantifier patterns (Ruby bad7..bad10, bad77, bad78).
// -------------------------------------------------------------------------
// BAD: ^([a-z]+)+$
{ std::regex re("^([a-z]+)+$"); run(re, input); }
{ std::regex re("^([a-z]+)+$"); run(re, input); } // $ Alert
// BAD: ^([a-z]*)*$
{ std::regex re("^([a-z]*)*$"); run(re, input); }
{ std::regex re("^([a-z]*)*$"); run(re, input); } // $ Alert
// BAD: e-mail-like regex
{ std::regex re("^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$");
{ std::regex re("^([a-zA-Z0-9])(([\\.-]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$"); // $ Alert
run(re, input); }
// BAD: ^(([a-z])+.)+[A-Z]([a-z])+$
{ std::regex re("^(([a-z])+.)+[A-Z]([a-z])+$"); run(re, input); }
{ std::regex re("^(([a-z])+.)+[A-Z]([a-z])+$"); run(re, input); } // $ Alert
// BAD: ^((a)+\w)+$
{ std::regex re("^((a)+\\w)+$"); run(re, input); }
{ std::regex re("^((a)+\\w)+$"); run(re, input); } // $ Alert
// BAD: ^(b+.)+$
{ std::regex re("^(b+.)+$"); run(re, input); }
{ std::regex re("^(b+.)+$"); run(re, input); } // $ Alert
// BAD: ^ab(c+)+$ (Ruby bad66)
{ std::regex re("^ab(c+)+$"); run(re, input); }
{ std::regex re("^ab(c+)+$"); run(re, input); } // $ Alert
// -------------------------------------------------------------------------
// 3. Alternations with overlapping / identical branches inside a repetition.
// -------------------------------------------------------------------------
// BAD: (a|a)*
{ std::regex re("^(a|a)*$"); run(re, input); }
{ std::regex re("^(a|a)*$"); run(re, input); } // $ Alert
// BAD: (a|aa?)*b (Ruby bad15)
{ std::regex re("(a|aa?)*b"); run(re, input); }
{ std::regex re("(a|aa?)*b"); run(re, input); } // $ Alert
// BAD: (b|a?b)*c (Ruby bad13)
{ std::regex re("(b|a?b)*c"); run(re, input); }
{ std::regex re("(b|a?b)*c"); run(re, input); } // $ Alert
// BAD: (a+|b+|c+)*c (Ruby bad47)
{ std::regex re("(a+|b+|c+)*c"); run(re, input); }
{ std::regex re("(a+|b+|c+)*c"); run(re, input); } // $ Alert
// -------------------------------------------------------------------------
// 4. Character-class / complement ambiguity (Ruby bad52, bad53, bad18,
@@ -130,19 +130,19 @@ int main(int argc, char** argv) {
// -------------------------------------------------------------------------
// BAD: ([^X]+)*$
{ std::regex re("([^X]+)*$"); run(re, input); }
{ std::regex re("([^X]+)*$"); run(re, input); } // $ Alert
// BAD: (([^X]b)+)*$
{ std::regex re("(([^X]b)+)*$"); run(re, input); }
{ std::regex re("(([^X]b)+)*$"); run(re, input); } // $ Alert
// BAD: (([\S\s]|[^a])*)"
{ std::regex re("(([\\S\\s]|[^a])*)\""); run(re, input); }
{ std::regex re("(([\\S\\s]|[^a])*)\""); run(re, input); } // $ Alert
// BAD: ((.|[^a])*)"
{ std::regex re("((.|[^a])*)\""); run(re, input); }
{ std::regex re("((.|[^a])*)\""); run(re, input); } // $ Alert
// BAD: ((b|[^a])*)"
{ std::regex re("((b|[^a])*)\""); run(re, input); }
{ std::regex re("((b|[^a])*)\""); run(re, input); } // $ Alert
// BAD: (([0-9]|[^a])*)"
{ std::regex re("(([0-9]|[^a])*)\""); run(re, input); }
{ std::regex re("(([0-9]|[^a])*)\""); run(re, input); } // $ Alert
// BAD: ^([^>a]+)*(>|$)
{ std::regex re("^([^>a]+)*(>|$)"); run(re, input); }
{ std::regex re("^([^>a]+)*(>|$)"); run(re, input); } // $ Alert
// -------------------------------------------------------------------------
// 5. Anchored-vs-unanchored GOOD/BAD pairs (Ruby good16/bad50,
@@ -154,17 +154,17 @@ int main(int argc, char** argv) {
// GOOD: (a+)+aaaaa*a+ -- Ruby good16 (no rejecting suffix)
{ std::regex re("(a+)+aaaaa*a+"); run(re, input); }
// BAD: (a+)+aaaaa$ -- Ruby bad50
{ std::regex re("(a+)+aaaaa$"); run(re, input); }
{ std::regex re("(a+)+aaaaa$"); run(re, input); } // $ Alert
// GOOD: (\n+)+\n\n -- Ruby good17
{ std::regex re("(\\n+)+\\n\\n"); run(re, input); }
// BAD: (\n+)+\n\n$ -- Ruby bad51
{ std::regex re("(\\n+)+\\n\\n$"); run(re, input); }
{ std::regex re("(\\n+)+\\n\\n$"); run(re, input); } // $ Alert
// GOOD: (([^X]b)+)*($|[^X]b) -- Ruby good18
{ std::regex re("(([^X]b)+)*($|[^X]b)"); run(re, input); }
// BAD: (([^X]b)+)*($|[^X]c) -- Ruby bad54
{ std::regex re("(([^X]b)+)*($|[^X]c)"); run(re, input); }
{ std::regex re("(([^X]b)+)*($|[^X]c)"); run(re, input); } // $ Alert
// GOOD: ((ab)+)*ababab -- Ruby good20
{ std::regex re("((ab)+)*ababab"); run(re, input); }
@@ -173,7 +173,7 @@ int main(int argc, char** argv) {
// GOOD: ((ab)+)* -- Ruby good22
{ std::regex re("((ab)+)*"); run(re, input); }
// BAD: ((ab)+)*$ -- Ruby bad55
{ std::regex re("((ab)+)*$"); run(re, input); }
{ std::regex re("((ab)+)*$"); run(re, input); } // $ Alert
// -------------------------------------------------------------------------
// 6. GOOD patterns that specifically guard against false positives.
@@ -226,7 +226,7 @@ int main(int argc, char** argv) {
// -------------------------------------------------------------------------
// BAD: exponential regex with icase - case-insensitivity does not suppress the alert.
{ std::regex re("^([a-z]+)+$", std::regex_constants::icase); run(re, input); }
{ std::regex re("^([a-z]+)+$", std::regex_constants::icase); run(re, input); } // $ Alert
// BAD: BRE grammar (`basic`) is modeled by the parser (`BreRegExp`,
// Phase D) and is backtracking-eligible, so the nested-quantifier is
// reported as exponential. Note that `(...)` in BRE is *literal* - the
@@ -238,7 +238,7 @@ int main(int argc, char** argv) {
// BAD: extended selects the ERE grammar (modeled by `EreRegExp` since
// Phase C) and is backtracking-eligible, so the shared engine reports
// the nested-quantifier as exponential - same as the ECMAScript case.
{ std::regex re("^(a+)+$", std::regex_constants::extended); run(re, input); }
{ std::regex re("^(a+)+$", std::regex_constants::extended); run(re, input); } // $ Alert
// GOOD: awk parses as ERE (same as `extended`) but is excluded from the
// ReDoS queries by `isBacktrackingEngine` (POSIX tool-style engines are
// treated as linear-time), *not* by any parsing/grammar difference.
@@ -262,9 +262,9 @@ int main(int argc, char** argv) {
// -------------------------------------------------------------------------
// BAD: nested-quantifier exponential regex using a POSIX character class.
{ std::regex re("^([[:alpha:]]+)+$"); run(re, input); }
{ std::regex re("^([[:alpha:]]+)+$"); run(re, input); } // $ Alert
// BAD: same pattern under icase - case-folding must not suppress the alert.
{ std::regex re("^([[:alpha:]]+)+$", std::regex_constants::icase); run(re, input); }
{ std::regex re("^([[:alpha:]]+)+$", std::regex_constants::icase); run(re, input); } // $ Alert
// Opaque POSIX character class: `[:punct:]` has no clean `\d`/`\s`/`\w`
// equivalent (see the discussion in RegexTreeView.qll::isEscapeClass).
// It is therefore left as an opaque atom in the shared engine's view;
@@ -328,7 +328,7 @@ int main(int argc, char** argv) {
// -------------------------------------------------------------------------
// BAD: ERE grammar via `extended` - parsed as ERE and backtracking-eligible.
{ std::regex re("^([a-z]+)+$", std::regex_constants::extended); run(re, input); }
{ std::regex re("^([a-z]+)+$", std::regex_constants::extended); run(re, input); } // $ Alert
// GOOD: same pattern under `egrep` - parses identically as ERE but
// excluded by `isBacktrackingEngine`.
{ std::regex re("^([a-z]+)+$", std::regex_constants::egrep); run(re, input); }
@@ -372,7 +372,7 @@ int main(int argc, char** argv) {
// exponentially many ways). BRE has no `+`, so this is the BRE
// analogue of the ECMAScript `^(a*)*$` (or the more familiar
// `^(a+)+$`) shape.
{ std::regex re("^\\(a*\\)*$", std::regex_constants::basic); run(re, input); }
{ std::regex re("^\\(a*\\)*$", std::regex_constants::basic); run(re, input); } // $ Alert
// GOOD: same pattern under `grep` - parses identically as BRE but
// excluded by `isBacktrackingEngine`.
{ std::regex re("^\\(a*\\)*$", std::regex_constants::grep); run(re, input); }

View File

@@ -1 +1,2 @@
query: Security/CWE/CWE-1333/PolynomialReDoS.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -104,7 +104,7 @@ struct Widget {
// -----------------------------------------------------------------------------
// argv is a LocalFlowSource (and hence a FlowSource).
int main(int argc, char** argv) {
int main(int argc, char** argv) { // $ Source
std::string input(argv[1]);
// -------------------------------------------------------------------------
@@ -114,7 +114,7 @@ int main(int argc, char** argv) {
// BAD: polynomial-backtracking pattern applied to user input.
{
std::regex re("^\\s+|\\s+$");
std::regex_replace(input, re, std::string(""));
std::regex_replace(input, re, std::string("")); // $ Alert
}
// Note: passing `argv[1]` (a raw `char*`) inline to `regex_search`
@@ -129,7 +129,7 @@ int main(int argc, char** argv) {
// BAD: quadratic \\d+E?\\d+ pattern applied to user input.
{
std::regex re("^0\\.\\d+E?\\d+$");
std::regex_search(input, re);
std::regex_search(input, re); // $ Alert
}
// GOOD: pattern is linear (a fixed prefix, no ambiguous repetition).
@@ -166,7 +166,7 @@ int main(int argc, char** argv) {
// BAD: \s+X? repeated - quadratic on strings of whitespace.
{
std::regex re("\\s+X?\\s+$");
std::regex_search(input, re);
std::regex_search(input, re); // $ Alert
}
// -------------------------------------------------------------------------
@@ -176,20 +176,20 @@ int main(int argc, char** argv) {
// BAD: std::regex_match with user input.
{
std::regex re("^\\s+|\\s+$");
std::regex_match(input, re);
std::regex_match(input, re); // $ Alert
}
// BAD: std::regex_search with user input.
{
std::regex re("^\\s+|\\s+$");
std::regex_search(input, re);
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(""));
std::regex_replace(input, re, std::string("")); // $ Alert
}
// BAD: std::regex_iterator constructed with a superlinear regex - the
@@ -276,7 +276,7 @@ int main(int argc, char** argv) {
int n = argc; // integer, small fixed size
(void)n;
std::regex re("^\\s+|\\s+$");
std::regex_search(input, re); // BAD (already reported above)
std::regex_search(input, re); // $ Alert // BAD (already reported above)
}
// -------------------------------------------------------------------------
@@ -305,7 +305,7 @@ int main(int argc, char** argv) {
// BAD: icase does not suppress the polynomial alert.
{
std::regex re("^\\s+|\\s+$", std::regex_constants::icase);
std::regex_replace(input, re, std::string(""));
std::regex_replace(input, re, std::string("")); // $ Alert
}
// GOOD: BRE grammar (`basic`) is now modeled by the parser
@@ -325,7 +325,7 @@ int main(int argc, char** argv) {
// ECMAScript case.
{
std::regex re("^\\s+|\\s+$", std::regex_constants::extended);
std::regex_replace(input, re, std::string(""));
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
@@ -357,14 +357,14 @@ int main(int argc, char** argv) {
// digit-heavy input (Java polynomial-ReDoS test `Test.java`).
{
std::regex re("(\\d+)*$");
std::regex_search(input, re);
std::regex_search(input, re); // $ Alert
}
// BAD: .*.*=.* - classic polynomial pattern, cf. JS
// `polynomial-redos/tst.js`.
{
std::regex re(".*.*=.*");
std::regex_search(input, re);
std::regex_search(input, re); // $ Alert
}
// GOOD (observed): `^(\w+\s?)*$` - a "trim-and-split" style pattern that
@@ -384,10 +384,10 @@ int main(int argc, char** argv) {
// BAD: `std::getenv` is modeled as a LocalFlowSource.
{
const char* env = std::getenv("USER_REGEX_INPUT");
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(""));
std::regex_replace(s, re, std::string("")); // $ Alert
}
// BAD: `std::fread` is modeled as a RemoteFlowSource (bytes read from a
@@ -395,10 +395,10 @@ int main(int argc, char** argv) {
// `FlowSource` union used by `PolynomialRedosConfig::isSource`.
{
char buf[256];
std::fread(buf, 1, sizeof(buf), (std::FILE*)0);
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(""));
std::regex_replace(s, re, std::string("")); // $ Alert
}
// -------------------------------------------------------------------------
@@ -436,7 +436,7 @@ int main(int argc, char** argv) {
// backtracking term on user input.
{
std::regex re("(a+)+b");
std::regex_match(input, re);
std::regex_match(input, re); // $ Alert
}
// -------------------------------------------------------------------------
@@ -499,7 +499,7 @@ int main(int argc, char** argv) {
// 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(""));
std::regex_replace(input, re, std::string("")); // $ Alert
}
// GOOD: same pattern/flow under `egrep` - parses identically as ERE
// but excluded by `isBacktrackingEngine`.
@@ -547,7 +547,7 @@ int main(int argc, char** argv) {
{
std::regex re("[[:space:]]\\{1,\\}.*[[:space:]]\\{1,\\}$",
std::regex_constants::basic);
std::regex_replace(input, re, std::string(""));
std::regex_replace(input, re, std::string("")); // $ Alert
}
// GOOD: same pattern/flow under `grep` - parses identically as BRE
// but excluded by `isBacktrackingEngine`.