Skip to content

Commit 67ff589

Browse files
authored
Commit 5: Remove Ruby-only parser features; shift to ECMAScript dialect
- ParseRegExp.qll: - specialCharacter: remove \A, \Z, \z, \G (Ruby-only anchors); keep only \b, \B - firstPart: remove \A arm; keep only ^ for start-of-string - lastPart: remove \Z, \z arms; keep only $ for end-of-string - nonCapturingGroupStart: remove < from char set (handled by namedGroupStart and lookbehindAssertionStart; was causing mis-tokenization of lookbehind) - namedGroupStart: remove Ruby-only single-quote (?'name'...) branch; keep only ECMAScript (?<name>...) syntax - RegexTreeView.qll: - RegExpCharacterClassEscape: remove h, H (Ruby hex-digit classes); keep d, D, s, S, w, W (ECMAScript set) - RegExpAnchor: change to [^, $] only (remove \A, \Z, \z) - RegExpDollar: change to $ only (remove \Z, \z) - RegExpCaret: change to ^ only (remove \A) - test.cpp: - r_cc3: replace \A[+-]?\d+ with ^[+-]?\d+ (ECMAScript caret) - r_meta5 \h\H: removed (Ruby-only hex classes) - r_anc1 \Gabc: removed (\G not in ECMAScript) - Regenerated parse.expected and regexp.expected via codeql test run --learn (CodeQL 2.26.1); all 2 tests pass.
1 parent 96c7e2d commit 67ff589

5 files changed

Lines changed: 137 additions & 162 deletions

File tree

