@@ -69,10 +69,15 @@ abstract class RegexCreation extends DataFlow::Node {
69
69
abstract DataFlow:: Node getStringInput ( ) ;
70
70
71
71
/**
72
- * Gets a dataflow node for the options input that might contain parse mode
73
- * flags (if any).
72
+ * Gets a dataflow node for an options input that might contain options
73
+ * such as parse mode flags (if any).
74
74
*/
75
- DataFlow:: Node getOptionsInput ( ) { none ( ) }
75
+ DataFlow:: Node getAnOptionsInput ( ) { none ( ) }
76
+
77
+ /**
78
+ * DEPRECATED: Use `getAnOptionsInput()` instead.
79
+ */
80
+ deprecated DataFlow:: Node getOptionsInput ( ) { result = this .getAnOptionsInput ( ) }
76
81
}
77
82
78
83
/**
@@ -110,7 +115,7 @@ private class NSRegularExpressionRegexCreation extends RegexCreation {
110
115
111
116
override DataFlow:: Node getStringInput ( ) { result = input }
112
117
113
- override DataFlow:: Node getOptionsInput ( ) {
118
+ override DataFlow:: Node getAnOptionsInput ( ) {
114
119
result .asExpr ( ) = this .asExpr ( ) .( CallExpr ) .getArgument ( 1 ) .getExpr ( )
115
120
}
116
121
}
@@ -121,7 +126,8 @@ private newtype TRegexParseMode =
121
126
MkDotAll ( ) or // dot matches all characters, including line terminators
122
127
MkMultiLine ( ) or // `^` and `$` also match beginning and end of lines
123
128
MkUnicodeBoundary ( ) or // Unicode UAX 29 word boundary mode
124
- MkUnicode ( ) // Unicode matching
129
+ MkUnicode ( ) or // Unicode matching
130
+ MkAnchoredStart ( ) // match must begin at start of string
125
131
126
132
/**
127
133
* A regular expression parse mode flag.
@@ -142,6 +148,8 @@ class RegexParseMode extends TRegexParseMode {
142
148
this = MkUnicodeBoundary ( ) and result = "UNICODEBOUNDARY"
143
149
or
144
150
this = MkUnicode ( ) and result = "UNICODE"
151
+ or
152
+ this = MkAnchoredStart ( ) and result = "ANCHOREDSTART"
145
153
}
146
154
147
155
/**
@@ -207,9 +215,9 @@ class RegexRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
207
215
}
208
216
209
217
/**
210
- * An additional flow step for `NSRegularExpression`.
218
+ * An additional flow step for `NSRegularExpression.Options `.
211
219
*/
212
- class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
220
+ private class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
213
221
override predicate step ( DataFlow:: Node nodeFrom , DataFlow:: Node nodeTo ) { none ( ) }
214
222
215
223
override predicate setsParseMode ( DataFlow:: Node node , RegexParseMode mode , boolean isSet ) {
@@ -257,6 +265,34 @@ class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep
257
265
}
258
266
}
259
267
268
+ /**
269
+ * An additional flow step for `NSString.CompareOptions`.
270
+ */
271
+ private 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
+
260
296
/**
261
297
* A call that evaluates a regular expression. For example, the call to `firstMatch` in:
262
298
* ```
@@ -265,27 +301,46 @@ class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep
265
301
*/
266
302
abstract class RegexEval extends CallExpr {
267
303
/**
268
- * Gets the input to this call that is the regular expression being evaluated. This may
269
- * be a regular expression object or a string literal.
304
+ * Gets the input to this call that is the regular expression being evaluated.
305
+ * This may be a regular expression object or a string literal.
306
+ *
307
+ * Consider using `getARegex()` instead (which tracks the regular expression
308
+ * input back to its source).
309
+ */
310
+ abstract DataFlow:: Node getRegexInputNode ( ) ;
311
+
312
+ /**
313
+ * DEPRECATED: Use `getRegexInputNode()` instead.
270
314
*/
271
- abstract Expr getRegexInput ( ) ;
315
+ deprecated Expr getRegexInput ( ) { result = this . getRegexInputNode ( ) . asExpr ( ) }
272
316
273
317
/**
274
318
* Gets the input to this call that is the string the regular expression is evaluated on.
275
319
*/
276
- abstract Expr getStringInput ( ) ;
320
+ abstract DataFlow:: Node getStringInputNode ( ) ;
321
+
322
+ /**
323
+ * DEPRECATED: Use `getStringInputNode()` instead.
324
+ */
325
+ deprecated Expr getStringInput ( ) { result = this .getStringInputNode ( ) .asExpr ( ) }
326
+
327
+ /**
328
+ * Gets a dataflow node for an options input that might contain options such
329
+ * as parse mode flags (if any).
330
+ */
331
+ DataFlow:: Node getAnOptionsInput ( ) { none ( ) }
277
332
278
333
/**
279
334
* Gets a regular expression value that is evaluated here (if any can be identified).
280
335
*/
281
336
RegExp getARegex ( ) {
282
337
// string literal used directly as a regex
283
- DataFlow:: exprNode ( result ) .( ParsedStringRegex ) .getAParse ( ) . asExpr ( ) = this .getRegexInput ( )
338
+ DataFlow:: exprNode ( result ) .( ParsedStringRegex ) .getAParse ( ) = this .getRegexInputNode ( )
284
339
or
285
340
// string literal -> regex object -> use
286
341
exists ( RegexCreation regexCreation |
287
342
DataFlow:: exprNode ( result ) .( ParsedStringRegex ) .getAParse ( ) = regexCreation .getStringInput ( ) and
288
- RegexUseFlow:: flow ( regexCreation , DataFlow :: exprNode ( this .getRegexInput ( ) ) )
343
+ RegexUseFlow:: flow ( regexCreation , this .getRegexInputNode ( ) )
289
344
)
290
345
}
291
346
@@ -298,7 +353,10 @@ abstract class RegexEval extends CallExpr {
298
353
// parse mode flag is set
299
354
any ( RegexAdditionalFlowStep s ) .setsParseMode ( setNode , result , true ) and
300
355
// reaches this eval
301
- RegexParseModeFlow:: flow ( setNode , DataFlow:: exprNode ( this .getRegexInput ( ) ) )
356
+ (
357
+ RegexParseModeFlow:: flow ( setNode , this .getRegexInputNode ( ) ) or
358
+ RegexParseModeFlow:: flow ( setNode , this .getAnOptionsInput ( ) )
359
+ )
302
360
)
303
361
}
304
362
}
@@ -307,15 +365,15 @@ abstract class RegexEval extends CallExpr {
307
365
* A call to a function that always evaluates a regular expression.
308
366
*/
309
367
private class AlwaysRegexEval extends RegexEval {
310
- Expr regexInput ;
311
- Expr stringInput ;
368
+ DataFlow :: Node regexInput ;
369
+ DataFlow :: Node stringInput ;
312
370
313
371
AlwaysRegexEval ( ) {
314
372
this .getStaticTarget ( )
315
373
.( Method )
316
374
.hasQualifiedName ( "Regex" , [ "firstMatch(in:)" , "prefixMatch(in:)" , "wholeMatch(in:)" ] ) and
317
- regexInput = this .getQualifier ( ) and
318
- stringInput = this .getArgument ( 0 ) .getExpr ( )
375
+ regexInput . asExpr ( ) = this .getQualifier ( ) and
376
+ stringInput . asExpr ( ) = this .getArgument ( 0 ) .getExpr ( )
319
377
or
320
378
this .getStaticTarget ( )
321
379
.( Method )
@@ -327,8 +385,8 @@ private class AlwaysRegexEval extends RegexEval {
327
385
"replaceMatches(in:options:range:withTemplate:)" ,
328
386
"stringByReplacingMatches(in:options:range:withTemplate:)"
329
387
] ) and
330
- regexInput = this .getQualifier ( ) and
331
- stringInput = this .getArgument ( 0 ) .getExpr ( )
388
+ regexInput . asExpr ( ) = this .getQualifier ( ) and
389
+ stringInput . asExpr ( ) = this .getArgument ( 0 ) .getExpr ( )
332
390
or
333
391
this .getStaticTarget ( )
334
392
.( Method )
@@ -339,8 +397,8 @@ private class AlwaysRegexEval extends RegexEval {
339
397
"split(separator:maxSplits:omittingEmptySubsequences:)" , "starts(with:)" ,
340
398
"trimmingPrefix(_:)" , "wholeMatch(of:)"
341
399
] ) and
342
- regexInput = this .getArgument ( 0 ) .getExpr ( ) and
343
- stringInput = this .getQualifier ( )
400
+ regexInput . asExpr ( ) = this .getArgument ( 0 ) .getExpr ( ) and
401
+ stringInput . asExpr ( ) = this .getQualifier ( )
344
402
or
345
403
this .getStaticTarget ( )
346
404
.( Method )
@@ -351,11 +409,103 @@ private class AlwaysRegexEval extends RegexEval {
351
409
"replacing(_:with:maxReplacements:)" , "replacing(_:with:subrange:maxReplacements:)" ,
352
410
"trimPrefix(_:)"
353
411
] ) and
354
- regexInput = this .getArgument ( 0 ) .getExpr ( ) and
355
- stringInput = this .getQualifier ( )
412
+ regexInput . asExpr ( ) = this .getArgument ( 0 ) .getExpr ( ) and
413
+ stringInput . asExpr ( ) = this .getQualifier ( )
356
414
}
357
415
358
- override Expr getRegexInput ( ) { result = regexInput }
416
+ override DataFlow :: Node getRegexInputNode ( ) { result = regexInput }
359
417
360
- override Expr getStringInput ( ) { result = stringInput }
418
+ override DataFlow:: Node getStringInputNode ( ) { result = stringInput }
419
+ }
420
+
421
+ /**
422
+ * A call to a function that sometimes evaluates a regular expression, if
423
+ * `NSString.CompareOptions.regularExpression` is set as an `options` argument.
424
+ *
425
+ * This is a helper class for `NSStringCompareOptionsRegexEval`.
426
+ */
427
+ private class NSStringCompareOptionsPotentialRegexEval extends CallExpr {
428
+ DataFlow:: Node regexInput ;
429
+ DataFlow:: Node stringInput ;
430
+ DataFlow:: Node optionsInput ;
431
+
432
+ NSStringCompareOptionsPotentialRegexEval ( ) {
433
+ (
434
+ this .getStaticTarget ( )
435
+ .( Method )
436
+ .hasQualifiedName ( "StringProtocol" ,
437
+ [ "range(of:options:range:locale:)" , "replacingOccurrences(of:with:options:range:)" ] )
438
+ or
439
+ this .getStaticTarget ( )
440
+ .( Method )
441
+ .hasQualifiedName ( "NSString" ,
442
+ [
443
+ "range(of:options:)" , "range(of:options:range:)" , "range(of:options:range:locale:)" ,
444
+ "replacingOccurrences(of:with:options:range:)"
445
+ ] )
446
+ ) and
447
+ regexInput .asExpr ( ) = this .getArgument ( 0 ) .getExpr ( ) and
448
+ stringInput .asExpr ( ) = this .getQualifier ( ) and
449
+ optionsInput .asExpr ( ) = this .getArgumentWithLabel ( "options" ) .getExpr ( )
450
+ }
451
+
452
+ DataFlow:: Node getRegexInput ( ) { result = regexInput }
453
+
454
+ DataFlow:: Node getStringInput ( ) { result = stringInput }
455
+
456
+ DataFlow:: Node getAnOptionsInput ( ) { result = optionsInput }
457
+ }
458
+
459
+ /**
460
+ * A data flow configuration for tracking `NSString.CompareOptions.regularExpression`
461
+ * values from where they are created to the point of use.
462
+ */
463
+ private module NSStringCompareOptionsFlagConfig implements DataFlow:: ConfigSig {
464
+ predicate isSource ( DataFlow:: Node node ) {
465
+ // creation of a `NSString.CompareOptions.regularExpression` value
466
+ node .asExpr ( )
467
+ .( MemberRefExpr )
468
+ .getMember ( )
469
+ .( FieldDecl )
470
+ .hasQualifiedName ( "NSString.CompareOptions" , "regularExpression" )
471
+ }
472
+
473
+ predicate isSink ( DataFlow:: Node node ) {
474
+ // use in a [potential] regex eval `options` argument
475
+ any ( NSStringCompareOptionsPotentialRegexEval potentialEval ) .getAnOptionsInput ( ) = node
476
+ }
477
+
478
+ predicate allowImplicitRead ( DataFlow:: Node node , DataFlow:: ContentSet c ) {
479
+ // flow out from collection content at the sink.
480
+ isSink ( node ) and
481
+ c .getAReadContent ( ) instanceof DataFlow:: Content:: CollectionContent
482
+ }
483
+ }
484
+
485
+ module NSStringCompareOptionsFlagFlow = DataFlow:: Global< NSStringCompareOptionsFlagConfig > ;
486
+
487
+ /**
488
+ * A call to a function that evaluates a regular expression because
489
+ * `NSString.CompareOptions.regularExpression` is set as an `options` argument.
490
+ */
491
+ private class NSStringCompareOptionsRegexEval extends RegexEval instanceof NSStringCompareOptionsPotentialRegexEval
492
+ {
493
+ NSStringCompareOptionsRegexEval ( ) {
494
+ // check there is flow from a `NSString.CompareOptions.regularExpression` value to an `options` argument;
495
+ // if there isn't, the input won't be interpretted as a regular expression.
496
+ NSStringCompareOptionsFlagFlow:: flow ( _,
497
+ this .( NSStringCompareOptionsPotentialRegexEval ) .getAnOptionsInput ( ) )
498
+ }
499
+
500
+ override DataFlow:: Node getRegexInputNode ( ) {
501
+ result = this .( NSStringCompareOptionsPotentialRegexEval ) .getRegexInput ( )
502
+ }
503
+
504
+ override DataFlow:: Node getStringInputNode ( ) {
505
+ result = this .( NSStringCompareOptionsPotentialRegexEval ) .getStringInput ( )
506
+ }
507
+
508
+ override DataFlow:: Node getAnOptionsInput ( ) {
509
+ result = this .( NSStringCompareOptionsPotentialRegexEval ) .getAnOptionsInput ( )
510
+ }
361
511
}
0 commit comments