Merge pull request #13770 from geoffw0/parsemode3

Swift: Track regular expression parse modes set in code
This commit is contained in:
Geoffrey White
2023-07-20 13:18:41 +01:00
committed by GitHub
6 changed files with 438 additions and 178 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The regular expression library now understands mode flags specified by `Regex` methods and the `NSRegularExpression` initializer.

View File

@@ -36,22 +36,23 @@ abstract class RegexCreation extends DataFlow::Node {
* created from.
*/
abstract DataFlow::Node getStringInput();
/**
* Gets a dataflow node for the options input that might contain parse mode
* flags (if any).
*/
DataFlow::Node getOptionsInput() { none() }
}
/**
* A data-flow node where a `Regex` or `NSRegularExpression` object is created.
* A data-flow node where a `Regex` object is created.
*/
private class StandardRegexCreation extends RegexCreation {
private class RegexRegexCreation extends RegexCreation {
DataFlow::Node input;
StandardRegexCreation() {
RegexRegexCreation() {
exists(CallExpr call |
(
call.getStaticTarget().(Method).hasQualifiedName("Regex", ["init(_:)", "init(_:as:)"]) or
call.getStaticTarget()
.(Method)
.hasQualifiedName("NSRegularExpression", "init(pattern:options:)")
) and
call.getStaticTarget().(Method).hasQualifiedName("Regex", ["init(_:)", "init(_:as:)"]) and
input.asExpr() = call.getArgument(0).getExpr() and
this.asExpr() = call
)
@@ -60,6 +61,168 @@ private class StandardRegexCreation extends RegexCreation {
override DataFlow::Node getStringInput() { result = input }
}
/**
* A data-flow node where an `NSRegularExpression` object is created.
*/
private class NSRegularExpressionRegexCreation extends RegexCreation {
DataFlow::Node input;
NSRegularExpressionRegexCreation() {
exists(CallExpr call |
call.getStaticTarget()
.(Method)
.hasQualifiedName("NSRegularExpression", "init(pattern:options:)") and
input.asExpr() = call.getArgument(0).getExpr() and
this.asExpr() = call
)
}
override DataFlow::Node getStringInput() { result = input }
override DataFlow::Node getOptionsInput() {
result.asExpr() = this.asExpr().(CallExpr).getArgument(1).getExpr()
}
}
private newtype TRegexParseMode =
MkIgnoreCase() or // case insensitive
MkVerbose() or // ignores whitespace and `#` comments within patterns
MkDotAll() or // dot matches all characters, including line terminators
MkMultiLine() or // `^` and `$` also match beginning and end of lines
MkUnicode() // Unicode UAX 29 word boundary mode
/**
* A regular expression parse mode flag.
*/
class RegexParseMode extends TRegexParseMode {
/**
* Gets the name of this parse mode flag.
*/
string getName() {
this = MkIgnoreCase() and result = "IGNORECASE"
or
this = MkVerbose() and result = "VERBOSE"
or
this = MkDotAll() and result = "DOTALL"
or
this = MkMultiLine() and result = "MULTILINE"
or
this = MkUnicode() and result = "UNICODE"
}
/**
* Gets a textual representation of this `RegexParseMode`.
*/
string toString() { result = this.getName() }
}
/**
* A unit class for adding additional flow steps for regular expressions.
*/
class RegexAdditionalFlowStep extends Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a flow
* step for regular expressions.
*/
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
/**
* Holds if a regular expression parse mode is either set (`isSet` = true)
* or unset (`isSet` = false) at `node`. Parse modes propagate through
* array construction and regex construction.
*/
abstract predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet);
}
/**
* An additional flow step for `Regex`.
*/
class RegexRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
this.setsParseModeEdge(nodeFrom, nodeTo, _, _)
}
override predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet) {
this.setsParseModeEdge(_, node, mode, isSet)
}
private predicate setsParseModeEdge(
DataFlow::Node nodeFrom, DataFlow::Node nodeTo, RegexParseMode mode, boolean isSet
) {
// `Regex` methods that modify the parse mode of an existing `Regex` object.
exists(CallExpr ce |
nodeFrom.asExpr() = ce.getQualifier() and
nodeTo.asExpr() = ce and
// decode the parse mode being set
(
ce.getStaticTarget().(Method).hasQualifiedName("Regex", "ignoresCase(_:)") and
mode = MkIgnoreCase()
or
ce.getStaticTarget().(Method).hasQualifiedName("Regex", "dotMatchesNewlines(_:)") and
mode = MkDotAll()
or
ce.getStaticTarget().(Method).hasQualifiedName("Regex", "anchorsMatchLineEndings(_:)") and
mode = MkMultiLine()
) and
// decode the value being set
if ce.getArgument(0).getExpr().(BooleanLiteralExpr).getValue() = false
then isSet = false // mode is set to false
else isSet = true // mode is set to true OR mode is set to default (=true) OR mode is set to an unknown value
)
}
}
/**
* An additional flow step for `NSRegularExpression`.
*/
class NSRegularExpressionRegexAdditionalFlowStep extends RegexAdditionalFlowStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
override predicate setsParseMode(DataFlow::Node node, RegexParseMode mode, boolean isSet) {
// `NSRegularExpression.Options` values (these are typically combined, then passed into
// the `NSRegularExpression` initializer).
node.asExpr()
.(MemberRefExpr)
.getMember()
.(FieldDecl)
.hasQualifiedName("NSRegularExpression.Options", "caseInsensitive") and
mode = MkIgnoreCase() and
isSet = true
or
node.asExpr()
.(MemberRefExpr)
.getMember()
.(FieldDecl)
.hasQualifiedName("NSRegularExpression.Options", "allowCommentsAndWhitespace") and
mode = MkVerbose() and
isSet = true
or
node.asExpr()
.(MemberRefExpr)
.getMember()
.(FieldDecl)
.hasQualifiedName("NSRegularExpression.Options", "dotMatchesLineSeparators") and
mode = MkDotAll() and
isSet = true
or
node.asExpr()
.(MemberRefExpr)
.getMember()
.(FieldDecl)
.hasQualifiedName("NSRegularExpression.Options", "anchorsMatchLines") and
mode = MkMultiLine() and
isSet = true
or
node.asExpr()
.(MemberRefExpr)
.getMember()
.(FieldDecl)
.hasQualifiedName("NSRegularExpression.Options", "useUnicodeWordBoundaries") and
mode = MkUnicode() and
isSet = true
}
}
/**
* A call that evaluates a regular expression. For example, the call to `firstMatch` in:
* ```
@@ -91,6 +254,19 @@ abstract class RegexEval extends CallExpr {
RegexUseFlow::flow(regexCreation, DataFlow::exprNode(this.getRegexInput()))
)
}
/**
* Gets a parse mode that is set at this evaluation (in at least one path
* from the creation of the regular expression object).
*/
RegexParseMode getAParseMode() {
exists(DataFlow::Node setNode |
// parse mode flag is set
any(RegexAdditionalFlowStep s).setsParseMode(setNode, result, true) and
// reaches this eval
RegexParseModeFlow::flow(setNode, DataFlow::exprNode(this.getRegexInput()))
)
}
}
/**

View File

@@ -6,6 +6,8 @@
*/
import swift
private import RegexTracking
private import codeql.swift.regex.Regex
/**
* A `Expr` containing a regular expression term, that is, either
@@ -317,14 +319,24 @@ abstract class RegExp extends Expr {
}
/**
* Gets a mode (if any) of this regular expression. Can be any of:
* Gets a mode (if any) of this regular expression in any evaluation. Can be
* any of:
* IGNORECASE
* VERBOSE
* DOTALL
* MULTILINE
* UNICODE
*/
string getAMode() { result = this.getModeFromPrefix() }
string getAMode() {
// mode flags from inside the regex string
result = this.getModeFromPrefix()
or
// mode flags applied to the regex object before evaluation
exists(RegexEval e |
e.getARegex() = this and
result = e.getAParseMode().getName()
)
}
/**
* Holds if the `i`th character could not be parsed.

View File

@@ -6,7 +6,7 @@
import swift
import codeql.swift.regex.RegexTreeView
private import codeql.swift.dataflow.DataFlow
import codeql.swift.dataflow.DataFlow
private import ParseRegex
private import codeql.swift.regex.Regex
@@ -37,7 +37,6 @@ private module RegexUseConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
// creation of the regex
node instanceof RegexCreation
// TODO: track parse mode flags.
}
predicate isSink(DataFlow::Node node) {
@@ -46,9 +45,60 @@ private module RegexUseConfig implements DataFlow::ConfigSig {
}
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// TODO: flow through regex methods that return a modified regex.
none()
any(RegexAdditionalFlowStep s).step(nodeFrom, nodeTo)
}
}
module RegexUseFlow = DataFlow::Global<RegexUseConfig>;
/**
* A data flow configuration for tracking regular expression parse mode
* flags from wherever they are created or set through to regular expression
* evaluation. The flow state encodes which parse mode flag was set.
*/
private module RegexParseModeConfig implements DataFlow::StateConfigSig {
class FlowState = RegexParseMode;
predicate isSource(DataFlow::Node node, FlowState flowstate) {
// parse mode flag is set
any(RegexAdditionalFlowStep s).setsParseMode(node, flowstate, true)
}
predicate isSink(DataFlow::Node node, FlowState flowstate) {
// evaluation of the regex
node.asExpr() = any(RegexEval eval).getRegexInput() and
exists(flowstate)
}
predicate isBarrier(DataFlow::Node node) { none() }
predicate isBarrier(DataFlow::Node node, FlowState flowstate) {
// parse mode flag is unset
any(RegexAdditionalFlowStep s).setsParseMode(node, flowstate, false)
}
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// flow through array construction
exists(ArrayExpr arr |
nodeFrom.asExpr() = arr.getAnElement() and
nodeTo.asExpr() = arr
)
or
// flow through regex creation
exists(RegexCreation create |
nodeFrom = create.getOptionsInput() and
nodeTo = create
)
or
// additional flow steps for regular expression objects
any(RegexAdditionalFlowStep s).step(nodeFrom, nodeTo)
}
predicate isAdditionalFlowStep(
DataFlow::Node nodeFrom, FlowState flowstateFrom, DataFlow::Node nodeTo, FlowState flowStateTo
) {
none()
}
}
module RegexParseModeFlow = DataFlow::GlobalWithState<RegexParseModeConfig>;