cpp/ql/lib/semmle/code/cpp/regex/RegexTreeView.qll

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,8 @@ private module Impl implements RegexTreeViewSig {
631631
/**
632632
* A character class escape in a regular expression.
633633
* That is, an escaped character that denotes multiple characters.
634+
* ECMAScript supports: \d, \D, \s, \S, \w, \W
635+
* (Ruby-only \h, \H are removed)
634636
*
635637
* Examples:
636638
*
@@ -640,7 +642,7 @@ private module Impl implements RegexTreeViewSig {
640642
* ```
641643
*/
642644
class RegExpCharacterClassEscape extends RegExpEscape {
643-
RegExpCharacterClassEscape() { this.getValue() in ["d", "D", "s", "S", "w", "W", "h", "H"] }
645+
RegExpCharacterClassEscape() { this.getValue() in ["d", "D", "s", "S", "w", "W"] }
644646

645647
override RegExpTerm getChild(int i) { none() }
646648

@@ -921,21 +923,17 @@ private module Impl implements RegexTreeViewSig {
921923

922924
/**
923925
* A term that matches a specific position between characters in the string.
924-
*
925-
* Example:
926-
*
927-
* ```
928-
* \A
929-
* ```
926+
* ECMAScript anchors: `^` and `$` only.
927+
* (Ruby-only `\A`, `\Z`, `\z` removed)
930928
*/
931929
class RegExpAnchor extends RegExpSpecialChar {
932-
RegExpAnchor() { this.getChar() = ["^", "$", "\\A", "\\Z", "\\z"] }
930+
RegExpAnchor() { this.getChar() = ["^", "$"] }
933931

934932
override string getAPrimaryQlClass() { result = "RegExpAnchor" }
935933
}
936934

937935
/**
938-
* A dollar assertion `$` or `\Z` matching the end of a line.
936+
* A dollar assertion `$` matching the end of a line.
939937
*
940938
* Example:
941939
*
@@ -944,15 +942,15 @@ private module Impl implements RegexTreeViewSig {
944942
* ```
945943
*/
946944
class RegExpDollar extends RegExpAnchor {
947-
RegExpDollar() { this.getChar() = ["$", "\\Z", "\\z"] }
945+
RegExpDollar() { this.getChar() = "$" }
948946

949947
override string getAPrimaryQlClass() { result = "RegExpDollar" }
950948

951949
override predicate isNullable() { any() }
952950
}
953951

954952
/**
955-
* A caret assertion `^` or `\A` matching the beginning of a line.
953+
* A caret assertion `^` matching the beginning of a line.
956954
*
957955
* Example:
958956
*
@@ -961,7 +959,7 @@ private module Impl implements RegexTreeViewSig {
961959
* ```
962960
*/
963961
class RegExpCaret extends RegExpAnchor {
964-
RegExpCaret() { this.getChar() = ["^", "\\A"] }
962+
RegExpCaret() { this.getChar() = "^" }
965963

966964
override string getAPrimaryQlClass() { result = "RegExpCaret" }
967965

cpp/ql/lib/semmle/code/cpp/regex/internal/ParseRegExp.qll

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,8 @@ class RegExp extends StringLiteral {
468468

469469
/**
470470
* Holds if a special character is found between `start` and `end`.
471+
* ECMAScript anchors: `^`, `$`, `.`, `\b`, `\B` only.
472+
* Ruby-only anchors `\A`, `\Z`, `\z`, `\G` are not in ECMAScript.
471473
*/
472474
predicate specialCharacter(int start, int end, string char) {
473475
this.character(start, end) and
@@ -480,7 +482,7 @@ class RegExp extends StringLiteral {
480482
end = start + 2 and
481483
this.escapingChar(start) and
482484
char = this.getText().substring(start, end) and
483-
char = ["\\A", "\\Z", "\\z", "\\G", "\\b", "\\B"]
485+
char = ["\\b", "\\B"]
484486
)
485487
}
486488

@@ -642,11 +644,16 @@ class RegExp extends StringLiteral {
642644
this.simpleGroupStart(start, end)
643645
}
644646

645-
/** Matches the start of a non-capturing group, e.g. `(?:` */
647+
/**
648+
* Matches the start of a non-capturing group.
649+
* ECMAScript non-capturing groups: `(?:`, lookaheads `(?=`/`(?!`, comments `(?#`.
650+
* Note: `(?<` is intentionally excluded here — it is handled by `namedGroupStart`
651+
* and `lookbehindAssertionStart`.
652+
*/
646653
private predicate nonCapturingGroupStart(int start, int end) {
647654
this.isGroupStart(start) and
648655
this.getChar(start + 1) = "?" and
649-
this.getChar(start + 2) = [":", "=", "<", "!", "#"] and
656+
this.getChar(start + 2) = [":", "=", "!", "#"] and
650657
end = start + 3
651658
}
652659

@@ -659,25 +666,18 @@ class RegExp extends StringLiteral {
659666

660667
/**
661668
* Matches the start of a named group, such as:
662-
* - `(?<name>\w+)`
663-
* - `(?'name'\w+)`
669+
* - `(?<name>\w+)` (ECMAScript named group)
670+
* Note: Ruby-only `(?'name'\w+)` single-quote form is removed.
664671
*/
665672
private predicate namedGroupStart(int start, int end) {
666673
this.isGroupStart(start) and
667674
this.getChar(start + 1) = "?" and
668-
(
669-
this.getChar(start + 2) = "<" and
670-
not this.getChar(start + 3) = "=" and // (?<=foo) is a positive lookbehind assertion
671-
not this.getChar(start + 3) = "!" and // (?<!foo) is a negative lookbehind assertion
672-
exists(int nameEnd |
673-
nameEnd = min(int i | i > start + 3 and this.getChar(i) = ">") and
674-
end = nameEnd + 1
675-
)
676-
or
677-
this.getChar(start + 2) = "'" and
678-
exists(int nameEnd |
679-
nameEnd = min(int i | i > start + 2 and this.getChar(i) = "'") and end = nameEnd + 1
680-
)
675+
this.getChar(start + 2) = "<" and
676+
not this.getChar(start + 3) = "=" and // (?<=foo) is a positive lookbehind assertion
677+
not this.getChar(start + 3) = "!" and // (?<!foo) is a negative lookbehind assertion
678+
exists(int nameEnd |
679+
nameEnd = min(int i | i > start + 3 and this.getChar(i) = ">") and
680+
end = nameEnd + 1
681681
)
682682
}
683683

@@ -962,8 +962,8 @@ class RegExp extends StringLiteral {
962962
or
963963
this.qualifiedItem(x, start, true, _)
964964
or
965-
// ^ and \A match the start of the string
966-
this.specialCharacter(x, start, ["^", "\\A"])
965+
// ^ matches the start of the string (ECMAScript only)
966+
this.specialCharacter(x, start, "^")
967967
)
968968
or
969969
exists(int y | this.firstPart(start, y) |
@@ -988,8 +988,8 @@ class RegExp extends StringLiteral {
988988
or
989989
this.qualifiedItem(end, y, true, _)
990990
or
991-
// $, \Z, and \z match the end of the string.
992-
this.specialCharacter(end, y, ["$", "\\Z", "\\z"])
991+
// $ matches the end of the string (ECMAScript only)
992+
this.specialCharacter(end, y, "$")
993993
)
994994
or
995995
this.lastPart(_, end) and

cpp/ql/test/library-tests/regex/parse.expected

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -90,111 +90,111 @@ test.cpp:
9090

9191
# 45| [RegExpConstant, RegExpNormalChar] _
9292

93-
# 46| [RegExpCaret] \A
93+
# 47| [RegExpCaret] ^
9494

95-
# 46| [RegExpSequence] \A[+-]?\d+
96-
#-----| 0 -> [RegExpCaret] \A
95+
# 47| [RegExpSequence] ^[+-]?\d+
96+
#-----| 0 -> [RegExpCaret] ^
9797
#-----| 1 -> [RegExpOpt] [+-]?
9898
#-----| 2 -> [RegExpPlus] \d+
9999

100-
# 46| [RegExpCharacterClass] [+-]
100+
# 47| [RegExpCharacterClass] [+-]
101101
#-----| 0 -> [RegExpConstant, RegExpNormalChar] +
102102
#-----| 1 -> [RegExpConstant, RegExpNormalChar] -
103103

104-
# 46| [RegExpOpt] [+-]?
104+
# 47| [RegExpOpt] [+-]?
105105
#-----| 0 -> [RegExpCharacterClass] [+-]
106106

107-
# 46| [RegExpConstant, RegExpNormalChar] +
107+
# 47| [RegExpConstant, RegExpNormalChar] +
108108

109-
# 46| [RegExpConstant, RegExpNormalChar] -
109+
# 47| [RegExpConstant, RegExpNormalChar] -
110110

111-
# 46| [RegExpCharacterClassEscape] \d
111+
# 47| [RegExpCharacterClassEscape] \d
112112

113-
# 46| [RegExpPlus] \d+
113+
# 47| [RegExpPlus] \d+
114114
#-----| 0 -> [RegExpCharacterClassEscape] \d
115115

116-
# 47| [RegExpCharacterClass] [\w]
116+
# 48| [RegExpCharacterClass] [\w]
117117
#-----| 0 -> [RegExpCharacterClassEscape] \w
118118

119-
# 47| [RegExpPlus] [\w]+
119+
# 48| [RegExpPlus] [\w]+
120120
#-----| 0 -> [RegExpCharacterClass] [\w]
121121

122-
# 47| [RegExpCharacterClassEscape] \w
122+
# 48| [RegExpCharacterClassEscape] \w
123123

124-
# 48| [RegExpConstant, RegExpEscape] \[
124+
# 49| [RegExpConstant, RegExpEscape] \[
125125

126-
# 48| [RegExpSequence] \[\][123]
126+
# 49| [RegExpSequence] \[\][123]
127127
#-----| 0 -> [RegExpConstant, RegExpEscape] \[
128128
#-----| 1 -> [RegExpConstant, RegExpEscape] \]
129129
#-----| 2 -> [RegExpCharacterClass] [123]
130130

131-
# 48| [RegExpConstant, RegExpEscape] \]
131+
# 49| [RegExpConstant, RegExpEscape] \]
132132

133-
# 48| [RegExpCharacterClass] [123]
133+
# 49| [RegExpCharacterClass] [123]
134134
#-----| 0 -> [RegExpConstant, RegExpNormalChar] 1
135135
#-----| 1 -> [RegExpConstant, RegExpNormalChar] 2
136136
#-----| 2 -> [RegExpConstant, RegExpNormalChar] 3
137137

138-
# 48| [RegExpConstant, RegExpNormalChar] 1
138+
# 49| [RegExpConstant, RegExpNormalChar] 1
139139

140-
# 48| [RegExpConstant, RegExpNormalChar] 2
140+
# 49| [RegExpConstant, RegExpNormalChar] 2
141141

142-
# 48| [RegExpConstant, RegExpNormalChar] 3
142+
# 49| [RegExpConstant, RegExpNormalChar] 3
143143

144-
# 49| [RegExpCharacterClass] [^A-Z]
144+
# 50| [RegExpCharacterClass] [^A-Z]
145145
#-----| 0 -> [RegExpCharacterRange] A-Z
146146

147-
# 49| [RegExpConstant, RegExpNormalChar] A
147+
# 50| [RegExpConstant, RegExpNormalChar] A
148148

149-
# 49| [RegExpCharacterRange] A-Z
149+
# 50| [RegExpCharacterRange] A-Z
150150
#-----| 0 -> [RegExpConstant, RegExpNormalChar] A
151151
#-----| 1 -> [RegExpConstant, RegExpNormalChar] Z
152152

153-
# 49| [RegExpConstant, RegExpNormalChar] Z
153+
# 50| [RegExpConstant, RegExpNormalChar] Z
154154

155-
# 50| [RegExpCharacterClass] []]
155+
# 51| [RegExpCharacterClass] []]
156156
#-----| 0 -> [RegExpConstant, RegExpNormalChar] ]
157157

158-
# 50| [RegExpConstant, RegExpNormalChar] ]
158+
# 51| [RegExpConstant, RegExpNormalChar] ]
159159

160-
# 51| [RegExpCharacterClass] [^]]
160+
# 52| [RegExpCharacterClass] [^]]
161161
#-----| 0 -> [RegExpConstant, RegExpNormalChar] ]
162162

163-
# 51| [RegExpConstant, RegExpNormalChar] ]
163+
# 52| [RegExpConstant, RegExpNormalChar] ]
164164

165-
# 52| [RegExpCharacterClass] [^-]
165+
# 53| [RegExpCharacterClass] [^-]
166166
#-----| 0 -> [RegExpConstant, RegExpNormalChar] -
167167

168-
# 52| [RegExpConstant, RegExpNormalChar] -
168+
# 53| [RegExpConstant, RegExpNormalChar] -
169169

170-
# 53| [RegExpCharacterClass] [|]
170+
# 54| [RegExpCharacterClass] [|]
171171
#-----| 0 -> [RegExpConstant, RegExpNormalChar] |
172172

173-
# 53| [RegExpConstant, RegExpNormalChar] |
173+
# 54| [RegExpConstant, RegExpNormalChar] |
174174

175-
# 56| [RegExpCharacterClass] [[a-f]
175+
# 57| [RegExpCharacterClass] [[a-f]
176176
#-----| 0 -> [RegExpConstant, RegExpNormalChar] [
177177
#-----| 1 -> [RegExpCharacterRange] a-f
178178

179-
# 56| [RegExpSequence] [[a-f]A-F]
179+
# 57| [RegExpSequence] [[a-f]A-F]
180180
#-----| 0 -> [RegExpCharacterClass] [[a-f]
181181
#-----| 1 -> [RegExpConstant, RegExpNormalChar] A-F]
182182

183-
# 56| [RegExpConstant, RegExpNormalChar] [
183+
# 57| [RegExpConstant, RegExpNormalChar] [
184184

185-
# 56| [RegExpConstant, RegExpNormalChar] a
185+
# 57| [RegExpConstant, RegExpNormalChar] a
186186

187-
# 56| [RegExpCharacterRange] a-f
187+
# 57| [RegExpCharacterRange] a-f
188188
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
189189
#-----| 1 -> [RegExpConstant, RegExpNormalChar] f
190190

191-
# 56| [RegExpConstant, RegExpNormalChar] f
191+
# 57| [RegExpConstant, RegExpNormalChar] f
192192

193-
# 56| [RegExpConstant, RegExpNormalChar] A-F]
193+
# 57| [RegExpConstant, RegExpNormalChar] A-F]
194194

195-
# 59| [RegExpDot] .
195+
# 60| [RegExpDot] .
196196

197-
# 59| [RegExpStar] .*
197+
# 60| [RegExpStar] .*
198198
#-----| 0 -> [RegExpDot] .
199199

200200
# 61| [RegExpCharacterClassEscape] \w
@@ -224,14 +224,6 @@ test.cpp:
224224

225225
# 63| [RegExpCharacterClassEscape] \D
226226

227-
# 64| [RegExpCharacterClassEscape] \h
228-
229-
# 64| [RegExpSequence] \h\H
230-
#-----| 0 -> [RegExpCharacterClassEscape] \h
231-
#-----| 1 -> [RegExpCharacterClassEscape] \H
232-
233-
# 64| [RegExpCharacterClassEscape] \H
234-
235227
# 65| [RegExpConstant, RegExpEscape] \n
236228

237229
# 65| [RegExpSequence] \n\r\t
@@ -243,14 +235,6 @@ test.cpp:
243235

244236
# 65| [RegExpConstant, RegExpEscape] \t
245237

246-
# 68| [RegExpSpecialChar] \G
247-
248-
# 68| [RegExpSequence] \Gabc
249-
#-----| 0 -> [RegExpSpecialChar] \G
250-
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
251-
252-
# 68| [RegExpConstant, RegExpNormalChar] abc
253-
254238
# 69| [RegExpSpecialChar] \b
255239

256240
# 69| [RegExpSequence] \b!a\B

0 commit comments

Comments
 (0)