Files
codeql/cpp/ql/test/library-tests/regex/test.cpp
copilot-swe-agent[bot] e7ccb1f2ce Commit 8: Add location tests exposing term-location off-by-one (pre-fix)
Add location test cases to test.cpp covering various C++ string literal forms:
- r_plain:     "a+b"          — plain, offset 1 (already correct)
- r_raw:       R"(a+b)"       — raw, offset 3 (R"( = 3); currently uses 1 (WRONG)
- r_raw2:      R"(\s+$)"      — raw with metacharacters; currently wrong
- r_raw3:      R"(\(([,\w]+)+\)$)" — complex raw; currently wrong
- r_raw4:      R"x(a+b)x"    — custom-delimiter raw, offset 4; currently uses 1 (WRONG)
- r_wide:      L"a+b"         — L" prefix, offset 2; currently uses 1 (WRONG)
- r_wide_raw:  LR"(a+b)"      — combined LR"( prefix, offset 4; currently uses 1 (WRONG)
- r_esc1:      "\\s+"         — escape-containing plain; offset 1 (correct)
- r_esc2:      "a\\.b"        — escaped dot; offset 1 (correct)

Added std::wregex typedef to stubs (for L"..." and LR"(...)" wide-char literals).

New locations.ql query reports per-term: litStartCol, valueStart, valueEnd,
termStartCol, termEndCol — restricted to test_locations() function.

Generated locations.expected via codeql test run --learn (CodeQL 2.26.1).
All 3 tests pass. The expected output captures the CURRENT (pre-fix) columns:
raw/prefixed rows show termStartCol = litStartCol + 1 (wrong);
plain rows show litStartCol + 1 (correct).
Commit 9 changes the raw/prefixed rows to their correct values.
2026-07-21 10:27:55 +00:00

171 lines
6.1 KiB
C++

// Minimal stubs for std::regex surface — no #include of any external header.
// Provides just enough of the std:: regex API that the C++ extractor
// creates StringLiteral nodes for each regex pattern, using real qualified
// names so a future flow-config PR can identify them unchanged.
namespace std {
template <class CharT>
class basic_regex {
public:
basic_regex(const char *s) {}
basic_regex(const char *s, int flags) {}
basic_regex(const wchar_t *s) {}
basic_regex &assign(const char *s) { return *this; }
};
typedef basic_regex<char> regex;
typedef basic_regex<wchar_t> wregex;
template <class CharT>
bool regex_match(const char *s, const basic_regex<CharT> &re) { return false; }
template <class CharT>
bool regex_search(const char *s, const basic_regex<CharT> &re) { return false; }
template <class CharT>
const char *regex_replace(const char *s, const basic_regex<CharT> &re,
const char *fmt) { return s; }
} // namespace std
void test() {
// Empty
std::regex r_empty("");
// Basic sequence
std::regex r_abc("abc");
// Repetition
std::regex r_rep1("a*b+c?d");
std::regex r_rep2("a{4,8}");
// Removed: a{,8} — Ruby-only "{,n}" no-lower-bound quantifier (not in ECMAScript)
std::regex r_rep4("a{3,}");
std::regex r_rep5("a{7}");
// Alternation
std::regex r_alt("foo|bar");
// Character classes
std::regex r_cc1("[abc]");
std::regex r_cc2("[a-fA-F0-9_]");
// Removed: \\A[+-]?\\d+ — \\A is a Ruby-only anchor; replaced with ^ for ECMAScript
std::regex r_cc3("^[+-]?\\d+");
std::regex r_cc4("[\\w]+");
std::regex r_cc5("\\[\\][123]");
std::regex r_cc6("[^A-Z]");
std::regex r_cc7("[]]"); // MRI gives a warning, but accepts this as matching ']'
std::regex r_cc8("[^]]"); // MRI gives a warning, but accepts this as matching anything except ']'
std::regex r_cc9("[^-]");
std::regex r_cc10("[|]");
// Nested character classes (BAD - not parsed correctly)
std::regex r_nested("[[a-f]A-F]");
// POSIX bracket extensions (std::regex ECMAScript mode supports these inside [...])
// Note: commit 7 fixes tokenization for these cases; commit 6 captures pre-fix behavior.
// Single POSIX classes (single outer bracket around a single POSIX expression)
std::regex r_posix_alpha("[[:alpha:]]"); // naïve parser closes at inner ] of [:alpha:]
std::regex r_posix_digit("[[:digit:]]");
std::regex r_posix_space("[[:space:]]");
std::regex r_posix_upper("[[:upper:]]");
std::regex r_posix_lower("[[:lower:]]");
std::regex r_posix_alnum("[[:alnum:]]");
std::regex r_posix_print("[[:print:]]");
std::regex r_posix_punct("[[:punct:]]");
// POSIX class negated (outer bracket negated)
std::regex r_posix_neg("[^[:space:]]");
// Mixed: regular char + POSIX class inside outer bracket
std::regex r_posix_mixed("[a[:space:]]");
// POSIX collating symbol
std::regex r_posix_coll("[[.a.]]");
// POSIX equivalence class
std::regex r_posix_equiv("[[=a=]]");
// Mixed: POSIX class + range inside outer bracket (mis-tokenization-prone)
std::regex r_posix_range("[[:alpha:]0-9]");
// Meta-character classes
std::regex r_meta1(".*");
std::regex r_meta2("\\w+\\W");
std::regex r_meta3("\\s\\S");
std::regex r_meta4("\\d\\D");
// Removed: \\h\\H — Ruby-only hex-digit escape classes (not in ECMAScript)
std::regex r_meta6("\\n\\r\\t");
// Anchors (ECMAScript: \b, \B only; \A, \G removed)
// Removed: \\Gabc — Ruby-only \G anchor (not in ECMAScript)
std::regex r_anc2("\\b!a\\B");
// Groups
std::regex r_grp1("(foo)*bar");
std::regex r_grp2("fo(o|b)ar");
std::regex r_grp3("(a|b|cd)e");
std::regex r_grp4("(?::+)\\w"); // Non-capturing group matching colons
// Named groups
std::regex r_ng1("(?<id>\\w+)");
// Removed: (?'foo'fo+) — Ruby single-quote named-group form (not in ECMAScript)
// Backreferences
std::regex r_bref1("(a+)b+\\1");
std::regex r_bref2("(?<qux>q+)\\s+\\k<qux>+");
// Named character properties using the p-style syntax
std::regex r_prop1("\\p{Word}*");
std::regex r_prop2("\\P{Digit}+");
std::regex r_prop3("\\p{^Alnum}{2,3}");
std::regex r_prop4("[a-f\\p{Digit}]+"); // Also valid inside character classes
// Two separate character classes, each containing a single POSIX bracket expression
std::regex r_posix1("[[:alpha:]][[:digit:]]");
// A single character class containing two POSIX bracket expressions
std::regex r_posix2("[[:alpha:][:digit:]]");
// A single character class containing two ranges and one POSIX bracket expression
std::regex r_posix3("[A-F[:digit:]a-f]");
// *Not* a POSIX bracket expression; just a regular character class.
std::regex r_posix4("[:digit:]");
// 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}");
}
// Location tests (commit 8: pre-fix columns; commit 9: fixes raw/prefixed offsets).
// Each literal is wrapped in an appropriate std::regex use so the literal is in the DB.
// For each form, we test a simple "a+b" regex so the term locations are predictable.
void test_locations() {
// Plain "..." — content offset 1 (CORRECT in current code)
std::regex r_plain("a+b");
// Raw R"(...)" — content offset 3 (R"( = 3 chars); currently wrong (uses 1)
std::regex r_raw(R"(a+b)");
// Raw with regex metacharacters — R"(\s+$)" offset 3; currently wrong
std::regex r_raw2(R"(\s+$)");
// Complex raw — R"(\(([,\w]+)+\)$)" offset 3; currently wrong
std::regex r_raw3(R"(\(([,\w]+)+\)$)");
// Custom-delimiter raw — R"x(a+b)x" offset 4 (R"x( = 4); currently wrong
std::regex r_raw4(R"x(a+b)x");
// Wide-char prefix L"..." — content offset 2 (L" = 2); currently wrong
std::wregex r_wide(L"a+b");
// Wide raw LR"(...)" — content offset 4 (LR"( = 4); currently wrong
std::wregex r_wide_raw(LR"(a+b)");
// Escape-containing plain — "\\s+" value is \s+ (2 chars); offset 1 correct
// (within-content mapping is approximate for escaped strings, documented)
std::regex r_esc1("\\s+");
// Escape with dot — "a\\.b" value is a\.b; offset 1 correct
std::regex r_esc2("a\\.b");
}