View File

@@ -6327,84 +6327,72 @@ redos_variants.swift:
# 579| [RegExpConstant, RegExpNormalChar] c
regex.swift:
# 110| [RegExpDot] .
# 111| [RegExpDot] .
# 110| [RegExpStar] .*
# 111| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 132| [RegExpDot] .
# 133| [RegExpDot] .
# 132| [RegExpStar] .*
# 133| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 149| [RegExpDot] .
# 150| [RegExpDot] .
# 149| [RegExpStar] .*
# 150| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 149| [RegExpDot] .
# 150| [RegExpDot] .
# 149| [RegExpPlus] .+
# 150| [RegExpPlus] .+
#-----| 0 -> [RegExpDot] .
# 156| [RegExpGroup] ([\w.]+)
# 157| [RegExpGroup] ([\w.]+)
#-----| 0 -> [RegExpPlus] [\w.]+
# 156| [RegExpStar] ([\w.]+)*
# 157| [RegExpStar] ([\w.]+)*
#-----| 0 -> [RegExpGroup] ([\w.]+)
# 156| [RegExpCharacterClass] [\w.]
# 157| [RegExpCharacterClass] [\w.]
#-----| 0 -> [RegExpCharacterClassEscape] \w
#-----| 1 -> [RegExpConstant, RegExpNormalChar] .
# 156| [RegExpPlus] [\w.]+
# 157| [RegExpPlus] [\w.]+
#-----| 0 -> [RegExpCharacterClass] [\w.]
# 156| [RegExpCharacterClassEscape] \w
# 157| [RegExpCharacterClassEscape] \w
# 156| [RegExpConstant, RegExpNormalChar] .
# 157| [RegExpConstant, RegExpNormalChar] .
# 163| [RegExpConstant, RegExpNormalChar]
# 163|
# 164| [RegExpConstant, RegExpEscape] \n
# 164| [RegExpConstant, RegExpNormalChar]
# 164|
# 165| [RegExpConstant, RegExpEscape] \n
# 175| [RegExpConstant, RegExpNormalChar] aa
# 166| [RegExpConstant, RegExpEscape] \n
# 175| [RegExpAlt] aa|bb
# 176| [RegExpConstant, RegExpNormalChar] aa
# 176| [RegExpAlt] aa|bb
#-----| 0 -> [RegExpConstant, RegExpNormalChar] aa
#-----| 1 -> [RegExpConstant, RegExpNormalChar] bb
# 175| [RegExpConstant, RegExpNormalChar] bb
# 176| [RegExpConstant, RegExpNormalChar] bb
# 179| [RegExpConstant, RegExpNormalChar] aa
# 180| [RegExpConstant, RegExpNormalChar] aa
# 179| [RegExpAlt] aa|
# 179| bb
# 180| [RegExpAlt] aa|
# 180| bb
#-----| 0 -> [RegExpConstant, RegExpNormalChar] aa
#-----| 1 -> [RegExpConstant, RegExpNormalChar]
#-----| bb
# 179| [RegExpConstant, RegExpNormalChar]
# 179| bb
# 180| [RegExpConstant, RegExpNormalChar]
# 180| bb
# 187| [RegExpCharacterClass] [a-z]
# 188| [RegExpCharacterClass] [a-z]
#-----| 0 -> [RegExpCharacterRange] a-z
# 187| [RegExpConstant, RegExpNormalChar] a
# 187| [RegExpCharacterRange] a-z
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
#-----| 1 -> [RegExpConstant, RegExpNormalChar] z
# 187| [RegExpConstant, RegExpNormalChar] z
# 188| [RegExpCharacterClass] [a-zA-Z]
#-----| 0 -> [RegExpCharacterRange] a-z
#-----| 1 -> [RegExpCharacterRange] A-Z
# 188| [RegExpConstant, RegExpNormalChar] a
# 188| [RegExpCharacterRange] a-z
@@ -6413,189 +6401,208 @@ regex.swift:
# 188| [RegExpConstant, RegExpNormalChar] z
# 188| [RegExpConstant, RegExpNormalChar] A
# 189| [RegExpCharacterClass] [a-zA-Z]
#-----| 0 -> [RegExpCharacterRange] a-z
#-----| 1 -> [RegExpCharacterRange] A-Z
# 188| [RegExpCharacterRange] A-Z
# 189| [RegExpConstant, RegExpNormalChar] a
# 189| [RegExpCharacterRange] a-z
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
#-----| 1 -> [RegExpConstant, RegExpNormalChar] z
# 189| [RegExpConstant, RegExpNormalChar] z
# 189| [RegExpConstant, RegExpNormalChar] A
# 189| [RegExpCharacterRange] A-Z
#-----| 0 -> [RegExpConstant, RegExpNormalChar] A
#-----| 1 -> [RegExpConstant, RegExpNormalChar] Z
# 188| [RegExpConstant, RegExpNormalChar] Z
# 189| [RegExpConstant, RegExpNormalChar] Z
# 191| [RegExpCharacterClass] [a-]
# 192| [RegExpCharacterClass] [a-]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
#-----| 1 -> [RegExpConstant, RegExpNormalChar] -
# 191| [RegExpConstant, RegExpNormalChar] a
# 191| [RegExpConstant, RegExpNormalChar] -
# 192| [RegExpCharacterClass] [-a]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] -
#-----| 1 -> [RegExpConstant, RegExpNormalChar] a
# 192| [RegExpConstant, RegExpNormalChar] a
# 192| [RegExpConstant, RegExpNormalChar] -
# 192| [RegExpConstant, RegExpNormalChar] a
# 193| [RegExpCharacterClass] [-]
# 193| [RegExpCharacterClass] [-a]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] -
#-----| 1 -> [RegExpConstant, RegExpNormalChar] a
# 193| [RegExpConstant, RegExpNormalChar] -
# 194| [RegExpCharacterClass] [*]
# 193| [RegExpConstant, RegExpNormalChar] a
# 194| [RegExpCharacterClass] [-]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] -
# 194| [RegExpConstant, RegExpNormalChar] -
# 195| [RegExpCharacterClass] [*]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] *
# 194| [RegExpConstant, RegExpNormalChar] *
# 195| [RegExpConstant, RegExpNormalChar] *
# 195| [RegExpCharacterClass] [^a]
# 196| [RegExpCharacterClass] [^a]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
# 195| [RegExpConstant, RegExpNormalChar] a
# 196| [RegExpCharacterClass] [a^]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
#-----| 1 -> [RegExpConstant, RegExpNormalChar] ^
# 196| [RegExpConstant, RegExpNormalChar] a
# 196| [RegExpConstant, RegExpNormalChar] ^
# 197| [RegExpCharacterClass] [a^]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] a
#-----| 1 -> [RegExpConstant, RegExpNormalChar] ^
# 197| [RegExpCharacterClass] [\\]
# 197| [RegExpConstant, RegExpNormalChar] a
# 197| [RegExpConstant, RegExpNormalChar] ^
# 198| [RegExpCharacterClass] [\\]
#-----| 0 -> [RegExpConstant, RegExpEscape] \\
# 197| [RegExpConstant, RegExpEscape] \\
# 198| [RegExpCharacterClass] [\\\]]
#-----| 0 -> [RegExpConstant, RegExpEscape] \\
#-----| 1 -> [RegExpConstant, RegExpEscape] \]
# 198| [RegExpConstant, RegExpEscape] \\
# 198| [RegExpConstant, RegExpEscape] \]
# 199| [RegExpCharacterClass] [\\\]]
#-----| 0 -> [RegExpConstant, RegExpEscape] \\
#-----| 1 -> [RegExpConstant, RegExpEscape] \]
# 199| [RegExpCharacterClass] [:]
# 199| [RegExpConstant, RegExpEscape] \\
# 199| [RegExpConstant, RegExpEscape] \]
# 200| [RegExpCharacterClass] [:]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] :
# 199| [RegExpConstant, RegExpNormalChar] :
# 200| [RegExpConstant, RegExpNormalChar] :
# 200| [RegExpNamedCharacterProperty] [:digit:]
# 201| [RegExpNamedCharacterProperty] [:digit:]
# 201| [RegExpNamedCharacterProperty] [:alnum:]
# 202| [RegExpNamedCharacterProperty] [:alnum:]
# 204| [RegExpCharacterClass] []a]
# 205| [RegExpCharacterClass] []a]
#-----| 0 -> [RegExpConstant, RegExpNormalChar] ]
#-----| 1 -> [RegExpConstant, RegExpNormalChar] a
# 204| [RegExpConstant, RegExpNormalChar] ]
# 205| [RegExpConstant, RegExpNormalChar] ]
# 204| [RegExpConstant, RegExpNormalChar] a
# 205| [RegExpConstant, RegExpNormalChar] a
# 205| [RegExpNamedCharacterProperty] [:aaaaa:]
# 206| [RegExpNamedCharacterProperty] [:aaaaa:]
# 209| [RegExpGroup] (?i)
# 211| [RegExpGroup] (?i)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] i
# 209| [RegExpSequence] (?i)abc
# 211| [RegExpSequence] (?i)abc
#-----| 0 -> [RegExpGroup] (?i)
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
# 209| [RegExpConstant, RegExpNormalChar] i
# 209| [RegExpConstant, RegExpNormalChar] abc
# 210| [RegExpGroup] (?s)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] s
# 210| [RegExpSequence] (?s)abc
#-----| 0 -> [RegExpGroup] (?s)
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
# 210| [RegExpConstant, RegExpNormalChar] s
# 210| [RegExpConstant, RegExpNormalChar] abc
# 211| [RegExpGroup] (?is)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] is
# 211| [RegExpSequence] (?is)abc
#-----| 0 -> [RegExpGroup] (?is)
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
# 211| [RegExpConstant, RegExpNormalChar] is
# 211| [RegExpConstant, RegExpNormalChar] i
# 211| [RegExpConstant, RegExpNormalChar] abc
# 212| [RegExpGroup] (?s)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] s
# 212| [RegExpSequence] (?s)abc
#-----| 0 -> [RegExpGroup] (?s)
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
# 212| [RegExpConstant, RegExpNormalChar] s
# 212| [RegExpConstant, RegExpNormalChar] abc
# 213| [RegExpGroup] (?is)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] is
# 213| [RegExpSequence] (?is)abc
#-----| 0 -> [RegExpGroup] (?is)
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
# 213| [RegExpConstant, RegExpNormalChar] is
# 213| [RegExpConstant, RegExpNormalChar] abc
# 214| [RegExpConstant, RegExpNormalChar] abc
# 215| [RegExpConstant, RegExpNormalChar] abc
# 216| [RegExpConstant, RegExpNormalChar] abc
# 217| [RegExpConstant, RegExpNormalChar] abc
# 219| [RegExpDot] .
# 219| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 220| [RegExpDot] .
# 220| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 221| [RegExpDot] .
# 221| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 227| [RegExpGroup] (?i-s)
# 214| [RegExpGroup] (?i-s)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] i-s
# 227| [RegExpSequence] (?i-s)abc
# 214| [RegExpSequence] (?i-s)abc
#-----| 0 -> [RegExpGroup] (?i-s)
#-----| 1 -> [RegExpConstant, RegExpNormalChar] abc
# 227| [RegExpConstant, RegExpNormalChar] i-s
# 214| [RegExpConstant, RegExpNormalChar] i-s
# 227| [RegExpConstant, RegExpNormalChar] abc
# 214| [RegExpConstant, RegExpNormalChar] abc
# 230| [RegExpConstant, RegExpNormalChar] abc
# 217| [RegExpConstant, RegExpNormalChar] abc
# 230| [RegExpSequence] abc(?i)def
# 217| [RegExpSequence] abc(?i)def
#-----| 0 -> [RegExpConstant, RegExpNormalChar] abc
#-----| 1 -> [RegExpGroup] (?i)
#-----| 2 -> [RegExpConstant, RegExpNormalChar] def
# 230| [RegExpGroup] (?i)
# 217| [RegExpGroup] (?i)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] i
# 230| [RegExpConstant, RegExpNormalChar] i
# 217| [RegExpConstant, RegExpNormalChar] i
# 230| [RegExpConstant, RegExpNormalChar] def
# 217| [RegExpConstant, RegExpNormalChar] def
# 231| [RegExpConstant, RegExpNormalChar] abc
# 218| [RegExpConstant, RegExpNormalChar] abc
# 231| [RegExpSequence] abc(?i:def)ghi
# 218| [RegExpSequence] abc(?i:def)ghi
#-----| 0 -> [RegExpConstant, RegExpNormalChar] abc
#-----| 1 -> [RegExpGroup] (?i:def)
#-----| 2 -> [RegExpConstant, RegExpNormalChar] ghi
# 231| [RegExpGroup] (?i:def)
# 218| [RegExpGroup] (?i:def)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] i:def
# 231| [RegExpConstant, RegExpNormalChar] i:def
# 218| [RegExpConstant, RegExpNormalChar] i:def
# 231| [RegExpConstant, RegExpNormalChar] ghi
# 218| [RegExpConstant, RegExpNormalChar] ghi
# 232| [RegExpGroup] (?i)
# 219| [RegExpGroup] (?i)
#-----| 0 -> [RegExpConstant, RegExpNormalChar] i
# 232| [RegExpConstant, RegExpNormalChar] i
# 219| [RegExpConstant, RegExpNormalChar] i
# 232| [RegExpConstant, RegExpNormalChar] abc
# 219| [RegExpConstant, RegExpNormalChar] abc
# 232| [RegExpConstant, RegExpNormalChar] -i
# 219| [RegExpConstant, RegExpNormalChar] -i
# 232| [RegExpConstant, RegExpNormalChar] def
# 219| [RegExpConstant, RegExpNormalChar] def
# 222| [RegExpConstant, RegExpNormalChar] abc
# 223| [RegExpConstant, RegExpNormalChar] abc
# 224| [RegExpConstant, RegExpNormalChar] abc
# 225| [RegExpConstant, RegExpNormalChar] abc
# 226| [RegExpConstant, RegExpNormalChar] abc
# 227| [RegExpConstant, RegExpNormalChar] abc
# 230| [RegExpDot] .
# 230| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 231| [RegExpDot] .
# 231| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 232| [RegExpDot] .
# 232| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .
# 235| [RegExpDot] .
# 235| [RegExpStar] .*
#-----| 0 -> [RegExpDot] .

