Skip to content

Commit 4a08ca3

Browse files
committed
Swift: Replace PotentialRegexEval with a more specialized solution.
1 parent 3549830 commit 4a08ca3

File tree

2 files changed

+63
-80
lines changed

2 files changed

+63
-80
lines changed

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

Lines changed: 63 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -299,40 +299,39 @@ private class NSStringRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
299299
* Regex("(a|b).*").firstMatch(in: myString)
300300
* ```
301301
*/
302-
class RegexEval extends CallExpr instanceof PotentialRegexEval {
303-
RegexEval() { this.doesEvaluate() }
304-
302+
abstract class RegexEval extends CallExpr {
305303
/**
306304
* Gets the input to this call that is the regular expression being evaluated.
307305
* This may be a regular expression object or a string literal.
308306
*
309307
* Consider using `getARegex()` instead (which tracks the regular expression
310308
* input back to its source).
311309
*/
312-
Expr getRegexInput() { result = super.getRegexInput().asExpr() }
310+
abstract Expr getRegexInput();
313311

314312
/**
315313
* Gets the input to this call that is the string the regular expression is evaluated on.
316314
*/
317-
Expr getStringInput() { result = super.getStringInput().asExpr() }
315+
abstract Expr getStringInput();
318316

319317
/**
320318
* Gets a dataflow node for an options input that might contain parse mode
321319
* flags (if any).
322320
*/
323-
DataFlow::Node getAnOptionsInput() { result = super.getAnOptionsInput() }
321+
DataFlow::Node getAnOptionsInput() { none() }
324322

325323
/**
326324
* Gets a regular expression value that is evaluated here (if any can be identified).
327325
*/
328326
RegExp getARegex() {
329327
// string literal used directly as a regex
330-
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() = super.getRegexInput()
328+
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() =
329+
DataFlow::exprNode(this.getRegexInput())
331330
or
332331
// string literal -> regex object -> use
333332
exists(RegexCreation regexCreation |
334333
DataFlow::exprNode(result).(ParsedStringRegex).getAParse() = regexCreation.getStringInput() and
335-
RegexUseFlow::flow(regexCreation, super.getRegexInput())
334+
RegexUseFlow::flow(regexCreation, DataFlow::exprNode(this.getRegexInput()))
336335
)
337336
}
338337

@@ -346,50 +345,17 @@ class RegexEval extends CallExpr instanceof PotentialRegexEval {
346345
any(RegexAdditionalFlowStep s).setsParseMode(setNode, result, true) and
347346
// reaches this eval
348347
(
349-
RegexParseModeFlow::flow(setNode, super.getRegexInput()) or
350-
RegexParseModeFlow::flow(setNode, super.getAnOptionsInput())
348+
RegexParseModeFlow::flow(setNode, DataFlow::exprNode(this.getRegexInput())) or
349+
RegexParseModeFlow::flow(setNode, this.getAnOptionsInput())
351350
)
352351
)
353352
}
354353
}
355354

356-
/**
357-
* A call that may evaluate a regular expression. Extend this abstract class to
358-
* add new regular expression evaluation models.
359-
*/
360-
abstract class PotentialRegexEval extends CallExpr {
361-
/**
362-
* Gets the input to this call that is the regular expression being evaluated.
363-
* This may be a regular expression object or a string literal.
364-
*/
365-
abstract DataFlow::Node getRegexInput();
366-
367-
/**
368-
* Gets the input to this call that is the string the regular expression is evaluated on.
369-
*/
370-
abstract DataFlow::Node getStringInput();
371-
372-
/**
373-
* Gets a dataflow node for an options input that might contain parse mode
374-
* flags (if any).
375-
*/
376-
DataFlow::Node getAnOptionsInput() { none() }
377-
378-
/**
379-
* Holds if this instance actually evaluates a regular expression. If this
380-
* does not hold, a `RegexEval` is not created for this `PotentialRegexEval`.
381-
*
382-
* This mechanism exists so that we have something to track flow of options
383-
* into (for example an `NSString.CompareOptions.regularExpression` option)
384-
* before deciding whether a regular expression is actually evaluated.
385-
*/
386-
predicate doesEvaluate() { any() }
387-
}
388-
389355
/**
390356
* A call to a function that always evaluates a regular expression.
391357
*/
392-
private class AlwaysRegexEval extends PotentialRegexEval {
358+
private class AlwaysRegexEval extends RegexEval {
393359
DataFlow::Node regexInput;
394360
DataFlow::Node stringInput;
395361

@@ -438,16 +404,18 @@ private class AlwaysRegexEval extends PotentialRegexEval {
438404
stringInput.asExpr() = this.getQualifier()
439405
}
440406

441-
override DataFlow::Node getRegexInput() { result = regexInput }
407+
override Expr getRegexInput() { result = regexInput.asExpr() }
442408

443-
override DataFlow::Node getStringInput() { result = stringInput }
409+
override Expr getStringInput() { result = stringInput.asExpr() }
444410
}
445411

446412
/**
447413
* A call to a function that sometimes evaluates a regular expression, if
448414
* `NSString.CompareOptions.regularExpression` is set as an `options` argument.
415+
*
416+
* This is a helper class for `NSStringCompareOptionsRegexEval`.
449417
*/
450-
private class NSStringCompareOptionsPotentialRegexEval extends PotentialRegexEval {
418+
private class NSStringCompareOptionsPotentialRegexEval extends CallExpr {
451419
DataFlow::Node regexInput;
452420
DataFlow::Node stringInput;
453421
DataFlow::Node optionsInput;
@@ -472,15 +440,58 @@ private class NSStringCompareOptionsPotentialRegexEval extends PotentialRegexEva
472440
optionsInput.asExpr() = this.getArgumentWithLabel("options").getExpr()
473441
}
474442

475-
override DataFlow::Node getRegexInput() { result = regexInput }
443+
DataFlow::Node getRegexInput() { result = regexInput }
444+
445+
DataFlow::Node getStringInput() { result = stringInput }
446+
447+
DataFlow::Node getAnOptionsInput() { result = optionsInput }
448+
}
476449

477-
override DataFlow::Node getStringInput() { result = stringInput }
450+
/**
451+
* A data flow configuration for tracking `NSString.CompareOptions.regularExpression`
452+
* values from where they are created to the point of use.
453+
*/
454+
private module NSStringCompareOptionsFlagConfig implements DataFlow::ConfigSig {
455+
predicate isSource(DataFlow::Node node) {
456+
// creation of a `NSString.CompareOptions.regularExpression` value
457+
node.asExpr()
458+
.(MemberRefExpr)
459+
.getMember()
460+
.(FieldDecl)
461+
.hasQualifiedName("NSString.CompareOptions", "regularExpression")
462+
}
478463

479-
override DataFlow::Node getAnOptionsInput() { result = optionsInput }
464+
predicate isSink(DataFlow::Node node) {
465+
// use in a regex eval `options` argument
466+
any(NSStringCompareOptionsPotentialRegexEval potentialEval).getAnOptionsInput() = node
467+
}
480468

481-
override predicate doesEvaluate() {
469+
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
470+
// flow out from collection content at the sink.
471+
isSink(node) and
472+
c.getAReadContent() instanceof DataFlow::Content::CollectionContent
473+
}
474+
}
475+
476+
module NSStringCompareOptionsFlagFlow = DataFlow::Global<NSStringCompareOptionsFlagConfig>;
477+
478+
/**
479+
* A call to a function that evaluates a regular expression because
480+
* `NSString.CompareOptions.regularExpression` is set as an `options` argument.
481+
*/
482+
private class NSStringCompareOptionsRegexEval extends RegexEval {
483+
NSStringCompareOptionsPotentialRegexEval potentialEval;
484+
485+
NSStringCompareOptionsRegexEval() {
486+
this = potentialEval and
482487
// check there is flow from a `NSString.CompareOptions.regularExpression` value to an `options` argument;
483488
// if there isn't, the input won't be interpretted as a regular expression.
484-
RegexEnableFlagFlow::flow(_, optionsInput)
489+
NSStringCompareOptionsFlagFlow::flow(_, potentialEval.getAnOptionsInput())
485490
}
491+
492+
override Expr getRegexInput() { result = potentialEval.getRegexInput().asExpr() }
493+
494+
override Expr getStringInput() { result = potentialEval.getStringInput().asExpr() }
495+
496+
override DataFlow::Node getAnOptionsInput() { result = potentialEval.getAnOptionsInput() }
486497
}

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

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -105,31 +105,3 @@ private module RegexParseModeConfig implements DataFlow::StateConfigSig {
105105
}
106106

107107
module RegexParseModeFlow = DataFlow::GlobalWithState<RegexParseModeConfig>;
108-
109-
/**
110-
* A data flow configuration for tracking `NSString.CompareOptions.regularExpression`
111-
* values from where they are created to the point of use.
112-
*/
113-
private module RegexEnableFlagConfig implements DataFlow::ConfigSig {
114-
predicate isSource(DataFlow::Node node) {
115-
// creation of a `NSString.CompareOptions.regularExpression` value
116-
node.asExpr()
117-
.(MemberRefExpr)
118-
.getMember()
119-
.(FieldDecl)
120-
.hasQualifiedName("NSString.CompareOptions", "regularExpression")
121-
}
122-
123-
predicate isSink(DataFlow::Node node) {
124-
// use in a regex eval `options` argument
125-
any(PotentialRegexEval eval).getAnOptionsInput() = node
126-
}
127-
128-
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
129-
// flow out from collection content at the sink.
130-
isSink(node) and
131-
c.getAReadContent() instanceof DataFlow::Content::CollectionContent
132-
}
133-
}
134-
135-
module RegexEnableFlagFlow = DataFlow::Global<RegexEnableFlagConfig>;

0 commit comments

Comments
 (0)