diff --git a/python/ql/lib/change-notes/2023-09-22-regex-prefix.md b/python/ql/lib/change-notes/2023-09-22-regex-prefix.md new file mode 100644 index 00000000000..20affaaab4e --- /dev/null +++ b/python/ql/lib/change-notes/2023-09-22-regex-prefix.md @@ -0,0 +1,4 @@ +--- +category: fix +--- +* Subterms of regular expressions encoded as single-line string literals now have better source-location information. \ No newline at end of file diff --git a/python/ql/lib/semmle/python/regexp/RegexTreeView.qll b/python/ql/lib/semmle/python/regexp/RegexTreeView.qll index 49db4470343..86aad44fc93 100644 --- a/python/ql/lib/semmle/python/regexp/RegexTreeView.qll +++ b/python/ql/lib/semmle/python/regexp/RegexTreeView.qll @@ -227,10 +227,11 @@ module Impl implements RegexTreeViewSig { predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - exists(int re_start | + exists(int re_start, int prefix_len | prefix_len = re.getPrefix().length() | re.getLocation().hasLocationInfo(filepath, startline, re_start, endline, _) and - startcolumn = re_start + start + 4 and - endcolumn = re_start + end + 3 + startcolumn = re_start + start + prefix_len and + endcolumn = re_start + end + prefix_len - 1 + /* inclusive vs exclusive */ ) } diff --git a/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll b/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll index 81eff018aff..90a976824ac 100644 --- a/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll +++ b/python/ql/lib/semmle/python/regexp/internal/ParseRegExp.qll @@ -101,7 +101,7 @@ private module FindRegexMode { } /** - * DEPRECATED: Use `Regex` instead. + * DEPRECATED: Use `RegExp` instead. */ deprecated class Regex = RegExp; @@ -327,6 +327,17 @@ class RegExp extends Expr instanceof StrConst { /** Gets the text of this regex */ string getText() { result = super.getText() } + /** + * Gets the prefix of this regex + * + * Examples: + * + * - The prefix of `'x*y'` is `'`. + * - The prefix of `r''` is `r'`. + * - The prefix of `r"""x*y"""` is `r"""`. + */ + string getPrefix() { result = super.getPrefix() } + /** Gets the `i`th character of this regex */ string getChar(int i) { result = this.getText().charAt(i) } diff --git a/python/ql/test/library-tests/regexparser/Locations.expected b/python/ql/test/library-tests/regexparser/Locations.expected new file mode 100644 index 00000000000..3b07d5d758c --- /dev/null +++ b/python/ql/test/library-tests/regexparser/Locations.expected @@ -0,0 +1,20 @@ +| locations.py | 14 | 2 | +| locations.py | 19 | 3 | +| locations.py | 24 | 3 | +| locations.py | 29 | 4 | +| locations.py | 34 | 4 | +| locations.py | 39 | 5 | +| locations.py | 44 | 5 | +| locations.py | 49 | 6 | +| locations.py | 54 | 2 | +| locations.py | 54 | 23 | +| locations.py | 59 | 2 | +| locations.py | 59 | 23 | +| locations.py | 65 | 2 | +| locations.py | 65 | 23 | +| locations.py | 72 | 6 | +| locations.py | 72 | 27 | +| locations.py | 80 | 3 | +| locations.py | 85 | 5 | +| locations.py | 90 | 2 | +| locations.py | 90 | 23 | diff --git a/python/ql/test/library-tests/regexparser/Locations.ql b/python/ql/test/library-tests/regexparser/Locations.ql new file mode 100644 index 00000000000..79956e487e9 --- /dev/null +++ b/python/ql/test/library-tests/regexparser/Locations.ql @@ -0,0 +1,7 @@ +import semmle.python.regexp.RegexTreeView::RegexTreeView + +from RegExpTerm t, string file, int line, int column +where + t.toString() = "[this]" and + t.hasLocationInfo(file, line, column, _, _) +select file, line, column diff --git a/python/ql/test/library-tests/regexparser/locations.py b/python/ql/test/library-tests/regexparser/locations.py new file mode 100644 index 00000000000..bbfb6b2840f --- /dev/null +++ b/python/ql/test/library-tests/regexparser/locations.py @@ -0,0 +1,92 @@ +import re + +# This file contains tests for the regexp parser's location tracking. + +# To keep the expected results manageable, we only test the locations of the +# regexp term `[this]`, appearing in various kinds of regexps. +# +# To make the location information easier to understand, we generally put each +# regexp on its own line, even though this is not idiomatic Python. +# Comments indicate cases we currently do not handle correctly. + +# plain string +re.compile( +'[this] is a test' +) + +# raw string +re.compile( +r'[this] is a test' +) + +# byte string +re.compile( +b'[this] is a test' +) + +# byte raw string +re.compile( +br'[this] is a test' +) + +# multiline string +re.compile( +'''[this] is a test''' +) + +# multiline raw string +re.compile( +r'''[this] is a test''' +) + +# multiline byte string +re.compile( +b'''[this] is a test''' +) + +# multiline byte raw string +re.compile( +br'''[this] is a test''' +) + +# plain string with multiple parts (second [this] gets wrong column: 23 instead of 26) +re.compile( +'[this] is a test' ' and [this] is another test' +) + +# plain string with multiple parts across lines (second [this] gets wrong location: 59:23 instead of 60:7) +re.compile( +'[this] is a test' +' and [this] is another test' +) + +# plain string with multiple parts across lines and comments (second [this] gets wrong location: 65:23 instead of 67:7) +re.compile( +'[this] is a test' +# comment +' and [this] is another test' +) + +# actual multiline string (both [this]s get wrong location: 72:6 and 72:27 instead of 73:1 and 74:5) +re.compile( +r''' +[this] is a test +and [this] is another test +''' +) + +# plain string with escape sequences ([this] gets wrong location: 80:3 instead of 80:4) +re.compile( +'\t[this] is a test' +) + +# raw string with escape sequences +re.compile( +r'\A[this] is a test' +) + +# plain string with escaped newline (second [this] gets wrong location: 90:23 instead of 91:6) +re.compile( +'[this] is a test\ + and [this] is another test' +) \ No newline at end of file diff --git a/python/ql/test/query-tests/Security/CWE-020-IncompleteHostnameRegExp/IncompleteHostnameRegExp.expected b/python/ql/test/query-tests/Security/CWE-020-IncompleteHostnameRegExp/IncompleteHostnameRegExp.expected index 6a3c6769c35..62633237f46 100644 --- a/python/ql/test/query-tests/Security/CWE-020-IncompleteHostnameRegExp/IncompleteHostnameRegExp.expected +++ b/python/ql/test/query-tests/Security/CWE-020-IncompleteHostnameRegExp/IncompleteHostnameRegExp.expected @@ -1 +1 @@ -| hosttest.py:6:31:6:53 | (www\|beta).example.com/ | This regular expression has an unescaped '.' before 'example.com/', so it might match more hosts than expected. | hosttest.py:6:27:6:51 | ControlFlowNode for Str | here | +| hosttest.py:6:28:6:50 | (www\|beta).example.com/ | This regular expression has an unescaped '.' before 'example.com/', so it might match more hosts than expected. | hosttest.py:6:27:6:51 | ControlFlowNode for Str | here | diff --git a/python/ql/test/query-tests/Security/CWE-020-SuspiciousRegexpRange/OverlyLargeRangeQuery.expected b/python/ql/test/query-tests/Security/CWE-020-SuspiciousRegexpRange/OverlyLargeRangeQuery.expected index f614f9214fc..bc017c35e80 100644 --- a/python/ql/test/query-tests/Security/CWE-020-SuspiciousRegexpRange/OverlyLargeRangeQuery.expected +++ b/python/ql/test/query-tests/Security/CWE-020-SuspiciousRegexpRange/OverlyLargeRangeQuery.expected @@ -1,10 +1,10 @@ -| test.py:3:29:3:31 | 0-9 | Suspicious character range that overlaps with 3-5 in the same character class. | -| test.py:5:31:5:33 | A-z | Suspicious character range that overlaps with A-Z in the same character class, and is equivalent to [A-Z\\[\\\\\\]^_`a-z]. | -| test.py:7:28:7:30 | z-a | Suspicious character range that is empty. | -| test.py:17:38:17:40 | A-f | Suspicious character range that overlaps with a-f in the same character class, and is equivalent to [A-Z\\[\\\\\\]^_`a-f]. | -| test.py:19:30:19:32 | $-` | Suspicious character range that is equivalent to [$%&'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_`]. | -| test.py:21:43:21:45 | +-< | Suspicious character range that is equivalent to [+,\\-.\\/0-9:;<]. | -| test.py:23:47:23:49 | .-_ | Suspicious character range that overlaps with 1-9 in the same character class, and is equivalent to [.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_]. | -| test.py:25:34:25:36 | 7-F | Suspicious character range that is equivalent to [7-9:;<=>?@A-F]. | -| test.py:27:38:27:40 | 0-9 | Suspicious character range that overlaps with \\d in the same character class. | -| test.py:29:41:29:43 | .-? | Suspicious character range that overlaps with \\w in the same character class, and is equivalent to [.\\/0-9:;<=>?]. | +| test.py:3:27:3:29 | 0-9 | Suspicious character range that overlaps with 3-5 in the same character class. | +| test.py:5:29:5:31 | A-z | Suspicious character range that overlaps with A-Z in the same character class, and is equivalent to [A-Z\\[\\\\\\]^_`a-z]. | +| test.py:7:26:7:28 | z-a | Suspicious character range that is empty. | +| test.py:17:36:17:38 | A-f | Suspicious character range that overlaps with a-f in the same character class, and is equivalent to [A-Z\\[\\\\\\]^_`a-f]. | +| test.py:19:28:19:30 | $-` | Suspicious character range that is equivalent to [$%&'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_`]. | +| test.py:21:41:21:43 | +-< | Suspicious character range that is equivalent to [+,\\-.\\/0-9:;<]. | +| test.py:23:45:23:47 | .-_ | Suspicious character range that overlaps with 1-9 in the same character class, and is equivalent to [.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_]. | +| test.py:25:32:25:34 | 7-F | Suspicious character range that is equivalent to [7-9:;<=>?@A-F]. | +| test.py:27:36:27:38 | 0-9 | Suspicious character range that overlaps with \\d in the same character class. | +| test.py:29:39:29:41 | .-? | Suspicious character range that overlaps with \\w in the same character class, and is equivalent to [.\\/0-9:;<=>?]. | diff --git a/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialBackTracking.expected b/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialBackTracking.expected index 1aa6d91f343..5dc8412ba84 100644 --- a/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialBackTracking.expected +++ b/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialBackTracking.expected @@ -1,5 +1,5 @@ -| test.py:8:12:8:23 | Str | test.py:8:21:8:23 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ | -| test.py:9:14:9:29 | Str | test.py:9:27:9:29 | \\d+ | Strings starting with '0.9' and with many repetitions of '99' can start matching anywhere after the start of the preceeding \\d+ | -| test.py:11:22:11:33 | Str | test.py:11:31:11:33 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ | -| test.py:18:14:18:25 | Str | test.py:18:23:18:25 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ | -| test.py:20:23:20:274 | Str | test.py:20:273:20:274 | .* | Strings starting with 'AAAAAAAAAAAAAAAAAAAABBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' and with many repetitions of 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' can start matching anywhere after the start of the preceeding (AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)C.*Y | +| test.py:8:12:8:23 | Str | test.py:8:19:8:21 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ | +| test.py:9:14:9:29 | Str | test.py:9:25:9:27 | \\d+ | Strings starting with '0.9' and with many repetitions of '99' can start matching anywhere after the start of the preceeding \\d+ | +| test.py:11:22:11:33 | Str | test.py:11:29:11:31 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ | +| test.py:18:14:18:25 | Str | test.py:18:21:18:23 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ | +| test.py:20:23:20:274 | Str | test.py:20:271:20:272 | .* | Strings starting with 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' and with many repetitions of 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' can start matching anywhere after the start of the preceeding (AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)(AA\|BB)C.*Y | diff --git a/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialReDoS.expected b/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialReDoS.expected index c5840c78cb2..4fccccf4f00 100644 --- a/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialReDoS.expected +++ b/python/ql/test/query-tests/Security/CWE-730-PolynomialReDoS/PolynomialReDoS.expected @@ -27,8 +27,8 @@ nodes | test.py:21:18:21:21 | ControlFlowNode for text | semmle.label | ControlFlowNode for text | subpaths #select -| test.py:8:30:8:33 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:8:30:8:33 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.py:8:21:8:23 | \\s+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | -| test.py:9:32:9:35 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:9:32:9:35 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings starting with '0.9' and with many repetitions of '99'. | test.py:9:27:9:29 | \\d+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | -| test.py:12:17:12:20 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:12:17:12:20 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.py:11:31:11:33 | \\s+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | -| test.py:16:24:16:30 | ControlFlowNode for my_text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:16:24:16:30 | ControlFlowNode for my_text | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.py:18:23:18:25 | \\s+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | -| test.py:21:18:21:21 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:21:18:21:21 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings starting with 'AAAAAAAAAAAAAAAAAAAABBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' and with many repetitions of 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'. | test.py:20:273:20:274 | .* | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:8:30:8:33 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:8:30:8:33 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.py:8:19:8:21 | \\s+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:9:32:9:35 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:9:32:9:35 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings starting with '0.9' and with many repetitions of '99'. | test.py:9:25:9:27 | \\d+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:12:17:12:20 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:12:17:12:20 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.py:11:29:11:31 | \\s+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:16:24:16:30 | ControlFlowNode for my_text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:16:24:16:30 | ControlFlowNode for my_text | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.py:18:21:18:23 | \\s+ | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:21:18:21:21 | ControlFlowNode for text | test.py:2:26:2:32 | ControlFlowNode for ImportMember | test.py:21:18:21:21 | ControlFlowNode for text | This $@ that depends on a $@ may run slow on strings starting with 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' and with many repetitions of 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'. | test.py:20:271:20:272 | .* | regular expression | test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected b/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected index 24a2142ff4a..30d2a753f67 100644 --- a/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected +++ b/python/ql/test/query-tests/Security/CWE-730-ReDoS/ReDoS.expected @@ -1,5 +1,5 @@ -| KnownCVEs.py:15:22:15:24 | \\d+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | -| KnownCVEs.py:30:24:31:25 | .* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ','. | +| KnownCVEs.py:15:20:15:22 | \\d+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| KnownCVEs.py:30:21:31:22 | .* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ','. | | KnownCVEs.py:35:18:35:81 | ([-/:,#%.'"\\s!\\w]\|\\w-\\w\|'[\\s\\w]+'\\s*\|"[\\s\\w]+"\|\\([\\d,%\\.\\s]+\\))* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '"\\t"'. | | redos.py:6:28:6:42 | (?:__\|[\\s\\S])+? | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '__'. | | redos.py:6:52:6:68 | (?:\\*\\*\|[\\s\\S])+? | This part of the regular expression may cause exponential backtracking on strings starting with '*' and containing many repetitions of '**'. | @@ -67,8 +67,8 @@ | redos.py:259:24:259:126 | (.thisisagoddamnlongstringforstresstestingthequery\|\\sthisisagoddamnlongstringforstresstestingthequery)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\tthisisagoddamnlongstringforstresstestingthequery'. | | redos.py:262:24:262:87 | (thisisagoddamnlongstringforstresstestingthequery\|this\\w+query)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'thisisagoddamnlongstringforstresstestingthequery'. | | redos.py:262:78:262:80 | \\w+ | This part of the regular expression may cause exponential backtracking on strings starting with 'this' and containing many repetitions of '0querythis'. | -| redos.py:268:28:268:39 | ([\ufffd\ufffd]\|[\ufffd\ufffd])* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\\ufffd'. | -| redos.py:271:28:271:41 | ((\ufffd\|\ufffd)\|(\ufffd\|\ufffd))* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\\ufffd'. | +| redos.py:268:27:268:38 | ([\ufffd\ufffd]\|[\ufffd\ufffd])* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\\ufffd'. | +| redos.py:271:27:271:40 | ((\ufffd\|\ufffd)\|(\ufffd\|\ufffd))* | This part of the regular expression may cause exponential backtracking on strings starting with 'foo' and containing many repetitions of '\\ufffd'. | | redos.py:274:31:274:32 | b+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'b'. | | redos.py:277:48:277:50 | \\s* | This part of the regular expression may cause exponential backtracking on strings starting with '<0\\t0=' and containing many repetitions of '""\\t0='. | | redos.py:283:26:283:27 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. | @@ -96,15 +96,15 @@ | redos.py:363:25:363:43 | ((?:a{0\|-)\|\\w\\{\\d)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a{0'. | | redos.py:364:25:364:45 | ((?:a{0,\|-)\|\\w\\{\\d,)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a{0,'. | | redos.py:365:25:365:48 | ((?:a{0,2\|-)\|\\w\\{\\d,\\d)+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a{0,2'. | -| redos.py:371:25:371:35 | (\\u0061\|a)* | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of 'a'. | -| redos.py:380:35:380:41 | [^"\\s]+ | This part of the regular expression may cause exponential backtracking on strings starting with '/' and containing many repetitions of '!'. | -| redos.py:381:35:381:41 | [^"\\s]+ | This part of the regular expression may cause exponential backtracking on strings starting with '/' and containing many repetitions of '!'. | -| redos.py:384:26:384:32 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | -| redos.py:385:24:385:30 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | -| redos.py:386:26:386:32 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | -| redos.py:391:15:391:25 | (\\u0061\|a)* | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of 'a'. | -| unittests.py:5:17:5:23 | (\u00c6\|\\\u00c6)+ | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of '\\u00c6'. | -| unittests.py:9:16:9:24 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | -| unittests.py:11:20:11:28 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | -| unittests.py:12:21:12:29 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | -| unittests.py:13:22:13:30 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings starting with 'x' and containing many repetitions of '\\n'. | +| redos.py:371:23:371:33 | (\\u0061\|a)* | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of 'a'. | +| redos.py:380:33:380:39 | [^"\\s]+ | This part of the regular expression may cause exponential backtracking on strings starting with '/' and containing many repetitions of '!'. | +| redos.py:381:33:381:39 | [^"\\s]+ | This part of the regular expression may cause exponential backtracking on strings starting with '/' and containing many repetitions of '!'. | +| redos.py:384:24:384:30 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| redos.py:385:22:385:28 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| redos.py:386:24:386:30 | (\\d\|0)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. | +| redos.py:391:13:391:23 | (\\u0061\|a)* | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of 'a'. | +| unittests.py:5:15:5:21 | (\u00c6\|\\\u00c6)+ | This part of the regular expression may cause exponential backtracking on strings starting with 'X' and containing many repetitions of '\\u00c6'. | +| unittests.py:9:14:9:22 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | +| unittests.py:11:18:11:26 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | +| unittests.py:12:19:12:27 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '\\n'. | +| unittests.py:13:20:13:28 | (?:.\|\\n)* | This part of the regular expression may cause exponential backtracking on strings starting with 'x' and containing many repetitions of '\\n'. |