From bfa65301f1239d31249132fd8821bb1fe7124958 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:36:49 +0000 Subject: [PATCH] Commit 9: Fix hasLocationInfo for all string literal forms; fix unicode escape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RegexTreeView.qll - hasLocationInfo fix (RULE 4): - Add regexpContentOffset(RegExp re) private helper that computes the correct number of source chars before the first content char, from getValueText(): - Plain "...": offset 1 - L"...": offset 2 (L" prefix) - u8"...": offset 3 (u8" prefix) - R"(...)": offset 3 (R"( opener) - R"x(...)x": offset 4 (R"x( with custom delim) - LR"(...)": offset 4 (LR"() - Uses vt.matches("%R\"%(%") to detect raw strings; finds '(' position - For non-raw, finds '"' position - Replace hardcoded `+ 1` with `+ regexpContentOffset(re)` in hasLocationInfo - Update comment: documents plain/encoding/raw/combined forms, notes that escaped non-raw strings are approximate (mirroring Java/Python) test.cpp: - Fix r_uni: change "\\u{9879}" to "\\u9879" — braced \u{...} is not standard C++ regex syntax; the 4-digit \uHHHH form is valid ECMAScript \u escape Regenerated all 3 .expected files via codeql test run --learn (CodeQL 2.26.1). All 3 tests pass. Location test confirms: - Plain "a+b" (line 144): litStartCol 22 → termStartCol 23 (22+1) UNCHANGED ✓ - Raw R"(a+b)" (line 147): litStartCol 20 → termStartCol 23 (20+3) FIXED ✓ - Raw R"x(a+b)x" (line 156): litStartCol 21 → termStartCol 25 (21+4) FIXED ✓ - L"a+b" (line 159): litStartCol 22 → termStartCol 24 (22+2) FIXED ✓ - LR"(a+b)" (line 162): litStartCol 26 → termStartCol 30 (26+4) FIXED ✓ --- .../semmle/code/cpp/regex/RegexTreeView.qll | 54 +++++++++-- .../library-tests/regex/locations.expected | 60 ++++++------ .../test/library-tests/regex/parse.expected | 2 +- .../test/library-tests/regex/regexp.expected | 92 +++++++++---------- cpp/ql/test/library-tests/regex/test.cpp | 4 +- 5 files changed, 127 insertions(+), 85 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll b/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll index 23a585a162b..1cd5896a16c 100644 --- a/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll +++ b/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll @@ -114,6 +114,36 @@ private module Impl implements RegexTreeViewSig { override string getAPrimaryQlClass() { result = "RegExpLiteral" } } + /** + * Gets the number of source characters from the start of the string literal `re` + * to the first content character (i.e., past the opening delimiter). + * + * This is derived from the raw source spelling via `StringLiteral.getValueText()`: + * - Plain `"..."`: offset 1 (just the `"`) + * - Encoding prefix `L"..."`: offset 2 (`L"`) + * - Encoding prefix `u8"..."`: offset 3 (`u8"`) + * - Raw `R"(...)"` offset 3 (`R"(`) + * - Custom-delim raw `R"x(...)x"` offset 4 (`R"x(` — delim length 1) + * - Combined `LR"(...)"` offset 4 (`LR"(`) + * + * For non-raw strings, the value-offset maps 1:1 to source columns only when + * there are no escape sequences; escaped non-raw strings are approximate + * (mirroring Java/Python regex libraries). + */ + private int regexpContentOffset(RegExp re) { + exists(string vt | vt = re.getValueText() | + // Raw string: find the '(' that opens the raw content. + // getValueText() for raw strings looks like: [prefix]R"[delim](...)[delim]" + // The opening '(' is after the optional prefix, 'R', '"', and custom delimiter. + vt.matches("%R\"%(%") and + result = 1 + min(int i | vt.charAt(i) = "(" and i >= 1) + or + // Non-raw string: find the opening '"'. + not vt.matches("%R\"%(%") and + result = 1 + min(int i | vt.charAt(i) = "\"") + ) + } + /** * A regular expression term, that is, a syntactic part of a regular expression. */ @@ -211,18 +241,30 @@ private module Impl implements RegexTreeViewSig { */ Location getLocation() { result = re.getLocation() } - /** Holds if this term is found at the specified location offsets. + /** + * Holds if this term is found at the specified location offsets. * - * Note: for commit 2, this uses an approximate offset of 1 for the - * opening delimiter. Commits 8-9 fix this for all string literal forms. + * The content offset is computed from `StringLiteral.getValueText()` (the raw source + * spelling including delimiters), so it is correct for: + * - Plain `"..."`: offset 1 (`"`) + * - Encoding prefixes `L"..."`: offset 2 (`L"`) + * - Encoding prefix `u8"..."`: offset 3 (`u8"`) + * - Raw `R"(...)"` offset 3 (`R"(`) + * - Custom-delim raw `R"x(...)x"`: offset 4+len(delim) (`R"` + delim + `(`) + * - Combined, e.g. `LR"(...)"` offset 4 (`LR"(`) + * + * Note: for non-raw strings, the value-string offset equals the source-column offset + * only when there are no escape sequences. Escaped characters are an approximation, + * mirroring the Java/Python regex libraries. */ predicate hasLocationInfo( string filepath, int startline, int startcolumn, int endline, int endcolumn ) { - exists(int re_start | + exists(int re_start, int offset | re.getLocation().hasLocationInfo(filepath, startline, re_start, endline, _) and - startcolumn = re_start + 1 + start and - endcolumn = re_start + 1 + end - 1 + offset = regexpContentOffset(re) and + startcolumn = re_start + offset + start and + endcolumn = re_start + offset + end - 1 ) } diff --git a/cpp/ql/test/library-tests/regex/locations.expected b/cpp/ql/test/library-tests/regex/locations.expected index 52d1b7103d9..d465969f899 100644 --- a/cpp/ql/test/library-tests/regex/locations.expected +++ b/cpp/ql/test/library-tests/regex/locations.expected @@ -2,36 +2,36 @@ | test.cpp:144:23:144:24 | a+ | 22 | 0 | 2 | 23 | 24 | | test.cpp:144:23:144:25 | a+b | 22 | 0 | 3 | 23 | 25 | | test.cpp:144:25:144:25 | b | 22 | 2 | 3 | 25 | 25 | -| test.cpp:147:21:147:21 | a | 20 | 0 | 1 | 21 | 21 | -| test.cpp:147:21:147:22 | a+ | 20 | 0 | 2 | 21 | 22 | -| test.cpp:147:21:147:23 | a+b | 20 | 0 | 3 | 21 | 23 | -| test.cpp:147:23:147:23 | b | 20 | 2 | 3 | 23 | 23 | -| test.cpp:150:22:150:23 | \\s | 21 | 0 | 2 | 22 | 23 | -| test.cpp:150:22:150:24 | \\s+ | 21 | 0 | 3 | 22 | 24 | -| test.cpp:150:22:150:25 | \\s+$ | 21 | 0 | 4 | 22 | 25 | -| test.cpp:150:25:150:25 | $ | 21 | 3 | 4 | 25 | 25 | -| test.cpp:153:22:153:23 | \\( | 21 | 0 | 2 | 22 | 23 | -| test.cpp:153:22:153:35 | \\(([,\\w]+)+\\)$ | 21 | 0 | 14 | 22 | 35 | -| test.cpp:153:24:153:31 | ([,\\w]+) | 21 | 2 | 10 | 24 | 31 | -| test.cpp:153:24:153:32 | ([,\\w]+)+ | 21 | 2 | 11 | 24 | 32 | -| test.cpp:153:25:153:29 | [,\\w] | 21 | 3 | 8 | 25 | 29 | -| test.cpp:153:25:153:30 | [,\\w]+ | 21 | 3 | 9 | 25 | 30 | -| test.cpp:153:26:153:26 | , | 21 | 4 | 5 | 26 | 26 | -| test.cpp:153:27:153:28 | \\w | 21 | 5 | 7 | 27 | 28 | -| test.cpp:153:33:153:34 | \\) | 21 | 11 | 13 | 33 | 34 | -| test.cpp:153:35:153:35 | $ | 21 | 13 | 14 | 35 | 35 | -| test.cpp:156:22:156:22 | a | 21 | 0 | 1 | 22 | 22 | -| test.cpp:156:22:156:23 | a+ | 21 | 0 | 2 | 22 | 23 | -| test.cpp:156:22:156:24 | a+b | 21 | 0 | 3 | 22 | 24 | -| test.cpp:156:24:156:24 | b | 21 | 2 | 3 | 24 | 24 | -| test.cpp:159:23:159:23 | a | 22 | 0 | 1 | 23 | 23 | -| test.cpp:159:23:159:24 | a+ | 22 | 0 | 2 | 23 | 24 | -| test.cpp:159:23:159:25 | a+b | 22 | 0 | 3 | 23 | 25 | -| test.cpp:159:25:159:25 | b | 22 | 2 | 3 | 25 | 25 | -| test.cpp:162:27:162:27 | a | 26 | 0 | 1 | 27 | 27 | -| test.cpp:162:27:162:28 | a+ | 26 | 0 | 2 | 27 | 28 | -| test.cpp:162:27:162:29 | a+b | 26 | 0 | 3 | 27 | 29 | -| test.cpp:162:29:162:29 | b | 26 | 2 | 3 | 29 | 29 | +| test.cpp:147:23:147:23 | a | 20 | 0 | 1 | 23 | 23 | +| test.cpp:147:23:147:24 | a+ | 20 | 0 | 2 | 23 | 24 | +| test.cpp:147:23:147:25 | a+b | 20 | 0 | 3 | 23 | 25 | +| test.cpp:147:25:147:25 | b | 20 | 2 | 3 | 25 | 25 | +| test.cpp:150:24:150:25 | \\s | 21 | 0 | 2 | 24 | 25 | +| test.cpp:150:24:150:26 | \\s+ | 21 | 0 | 3 | 24 | 26 | +| test.cpp:150:24:150:27 | \\s+$ | 21 | 0 | 4 | 24 | 27 | +| test.cpp:150:27:150:27 | $ | 21 | 3 | 4 | 27 | 27 | +| test.cpp:153:24:153:25 | \\( | 21 | 0 | 2 | 24 | 25 | +| test.cpp:153:24:153:37 | \\(([,\\w]+)+\\)$ | 21 | 0 | 14 | 24 | 37 | +| test.cpp:153:26:153:33 | ([,\\w]+) | 21 | 2 | 10 | 26 | 33 | +| test.cpp:153:26:153:34 | ([,\\w]+)+ | 21 | 2 | 11 | 26 | 34 | +| test.cpp:153:27:153:31 | [,\\w] | 21 | 3 | 8 | 27 | 31 | +| test.cpp:153:27:153:32 | [,\\w]+ | 21 | 3 | 9 | 27 | 32 | +| test.cpp:153:28:153:28 | , | 21 | 4 | 5 | 28 | 28 | +| test.cpp:153:29:153:30 | \\w | 21 | 5 | 7 | 29 | 30 | +| test.cpp:153:35:153:36 | \\) | 21 | 11 | 13 | 35 | 36 | +| test.cpp:153:37:153:37 | $ | 21 | 13 | 14 | 37 | 37 | +| test.cpp:156:25:156:25 | a | 21 | 0 | 1 | 25 | 25 | +| test.cpp:156:25:156:26 | a+ | 21 | 0 | 2 | 25 | 26 | +| test.cpp:156:25:156:27 | a+b | 21 | 0 | 3 | 25 | 27 | +| test.cpp:156:27:156:27 | b | 21 | 2 | 3 | 27 | 27 | +| test.cpp:159:24:159:24 | a | 22 | 0 | 1 | 24 | 24 | +| test.cpp:159:24:159:25 | a+ | 22 | 0 | 2 | 24 | 25 | +| test.cpp:159:24:159:26 | a+b | 22 | 0 | 3 | 24 | 26 | +| test.cpp:159:26:159:26 | b | 22 | 2 | 3 | 26 | 26 | +| test.cpp:162:30:162:30 | a | 26 | 0 | 1 | 30 | 30 | +| test.cpp:162:30:162:31 | a+ | 26 | 0 | 2 | 30 | 31 | +| test.cpp:162:30:162:32 | a+b | 26 | 0 | 3 | 30 | 32 | +| test.cpp:162:32:162:32 | b | 26 | 2 | 3 | 32 | 32 | | test.cpp:166:22:166:23 | \\s | 21 | 0 | 2 | 22 | 23 | | test.cpp:166:22:166:24 | \\s+ | 21 | 0 | 3 | 22 | 24 | | test.cpp:169:22:169:22 | a | 21 | 0 | 1 | 22 | 22 | diff --git a/cpp/ql/test/library-tests/regex/parse.expected b/cpp/ql/test/library-tests/regex/parse.expected index 7298205ebd6..cb4bbfdb3cf 100644 --- a/cpp/ql/test/library-tests/regex/parse.expected +++ b/cpp/ql/test/library-tests/regex/parse.expected @@ -521,7 +521,7 @@ test.cpp: # 131| [RegExpNamedCharacterProperty] [:digit:] -# 136| [RegExpConstant, RegExpEscape] \u{987 +# 136| [RegExpConstant, RegExpEscape] \u9879 # 144| [RegExpConstant, RegExpNormalChar] a diff --git a/cpp/ql/test/library-tests/regex/regexp.expected b/cpp/ql/test/library-tests/regex/regexp.expected index d5e9979aed4..d934025e3a6 100644 --- a/cpp/ql/test/library-tests/regex/regexp.expected +++ b/cpp/ql/test/library-tests/regex/regexp.expected @@ -8,7 +8,7 @@ groupNumber | test.cpp:108:21:108:30 | (?\\w+) | 1 | | test.cpp:112:23:112:26 | (a+) | 1 | | test.cpp:113:23:113:32 | (?q+) | 1 | -| test.cpp:153:24:153:31 | ([,\\w]+) | 1 | +| test.cpp:153:26:153:33 | ([,\\w]+) | 1 | term | test.cpp:33:21:33:23 | abc | RegExpConstant,RegExpNormalChar | | test.cpp:36:22:36:22 | a | RegExpConstant,RegExpNormalChar | @@ -201,41 +201,41 @@ term | test.cpp:128:37:128:39 | a-f | RegExpCharacterRange | | test.cpp:128:39:128:39 | f | RegExpConstant,RegExpNormalChar | | test.cpp:131:24:131:32 | [:digit:] | RegExpNamedCharacterProperty | -| test.cpp:136:21:136:26 | \\u{987 | RegExpConstant,RegExpEscape | +| test.cpp:136:21:136:26 | \\u9879 | RegExpConstant,RegExpEscape | | test.cpp:144:23:144:23 | a | RegExpConstant,RegExpNormalChar | | test.cpp:144:23:144:24 | a+ | RegExpPlus | | test.cpp:144:23:144:25 | a+b | RegExpSequence | | test.cpp:144:25:144:25 | b | RegExpConstant,RegExpNormalChar | -| test.cpp:147:21:147:21 | a | RegExpConstant,RegExpNormalChar | -| test.cpp:147:21:147:22 | a+ | RegExpPlus | -| test.cpp:147:21:147:23 | a+b | RegExpSequence | -| test.cpp:147:23:147:23 | b | RegExpConstant,RegExpNormalChar | -| test.cpp:150:22:150:23 | \\s | RegExpCharacterClassEscape | -| test.cpp:150:22:150:24 | \\s+ | RegExpPlus | -| test.cpp:150:22:150:25 | \\s+$ | RegExpSequence | -| test.cpp:150:25:150:25 | $ | RegExpDollar | -| test.cpp:153:22:153:23 | \\( | RegExpConstant,RegExpEscape | -| test.cpp:153:22:153:35 | \\(([,\\w]+)+\\)$ | RegExpSequence | -| test.cpp:153:24:153:31 | ([,\\w]+) | RegExpGroup | -| test.cpp:153:24:153:32 | ([,\\w]+)+ | RegExpPlus | -| test.cpp:153:25:153:29 | [,\\w] | RegExpCharacterClass | -| test.cpp:153:25:153:30 | [,\\w]+ | RegExpPlus | -| test.cpp:153:26:153:26 | , | RegExpConstant,RegExpNormalChar | -| test.cpp:153:27:153:28 | \\w | RegExpCharacterClassEscape | -| test.cpp:153:33:153:34 | \\) | RegExpConstant,RegExpEscape | -| test.cpp:153:35:153:35 | $ | RegExpDollar | -| test.cpp:156:22:156:22 | a | RegExpConstant,RegExpNormalChar | -| test.cpp:156:22:156:23 | a+ | RegExpPlus | -| test.cpp:156:22:156:24 | a+b | RegExpSequence | -| test.cpp:156:24:156:24 | b | RegExpConstant,RegExpNormalChar | -| test.cpp:159:23:159:23 | a | RegExpConstant,RegExpNormalChar | -| test.cpp:159:23:159:24 | a+ | RegExpPlus | -| test.cpp:159:23:159:25 | a+b | RegExpSequence | -| test.cpp:159:25:159:25 | b | RegExpConstant,RegExpNormalChar | -| test.cpp:162:27:162:27 | a | RegExpConstant,RegExpNormalChar | -| test.cpp:162:27:162:28 | a+ | RegExpPlus | -| test.cpp:162:27:162:29 | a+b | RegExpSequence | -| test.cpp:162:29:162:29 | b | RegExpConstant,RegExpNormalChar | +| test.cpp:147:23:147:23 | a | RegExpConstant,RegExpNormalChar | +| test.cpp:147:23:147:24 | a+ | RegExpPlus | +| test.cpp:147:23:147:25 | a+b | RegExpSequence | +| test.cpp:147:25:147:25 | b | RegExpConstant,RegExpNormalChar | +| test.cpp:150:24:150:25 | \\s | RegExpCharacterClassEscape | +| test.cpp:150:24:150:26 | \\s+ | RegExpPlus | +| test.cpp:150:24:150:27 | \\s+$ | RegExpSequence | +| test.cpp:150:27:150:27 | $ | RegExpDollar | +| test.cpp:153:24:153:25 | \\( | RegExpConstant,RegExpEscape | +| test.cpp:153:24:153:37 | \\(([,\\w]+)+\\)$ | RegExpSequence | +| test.cpp:153:26:153:33 | ([,\\w]+) | RegExpGroup | +| test.cpp:153:26:153:34 | ([,\\w]+)+ | RegExpPlus | +| test.cpp:153:27:153:31 | [,\\w] | RegExpCharacterClass | +| test.cpp:153:27:153:32 | [,\\w]+ | RegExpPlus | +| test.cpp:153:28:153:28 | , | RegExpConstant,RegExpNormalChar | +| test.cpp:153:29:153:30 | \\w | RegExpCharacterClassEscape | +| test.cpp:153:35:153:36 | \\) | RegExpConstant,RegExpEscape | +| test.cpp:153:37:153:37 | $ | RegExpDollar | +| test.cpp:156:25:156:25 | a | RegExpConstant,RegExpNormalChar | +| test.cpp:156:25:156:26 | a+ | RegExpPlus | +| test.cpp:156:25:156:27 | a+b | RegExpSequence | +| test.cpp:156:27:156:27 | b | RegExpConstant,RegExpNormalChar | +| test.cpp:159:24:159:24 | a | RegExpConstant,RegExpNormalChar | +| test.cpp:159:24:159:25 | a+ | RegExpPlus | +| test.cpp:159:24:159:26 | a+b | RegExpSequence | +| test.cpp:159:26:159:26 | b | RegExpConstant,RegExpNormalChar | +| test.cpp:162:30:162:30 | a | RegExpConstant,RegExpNormalChar | +| test.cpp:162:30:162:31 | a+ | RegExpPlus | +| test.cpp:162:30:162:32 | a+b | RegExpSequence | +| test.cpp:162:32:162:32 | b | RegExpConstant,RegExpNormalChar | | test.cpp:166:22:166:23 | \\s | RegExpCharacterClassEscape | | test.cpp:166:22:166:24 | \\s+ | RegExpPlus | | test.cpp:169:22:169:22 | a | RegExpConstant,RegExpNormalChar | @@ -318,22 +318,22 @@ regExpNormalCharValue | test.cpp:128:27:128:27 | F | F | | test.cpp:128:37:128:37 | a | a | | test.cpp:128:39:128:39 | f | f | -| test.cpp:136:21:136:26 | \\u{987 | \u0987 | +| test.cpp:136:21:136:26 | \\u9879 | \u9879 | | test.cpp:144:23:144:23 | a | a | | test.cpp:144:25:144:25 | b | b | -| test.cpp:147:21:147:21 | a | a | -| test.cpp:147:23:147:23 | b | b | -| test.cpp:150:22:150:23 | \\s | s | -| test.cpp:153:22:153:23 | \\( | ( | -| test.cpp:153:26:153:26 | , | , | -| test.cpp:153:27:153:28 | \\w | w | -| test.cpp:153:33:153:34 | \\) | ) | -| test.cpp:156:22:156:22 | a | a | -| test.cpp:156:24:156:24 | b | b | -| test.cpp:159:23:159:23 | a | a | -| test.cpp:159:25:159:25 | b | b | -| test.cpp:162:27:162:27 | a | a | -| test.cpp:162:29:162:29 | b | b | +| test.cpp:147:23:147:23 | a | a | +| test.cpp:147:25:147:25 | b | b | +| test.cpp:150:24:150:25 | \\s | s | +| test.cpp:153:24:153:25 | \\( | ( | +| test.cpp:153:28:153:28 | , | , | +| test.cpp:153:29:153:30 | \\w | w | +| test.cpp:153:35:153:36 | \\) | ) | +| test.cpp:156:25:156:25 | a | a | +| test.cpp:156:27:156:27 | b | b | +| test.cpp:159:24:159:24 | a | a | +| test.cpp:159:26:159:26 | b | b | +| test.cpp:162:30:162:30 | a | a | +| test.cpp:162:32:162:32 | b | b | | test.cpp:166:22:166:23 | \\s | s | | test.cpp:169:22:169:22 | a | a | | test.cpp:169:23:169:24 | \\. | . | diff --git a/cpp/ql/test/library-tests/regex/test.cpp b/cpp/ql/test/library-tests/regex/test.cpp index 67cb3a050c6..e363570807a 100644 --- a/cpp/ql/test/library-tests/regex/test.cpp +++ b/cpp/ql/test/library-tests/regex/test.cpp @@ -132,8 +132,8 @@ void test() { // Dropped: /#{A}bc/ — Ruby string interpolation, no C++ string-literal form. - // unicode: \u{9879} in C++ (Ruby unicode escape syntax) - std::regex r_uni("\\u{9879}"); + // unicode: \uHHHH 4-digit hex form (valid ECMAScript \u escape in std::regex) + std::regex r_uni("\\u9879"); } // Location tests (commit 8: pre-fix columns; commit 9: fixes raw/prefixed offsets).