Skip to content

Commit fa10dbe

Browse files
committed
Swift: Support mode flags through NSString.CompareOptions.
1 parent a052a4e commit fa10dbe

File tree

3 files changed

+45
-8
lines changed

3 files changed

+45
-8
lines changed

swift/ql/lib/codeql/swift/regex/Regex.qll

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ private newtype TRegexParseMode =
126126
MkDotAll() or // dot matches all characters, including line terminators
127127
MkMultiLine() or // `^` and `$` also match beginning and end of lines
128128
MkUnicodeBoundary() or // Unicode UAX 29 word boundary mode
129-
MkUnicode() // Unicode matching
129+
MkUnicode() or // Unicode matching
130+
MkAnchoredStart() // match must begin at start of string
130131

131132
/**
132133
* A regular expression parse mode flag.
@@ -147,6 +148,8 @@ class RegexParseMode extends TRegexParseMode {
147148
this = MkUnicodeBoundary() and result = "UNICODEBOUNDARY"
148149
or
149150
this = MkUnicode() and result = "UNICODE"
151+
or
152+
this = MkAnchoredStart() and result = "ANCHOREDSTART"
150153
}
151154

152155
/**
@@ -262,6 +265,34 @@ class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep
262265
}
263266
}
264267

268+
/**
269+
* An additional flow step for `NSString.CompareOptions`.
270+
*/
271+
class NSStringRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
272+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
273+
274+
override predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet) {
275+
// `NSString.CompareOptions` values (these are typically combined with
276+
// `NSString.CompareOptions.regularExpression`, then passed into a `StringProtocol`
277+
// or `NSString` method).
278+
node.asExpr()
279+
.(MemberRefExpr)
280+
.getMember()
281+
.(FieldDecl)
282+
.hasQualifiedName("NSString.CompareOptions", "caseInsensitive") and
283+
mode = MkIgnoreCase() and
284+
isSet = true
285+
or
286+
node.asExpr()
287+
.(MemberRefExpr)
288+
.getMember()
289+
.(FieldDecl)
290+
.hasQualifiedName("NSString.CompareOptions", "anchored") and
291+
mode = MkAnchoredStart() and
292+
isSet = true
293+
}
294+
}
295+
265296
/**
266297
* A call that evaluates a regular expression. For example, the call to `firstMatch` in:
267298
* ```
@@ -309,7 +340,10 @@ abstract class RegexEval extends CallExpr {
309340
// parse mode flag is set
310341
any(RegexAdditionalFlowStep s).setsParseMode(setNode, result, true) and
311342
// reaches this eval
312-
RegexParseModeFlow::flow(setNode, this.getRegexInput())
343+
(
344+
RegexParseModeFlow::flow(setNode, this.getRegexInput()) or
345+
RegexParseModeFlow::flow(setNode, this.getAnOptionsInput())
346+
)
313347
)
314348
}
315349
}

swift/ql/lib/codeql/swift/regex/internal/RegexTracking.qll

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ private module RegexParseModeConfig implements DataFlow::StateConfigSig {
6666

6767
predicate isSink(DataFlow::Node node, FlowState flowstate) {
6868
// evaluation of a regex
69-
node = any(RegexEval eval).getRegexInput() and
69+
(
70+
node = any(RegexEval eval).getRegexInput() or
71+
node = any(RegexEval eval).getAnOptionsInput()
72+
) and
7073
exists(flowstate)
7174
}
7275

swift/ql/test/library-tests/regex/regex.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
150150
_ = inputNS.range(of: ".*", options: .regularExpression) // $ regex=.* input=inputNS
151151
_ = inputNS.range(of: ".*", options: [.regularExpression]) // $ regex=.* input=inputNS
152152
_ = inputNS.range(of: ".*", options: regexOptions) // $ regex=.* input=inputNS
153-
_ = inputNS.range(of: ".*", options: regexOptions2) // $ regex=.* input=inputNS
153+
_ = inputNS.range(of: ".*", options: regexOptions2) // $ regex=.* input=inputNS modes=IGNORECASE
154154
_ = inputNS.range(of: ".*", options: .literal) // (not a regular expression)
155155
_ = inputNS.replacingOccurrences(of: ".*", with: "", options: .regularExpression, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input=inputNS
156156
_ = inputNS.replacingOccurrences(of: ".*", with: "", options: .literal, range: NSMakeRange(0, inputNS.length)) // (not a regular expression)
@@ -248,8 +248,8 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
248248
// parse modes set through other methods
249249

250250
let myOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive]
251-
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ regex=.* input=input MISSING: modes=IGNORECASE
252-
_ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ regex=.* input=input MISSING: modes=IGNORECASE
253-
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" MISSING: modes=IGNORECASE
254-
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" MISSING: modes=IGNORECASE
251+
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ regex=.* input=input modes=IGNORECASE
252+
_ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ regex=.* input=input modes=IGNORECASE
253+
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" modes=IGNORECASE
254+
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" modes=IGNORECASE
255255
}

0 commit comments

Comments
 (0)