Commit 9: Fix hasLocationInfo for all string literal forms; fix unicode escape

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 ✓
This commit is contained in:
copilot-swe-agent[bot]
2026-07-21 10:36:49 +00:00
committed by GitHub
parent e7ccb1f2ce
commit bfa65301f1
5 changed files with 127 additions and 85 deletions

View File

@@ -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
)
}

View File

@@ -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 |

View File

@@ -521,7 +521,7 @@ test.cpp:
# 131| [RegExpNamedCharacterProperty] [:digit:]
# 136| [RegExpConstant, RegExpEscape] \u{987
# 136| [RegExpConstant, RegExpEscape] \u9879
# 144| [RegExpConstant, RegExpNormalChar] a

View File

@@ -8,7 +8,7 @@ groupNumber
| test.cpp:108:21:108:30 | (?<id>\\w+) | 1 |
| test.cpp:112:23:112:26 | (a+) | 1 |
| test.cpp:113:23:113:32 | (?<qux>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 | \\. | . |

View File

@@ -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).