From afde8d9fc53ebeb3968e9b49cb38b950086e2f8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:22:15 +0000 Subject: [PATCH] C++: Implement POSIX collating symbols --- .../semmle/code/cpp/regex/RegexTreeView.qll | 20 +++++++- .../code/cpp/regex/internal/ParseRegExp.qll | 48 +++++++++++++++++-- .../test/library-tests/regex/parse.expected | 46 +++++------------- .../test/library-tests/regex/regexp.expected | 29 +++-------- 4 files changed, 80 insertions(+), 63 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll b/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll index 8eef64c9dc0..cf1b0a02d62 100644 --- a/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll +++ b/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll @@ -49,7 +49,11 @@ private newtype TRegExpParent = not re.specialCharacter(start, end, _) } or /** A back reference */ - TRegExpBackRef(RegExp re, int start, int end) { re.backreference(start, end) } + TRegExpBackRef(RegExp re, int start, int end) { re.backreference(start, end) } or + /** A POSIX collating symbol */ + TRegExpPosixCollatingSymbol(RegExp re, int start, int end) { + re.posixStyleCollatingSymbol(start, end, _) + } /** An implementation that satisfies the RegexTreeView signature. */ private module Impl implements RegexTreeViewSig { @@ -137,6 +141,8 @@ private module Impl implements RegexTreeViewSig { exists(seqChild(re, start, end, 1)) // if a sequence does not have more than one element, it should be treated as that element instead. or this = TRegExpSpecialChar(re, start, end) + or + this = TRegExpPosixCollatingSymbol(re, start, end) } /** @@ -177,6 +183,8 @@ private module Impl implements RegexTreeViewSig { result = this.(RegExpSequence).getChild(i) or result = this.(RegExpSpecialChar).getChild(i) + or + result = this.(RegExpPosixCollatingSymbol).getChild(i) } /** @@ -1113,6 +1121,16 @@ private module Impl implements RegexTreeViewSig { override predicate isNullable() { this.getGroup().isNullable() } } + additional class RegExpPosixCollatingSymbol extends RegExpTerm, TRegExpPosixCollatingSymbol { + RegExpPosixCollatingSymbol() { this = TRegExpPosixCollatingSymbol(re, start, end) } + + override RegExpTerm getChild(int i) { none() } + + override string getAPrimaryQlClass() { result = "RegExpPosixCollatingSymbol" } + + string getName() { re.posixStyleCollatingSymbol(start, end, result) } + } + class Top = RegExpParent; /** diff --git a/cpp/ql/lib/semmle/code/cpp/regex/internal/ParseRegExp.qll b/cpp/ql/lib/semmle/code/cpp/regex/internal/ParseRegExp.qll index 974c57a8908..24ba4b47cfb 100644 --- a/cpp/ql/lib/semmle/code/cpp/regex/internal/ParseRegExp.qll +++ b/cpp/ql/lib/semmle/code/cpp/regex/internal/ParseRegExp.qll @@ -101,7 +101,13 @@ abstract class RegExp extends StringLiteral { // Brackets that art part of POSIX expressions should not count as // char-set delimiters. not exists(int x, int y | - this.posixStyleNamedCharacterProperty(x, y, _) and pos >= x and pos < y + ( + this.posixStyleNamedCharacterProperty(x, y, _) + or + this.posixStyleCollatingSymbol(x, y, _) + ) and + pos >= x and + pos < y ) ) and ( @@ -133,7 +139,13 @@ abstract class RegExp extends StringLiteral { e > innerStart and this.nonEscapedCharAt(e) = "]" and not exists(int x, int y | - this.posixStyleNamedCharacterProperty(x, y, _) and e >= x and e < y + ( + this.posixStyleNamedCharacterProperty(x, y, _) + or + this.posixStyleCollatingSymbol(x, y, _) + ) and + e >= x and + e < y ) | e @@ -162,6 +174,8 @@ abstract class RegExp extends StringLiteral { or this.namedCharacterProperty(start, end, _) or + this.posixStyleCollatingSymbol(start, end, _) + or exists(this.nonEscapedCharAt(start)) and end = start + 1 ) or @@ -171,6 +185,8 @@ abstract class RegExp extends StringLiteral { or this.namedCharacterProperty(start, end, _) or + this.posixStyleCollatingSymbol(start, end, _) + or exists(this.nonEscapedCharAt(start)) and end = start + 1 and not this.getChar(start) = "]" @@ -313,6 +329,21 @@ abstract class RegExp extends StringLiteral { ) } + /** Matches a POSIX collating symbol such as `[.ch.]` within a character class. */ + predicate posixStyleCollatingSymbol(int start, int end, string name) { + this.getChar(start) = "[" and + this.getChar(start + 1) = "." and + end = + min(int e | + e > start and + this.getChar(e - 2) = "." and + this.getChar(e - 1) = "]" + | + e + ) and + name = this.getText().substring(start + 2, end - 2) + } + /** * Holds if the named character property is inverted. Examples for which it holds: * - `\P{Digit}` upper-case P means inverted @@ -371,7 +402,12 @@ abstract class RegExp extends StringLiteral { */ predicate inPosixBracket(int index) { exists(int x, int y | - this.posixStyleNamedCharacterProperty(x, y, _) and index in [x + 1 .. y - 2] + ( + this.posixStyleNamedCharacterProperty(x, y, _) + or + this.posixStyleCollatingSymbol(x, y, _) + ) and + index in [x + 1 .. y - 2] ) } @@ -383,7 +419,11 @@ abstract class RegExp extends StringLiteral { not this.charSet(start, _) and not this.charSet(_, start + 1) and not exists(int x, int y | - this.posixStyleNamedCharacterProperty(x, y, _) and + ( + this.posixStyleNamedCharacterProperty(x, y, _) + or + this.posixStyleCollatingSymbol(x, y, _) + ) and start >= x and end <= y ) and diff --git a/cpp/ql/test/library-tests/regex/parse.expected b/cpp/ql/test/library-tests/regex/parse.expected index 420e3f043ae..e2897718244 100644 --- a/cpp/ql/test/library-tests/regex/parse.expected +++ b/cpp/ql/test/library-tests/regex/parse.expected @@ -42,25 +42,7 @@ #-----| [RegExpConstant, RegExpNormalChar] [ -#-----| [RegExpConstant, RegExpNormalChar] [ - -#-----| [RegExpConstant, RegExpNormalChar] . - -#-----| [RegExpConstant, RegExpNormalChar] b - -#-----| [RegExpConstant, RegExpNormalChar] . - -#-----| [RegExpConstant, RegExpNormalChar] ]a-f] - -#-----| [RegExpConstant, RegExpNormalChar] [ - -#-----| [RegExpConstant, RegExpNormalChar] . - -#-----| [RegExpConstant, RegExpNormalChar] a - -#-----| [RegExpConstant, RegExpNormalChar] . - -#-----| [RegExpConstant, RegExpNormalChar] ] +#-----| [RegExpConstant, RegExpNormalChar] a-f] #-----| [RegExpConstant, RegExpNormalChar] A @@ -198,12 +180,8 @@ #-----| 2 -> [RegExpConstant, RegExpNormalChar] b #-----| [RegExpSequence] [A-F[[.b.]]a-f] -#-----| 0 -> [RegExpCharacterClass] [A-F[[.b.] -#-----| 1 -> [RegExpConstant, RegExpNormalChar] ]a-f] - -#-----| [RegExpSequence] [[.a.]] -#-----| 0 -> [RegExpCharacterClass] [[.a.] -#-----| 1 -> [RegExpConstant, RegExpNormalChar] ] +#-----| 0 -> [RegExpCharacterClass] [A-F[[.b.]] +#-----| 1 -> [RegExpConstant, RegExpNormalChar] a-f] #-----| [RegExpSequence] [[:alpha:]][[:digit:]] #-----| 0 -> [RegExpCharacterClass] [[:alpha:]] @@ -268,6 +246,10 @@ #-----| 2 -> [RegExpOpt] c? #-----| 3 -> [RegExpConstant, RegExpNormalChar] d +#-----| [RegExpPosixCollatingSymbol] [.b.] + +#-----| [RegExpPosixCollatingSymbol] [.a.] + #-----| [RegExpBackRef] \1 #-----| [RegExpSpecialChar] \b @@ -331,19 +313,13 @@ #-----| [RegExpCharacterClass] [\cZ] #-----| 0 -> [RegExpConstant, RegExpEscape] \cZ -#-----| [RegExpCharacterClass] [A-F[[.b.] +#-----| [RegExpCharacterClass] [A-F[[.b.]] #-----| 0 -> [RegExpCharacterRange] A-F #-----| 1 -> [RegExpConstant, RegExpNormalChar] [ -#-----| 2 -> [RegExpConstant, RegExpNormalChar] [ -#-----| 3 -> [RegExpConstant, RegExpNormalChar] . -#-----| 4 -> [RegExpConstant, RegExpNormalChar] b -#-----| 5 -> [RegExpConstant, RegExpNormalChar] . +#-----| 2 -> [RegExpPosixCollatingSymbol] [.b.] -#-----| [RegExpCharacterClass] [[.a.] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] [ -#-----| 1 -> [RegExpConstant, RegExpNormalChar] . -#-----| 2 -> [RegExpConstant, RegExpNormalChar] a -#-----| 3 -> [RegExpConstant, RegExpNormalChar] . +#-----| [RegExpCharacterClass] [[.a.]] +#-----| 0 -> [RegExpPosixCollatingSymbol] [.a.] #-----| [RegExpCharacterClass] [A-F[:digit:]a-f] #-----| 0 -> [RegExpCharacterRange] A-F diff --git a/cpp/ql/test/library-tests/regex/regexp.expected b/cpp/ql/test/library-tests/regex/regexp.expected index 18707fb6a52..9c7f8506d32 100644 --- a/cpp/ql/test/library-tests/regex/regexp.expected +++ b/cpp/ql/test/library-tests/regex/regexp.expected @@ -23,10 +23,6 @@ term | file://:0:0:0:0 | (foo)*bar | RegExpSequence | | file://:0:0:0:0 | (o\|b) | RegExpGroup | | file://:0:0:0:0 | - | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | . | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | . | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | . | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | . | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | . | RegExpDot | | file://:0:0:0:0 | . | RegExpDot | | file://:0:0:0:0 | .* | RegExpStar | @@ -48,14 +44,13 @@ term | file://:0:0:0:0 | Z | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | [ | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | [ | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | [ | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | [ | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | [123] | RegExpCharacterClass | +| file://:0:0:0:0 | [.a.] | RegExpPosixCollatingSymbol | +| file://:0:0:0:0 | [.b.] | RegExpPosixCollatingSymbol | | file://:0:0:0:0 | [A-F[:digit:]a-f] | RegExpCharacterClass | -| file://:0:0:0:0 | [A-F[[.b.] | RegExpCharacterClass | +| file://:0:0:0:0 | [A-F[[.b.]] | RegExpCharacterClass | | file://:0:0:0:0 | [A-F[[.b.]]a-f] | RegExpSequence | -| file://:0:0:0:0 | [[.a.] | RegExpCharacterClass | -| file://:0:0:0:0 | [[.a.]] | RegExpSequence | +| file://:0:0:0:0 | [[.a.]] | RegExpCharacterClass | | file://:0:0:0:0 | [[:alpha:][:digit:]] | RegExpCharacterClass | | file://:0:0:0:0 | [[:alpha:]] | RegExpCharacterClass | | file://:0:0:0:0 | [[:alpha:]][[:digit:]] | RegExpSequence | @@ -105,8 +100,6 @@ term | file://:0:0:0:0 | \| | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | ] | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | ] | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | ] | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | ]a-f] | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | _ | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | a | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | a | RegExpConstant,RegExpNormalChar | @@ -120,13 +113,13 @@ term | file://:0:0:0:0 | a | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | a | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | a | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | a | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | a* | RegExpStar | | file://:0:0:0:0 | a*b+c?d | RegExpSequence | | file://:0:0:0:0 | a+ | RegExpPlus | | file://:0:0:0:0 | a-f | RegExpCharacterRange | | file://:0:0:0:0 | a-f | RegExpCharacterRange | | file://:0:0:0:0 | a-f | RegExpCharacterRange | +| file://:0:0:0:0 | a-f] | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | a\\nb | RegExpSequence | | file://:0:0:0:0 | a\|b\|cd | RegExpAlt | | file://:0:0:0:0 | abc | RegExpConstant,RegExpNormalChar | @@ -152,7 +145,6 @@ term | file://:0:0:0:0 | b | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | b | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | b | RegExpConstant,RegExpNormalChar | -| file://:0:0:0:0 | b | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | b+ | RegExpPlus | | file://:0:0:0:0 | b+ | RegExpPlus | | file://:0:0:0:0 | bar | RegExpConstant,RegExpNormalChar | @@ -181,10 +173,6 @@ regExpNormalCharValue | file://:0:0:0:0 | 9 | 9 | | file://:0:0:0:0 | !a | !a | | file://:0:0:0:0 | - | - | -| file://:0:0:0:0 | . | . | -| file://:0:0:0:0 | . | . | -| file://:0:0:0:0 | . | . | -| file://:0:0:0:0 | . | . | | file://:0:0:0:0 | : | : | | file://:0:0:0:0 | A | A | | file://:0:0:0:0 | A | A | @@ -197,8 +185,6 @@ regExpNormalCharValue | file://:0:0:0:0 | Z | Z | | file://:0:0:0:0 | [ | [ | | file://:0:0:0:0 | [ | [ | -| file://:0:0:0:0 | [ | [ | -| file://:0:0:0:0 | [ | [ | | file://:0:0:0:0 | \\0 | 0 | | file://:0:0:0:0 | \\0 | 0 | | file://:0:0:0:0 | \\D | D | @@ -221,8 +207,6 @@ regExpNormalCharValue | file://:0:0:0:0 | \| | \| | | file://:0:0:0:0 | ] | ] | | file://:0:0:0:0 | ] | ] | -| file://:0:0:0:0 | ] | ] | -| file://:0:0:0:0 | ]a-f] | ]a-f] | | file://:0:0:0:0 | _ | _ | | file://:0:0:0:0 | a | a | | file://:0:0:0:0 | a | a | @@ -236,7 +220,7 @@ regExpNormalCharValue | file://:0:0:0:0 | a | a | | file://:0:0:0:0 | a | a | | file://:0:0:0:0 | a | a | -| file://:0:0:0:0 | a | a | +| file://:0:0:0:0 | a-f] | a-f] | | file://:0:0:0:0 | abc | abc | | file://:0:0:0:0 | abc | abc | | file://:0:0:0:0 | abc | abc | @@ -256,7 +240,6 @@ regExpNormalCharValue | file://:0:0:0:0 | b | b | | file://:0:0:0:0 | b | b | | file://:0:0:0:0 | b | b | -| file://:0:0:0:0 | b | b | | file://:0:0:0:0 | bar | bar | | file://:0:0:0:0 | bar | bar | | file://:0:0:0:0 | c | c |