From 12dc4b250f559424efa39a9377f15cd7ae36b214 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:24:06 +0000 Subject: [PATCH] C++: Implement POSIX equivalence classes --- .../semmle/code/cpp/regex/RegexTreeView.qll | 18 ++++++++ .../code/cpp/regex/internal/ParseRegExp.qll | 27 +++++++++++ .../test/library-tests/regex/parse.expected | 46 +++++-------------- .../test/library-tests/regex/regexp.expected | 29 +++--------- 4 files changed, 62 insertions(+), 58 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll b/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll index cf1b0a02d62..7097025c6cb 100644 --- a/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll +++ b/cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll @@ -53,6 +53,10 @@ private newtype TRegExpParent = /** A POSIX collating symbol */ TRegExpPosixCollatingSymbol(RegExp re, int start, int end) { re.posixStyleCollatingSymbol(start, end, _) + } or + /** A POSIX equivalence class */ + TRegExpPosixEquivalenceClass(RegExp re, int start, int end) { + re.posixStyleEquivalenceClass(start, end, _) } /** An implementation that satisfies the RegexTreeView signature. */ @@ -143,6 +147,8 @@ private module Impl implements RegexTreeViewSig { this = TRegExpSpecialChar(re, start, end) or this = TRegExpPosixCollatingSymbol(re, start, end) + or + this = TRegExpPosixEquivalenceClass(re, start, end) } /** @@ -185,6 +191,8 @@ private module Impl implements RegexTreeViewSig { result = this.(RegExpSpecialChar).getChild(i) or result = this.(RegExpPosixCollatingSymbol).getChild(i) + or + result = this.(RegExpPosixEquivalenceClass).getChild(i) } /** @@ -1131,6 +1139,16 @@ private module Impl implements RegexTreeViewSig { string getName() { re.posixStyleCollatingSymbol(start, end, result) } } + additional class RegExpPosixEquivalenceClass extends RegExpTerm, TRegExpPosixEquivalenceClass { + RegExpPosixEquivalenceClass() { this = TRegExpPosixEquivalenceClass(re, start, end) } + + override RegExpTerm getChild(int i) { none() } + + override string getAPrimaryQlClass() { result = "RegExpPosixEquivalenceClass" } + + string getName() { re.posixStyleEquivalenceClass(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 24ba4b47cfb..739b2c88481 100644 --- a/cpp/ql/lib/semmle/code/cpp/regex/internal/ParseRegExp.qll +++ b/cpp/ql/lib/semmle/code/cpp/regex/internal/ParseRegExp.qll @@ -105,6 +105,8 @@ abstract class RegExp extends StringLiteral { this.posixStyleNamedCharacterProperty(x, y, _) or this.posixStyleCollatingSymbol(x, y, _) + or + this.posixStyleEquivalenceClass(x, y, _) ) and pos >= x and pos < y @@ -143,6 +145,8 @@ abstract class RegExp extends StringLiteral { this.posixStyleNamedCharacterProperty(x, y, _) or this.posixStyleCollatingSymbol(x, y, _) + or + this.posixStyleEquivalenceClass(x, y, _) ) and e >= x and e < y @@ -176,6 +180,8 @@ abstract class RegExp extends StringLiteral { or this.posixStyleCollatingSymbol(start, end, _) or + this.posixStyleEquivalenceClass(start, end, _) + or exists(this.nonEscapedCharAt(start)) and end = start + 1 ) or @@ -187,6 +193,8 @@ abstract class RegExp extends StringLiteral { or this.posixStyleCollatingSymbol(start, end, _) or + this.posixStyleEquivalenceClass(start, end, _) + or exists(this.nonEscapedCharAt(start)) and end = start + 1 and not this.getChar(start) = "]" @@ -344,6 +352,21 @@ abstract class RegExp extends StringLiteral { name = this.getText().substring(start + 2, end - 2) } + /** Matches a POSIX equivalence class such as `[=a=]` within a character class. */ + predicate posixStyleEquivalenceClass(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 @@ -406,6 +429,8 @@ abstract class RegExp extends StringLiteral { this.posixStyleNamedCharacterProperty(x, y, _) or this.posixStyleCollatingSymbol(x, y, _) + or + this.posixStyleEquivalenceClass(x, y, _) ) and index in [x + 1 .. y - 2] ) @@ -423,6 +448,8 @@ abstract class RegExp extends StringLiteral { this.posixStyleNamedCharacterProperty(x, y, _) or this.posixStyleCollatingSymbol(x, y, _) + or + this.posixStyleEquivalenceClass(x, y, _) ) and start >= x and end <= y diff --git a/cpp/ql/test/library-tests/regex/parse.expected b/cpp/ql/test/library-tests/regex/parse.expected index 32bf025e6d7..82c57962c2f 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 @@ -206,12 +188,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] [A-F[[.b.]]a-f] #-----| 0 -> [RegExpCharacterClass] [A-F[[.b.]] @@ -280,6 +258,10 @@ #-----| 2 -> [RegExpOpt] c? #-----| 3 -> [RegExpConstant, RegExpNormalChar] d +#-----| [RegExpPosixEquivalenceClass] [=b=] + +#-----| [RegExpPosixEquivalenceClass] [=a=] + #-----| [RegExpPosixCollatingSymbol] [.b.] #-----| [RegExpPosixCollatingSymbol] [.a.] @@ -351,19 +333,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 -> [RegExpPosixEquivalenceClass] [=b=] -#-----| [RegExpCharacterClass] [[=a=] -#-----| 0 -> [RegExpConstant, RegExpNormalChar] [ -#-----| 1 -> [RegExpConstant, RegExpNormalChar] = -#-----| 2 -> [RegExpConstant, RegExpNormalChar] a -#-----| 3 -> [RegExpConstant, RegExpNormalChar] = +#-----| [RegExpCharacterClass] [[=a=]] +#-----| 0 -> [RegExpPosixEquivalenceClass] [=a=] #-----| [RegExpCharacterClass] [A-F[[.b.]] #-----| 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 b8c4976a0df..ac7aa2b58d6 100644 --- a/cpp/ql/test/library-tests/regex/regexp.expected +++ b/cpp/ql/test/library-tests/regex/regexp.expected @@ -29,10 +29,6 @@ term | file://:0:0:0:0 | .* | RegExpStar | | file://:0:0:0:0 | : | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | :+ | RegExpPlus | -| 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 | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | A | RegExpConstant,RegExpNormalChar | | file://:0:0:0:0 | A | RegExpConstant,RegExpNormalChar | @@ -52,23 +48,22 @@ 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 | [ | 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=] | RegExpPosixEquivalenceClass | +| file://:0:0:0:0 | [=b=] | RegExpPosixEquivalenceClass | | 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.]]a-f] | RegExpSequence | -| 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 | [[:alpha:][:digit:]] | RegExpCharacterClass | | file://:0:0:0:0 | [[:alpha:]] | RegExpCharacterClass | | file://:0:0:0:0 | [[:alpha:]][[:digit:]] | RegExpSequence | | file://:0:0:0:0 | [[:digit:]] | RegExpCharacterClass | -| 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 | [[a-f] | RegExpCharacterClass | | file://:0:0:0:0 | [[a-f]A-F] | RegExpSequence | | file://:0:0:0:0 | [\\0] | RegExpCharacterClass | @@ -114,8 +109,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 | @@ -129,7 +122,6 @@ 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 | @@ -137,6 +129,7 @@ term | 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-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 | @@ -162,7 +155,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 | @@ -192,10 +184,6 @@ regExpNormalCharValue | 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 | | file://:0:0:0:0 | A | A | @@ -210,8 +198,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 | [ | [ | | file://:0:0:0:0 | \\0 | 0 | | file://:0:0:0:0 | \\0 | 0 | | file://:0:0:0:0 | \\D | D | @@ -234,8 +220,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 | @@ -249,7 +233,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 | a-f] | a-f] | | file://:0:0:0:0 | abc | abc | | file://:0:0:0:0 | abc | abc | @@ -270,7 +254,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 |