View File

@@ -17,8 +17,9 @@ struct Regex<Output> : RegexComponent {
init(_ pattern: String) throws where Output == AnyRegexOutput { }
func ignoresCase(_ ignoresCase: Bool = true) -> Regex<Regex<Output>.RegexOutput> { return self }
func dotMatchesNewlines(_ dotMatchesNewlines: Bool = true) -> Regex<Regex<Output>.RegexOutput> { return self }
func ignoresCase(_ ignoresCase: Bool = true) -> Regex<Regex<Output>.RegexOutput> { return 0 as! Self }
func dotMatchesNewlines(_ dotMatchesNewlines: Bool = true) -> Regex<Regex<Output>.RegexOutput> { return 0 as! Self }
func anchorsMatchLineEndings(_ matchLineEndings: Bool = true) -> Regex<Regex<Output>.RegexOutput> { return 0 as! Self }
func firstMatch(in string: String) throws -> Regex<Output>.Match? { return nil }
func prefixMatch(in string: String) throws -> Regex<Output>.Match? { return nil }
@@ -206,28 +207,38 @@ func myRegexpMethodsTests(b: Bool, str_unknown: String) throws {
// --- parse modes ---
// parse modes encoded in the string
_ = try Regex("(?i)abc").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=(?i)abc
_ = try Regex("(?s)abc").firstMatch(in: input) // $ input=input modes=DOTALL regex=(?s)abc
_ = try Regex("(?is)abc").firstMatch(in: input) // $ input=input modes="DOTALL | IGNORECASE" regex=(?is)abc
_ = try Regex("abc").dotMatchesNewlines(true).firstMatch(in: input) // $ input=input regex=abc MISSING: modes=DOTALL
_ = try Regex("abc").dotMatchesNewlines(false).firstMatch(in: input) // $ input=input regex=abc
_ = try Regex("abc").dotMatchesNewlines(true).dotMatchesNewlines(false).firstMatch(in: input) // $ input=input regex=abc
_ = try Regex("abc").dotMatchesNewlines(false).dotMatchesNewlines(true).firstMatch(in: input) // $ input=input regex=abc MISSING: modes=DOTALL
_ = try Regex("abc").dotMatchesNewlines().ignoresCase().firstMatch(in: input) // $ input=input regex=abc MISSING: modes="DOTALL | IGNORECASE"
_ = try NSRegularExpression(pattern: ".*", options: .caseInsensitive).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input MISSING: modes=IGNORECASE
_ = try NSRegularExpression(pattern: ".*", options: .dotMatchesLineSeparators).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input MISSING: modes=DOTALL
_ = try NSRegularExpression(pattern: ".*", options: [.caseInsensitive, .dotMatchesLineSeparators]).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input MISSING: modes="DOTALL | IGNORECASE"
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ MISSING: regex=.* input=input modes=IGNORECASE
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS modes=IGNORECASE
_ = try Regex("(?i-s)abc").firstMatch(in: input) // $ input=input regex=(?i-s)abc MISSING: modes=IGNORECASE SPURIOUS: modes="DOTALL | IGNORECASE"
// these cases use parse modes on localized areas of the regex, which we don't currently support
_ = try Regex("abc(?i)def").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=abc(?i)def
_ = try Regex("abc(?i:def)ghi").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=abc(?i:def)ghi
_ = try Regex("(?i)abc(?-i)def").firstMatch(in: input) // $ input=input modes=IGNORECASE regex=(?i)abc(?-i)def SPURIOUS: hasParseFailure=
// parse modes set through Regex
_ = try Regex("abc").dotMatchesNewlines(true).firstMatch(in: input) // $ input=input regex=abc modes=DOTALL
_ = try Regex("abc").dotMatchesNewlines(false).firstMatch(in: input) // $ input=input regex=abc
_ = try Regex("abc").dotMatchesNewlines(true).dotMatchesNewlines(false).firstMatch(in: input) // $ input=input regex=abc
_ = try Regex("abc").dotMatchesNewlines(false).dotMatchesNewlines(true).firstMatch(in: input) // $ input=input regex=abc modes=DOTALL
_ = try Regex("abc").dotMatchesNewlines().ignoresCase().firstMatch(in: input) // $ input=input regex=abc modes="DOTALL | IGNORECASE"
_ = try Regex("abc").anchorsMatchLineEndings().firstMatch(in: input) // $ input=input regex=abc modes=MULTILINE
// parse modes set through NSRegularExpression
_ = try NSRegularExpression(pattern: ".*", options: .caseInsensitive).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes=IGNORECASE
_ = try NSRegularExpression(pattern: ".*", options: .dotMatchesLineSeparators).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes=DOTALL
_ = try NSRegularExpression(pattern: ".*", options: [.caseInsensitive, .dotMatchesLineSeparators]).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes="DOTALL | IGNORECASE"
let myOptions1 : NSRegularExpression.Options = [.caseInsensitive, .dotMatchesLineSeparators]
_ = try NSRegularExpression(pattern: ".*", options: myOptions1).firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input modes="DOTALL | IGNORECASE"
// parse modes set through other methods
let myOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive]
_ = input.replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive]) // $ MISSING: regex=.* input=input modes=IGNORECASE
_ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ MISSING: regex=.* input=input modes=IGNORECASE
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS modes=IGNORECASE
_ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ MISSING: regex=.* input=inputNS modes=IGNORECASE
}