// --- stubs --- struct Locale { } struct AnyRegexOutput { } protocol RegexComponent { associatedtype RegexOutput } struct Regex : RegexComponent { struct Match { } init(_ pattern: String) throws where Output == AnyRegexOutput { } func ignoresCase(_ ignoresCase: Bool = true) -> Regex.RegexOutput> { return 0 as! Self } func dotMatchesNewlines(_ dotMatchesNewlines: Bool = true) -> Regex.RegexOutput> { return 0 as! Self } func anchorsMatchLineEndings(_ matchLineEndings: Bool = true) -> Regex.RegexOutput> { return 0 as! Self } func firstMatch(in string: String) throws -> Regex.Match? { return nil } func prefixMatch(in string: String) throws -> Regex.Match? { return nil } func wholeMatch(in string: String) throws -> Regex.Match? { return nil } typealias RegexOutput = Output } extension RangeReplaceableCollection { mutating func replace(_ regex: some RegexComponent, with replacement: Replacement, maxReplacements: Int = .max) where Replacement : Collection, Replacement.Element == Character { } func replacing(_ regex: some RegexComponent, with replacement: Replacement, maxReplacements: Int = .max) -> Self where Replacement: Collection, Replacement.Element == Character { return self } mutating func trimPrefix(_ regex: some RegexComponent) { } } extension StringProtocol { func range(of aString: T, options mask:String.CompareOptions = [], range searchRange: Range? = nil, locale: Locale? = nil) -> Range? where T : StringProtocol { return nil } func replacingOccurrences(of target: Target, with replacement: Replacement, options: String.CompareOptions = [], range searchRange: Range? = nil) -> String where Target : StringProtocol, Replacement : StringProtocol { return "" } } extension String : RegexComponent { typealias CompareOptions = NSString.CompareOptions typealias Output = Substring typealias RegexOutput = String.Output } class NSObject { } class NSString : NSObject { struct CompareOptions : OptionSet { var rawValue: UInt static var regularExpression: NSString.CompareOptions { get { return CompareOptions(rawValue: 1) } } static var caseInsensitive: NSString.CompareOptions { get { return CompareOptions(rawValue: 2) } } static var literal: NSString.CompareOptions { get { return CompareOptions(rawValue: 4) } } } convenience init(string aString: String) { self.init() } func range(of searchString: String, options mask: NSString.CompareOptions = []) -> NSRange { return NSRange(location: 0, length: 0) } func replacingOccurrences(of target: String, with replacement: String, options: NSString.CompareOptions = [], range searchRange: NSRange) -> String { return "" } var length: Int { get { return 0 } } } class NSMutableString : NSString { } struct _NSRange { init(location: Int, length: Int) { } } typealias NSRange = _NSRange func NSMakeRange(_ loc: Int, _ len: Int) -> NSRange { return NSRange(location: loc, length: len) } class NSTextCheckingResult : NSObject { } class NSRegularExpression : NSObject { struct Options : OptionSet { var rawValue: UInt static var caseInsensitive: NSRegularExpression.Options { get { return Options(rawValue: 1) } } static var dotMatchesLineSeparators: NSRegularExpression.Options { get { return Options(rawValue: 2) } } } struct MatchingOptions : OptionSet { var rawValue: UInt } init(pattern: String, options: NSRegularExpression.Options = []) throws { } // some types have been simplified a little here func numberOfMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> Int { return 0 } func enumerateMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange, using block: (Int, Int, Int) -> Void) { } func matches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> [NSTextCheckingResult] { return [] } func firstMatch(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> NSTextCheckingResult? { return nil } func rangeOfFirstMatch(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> NSRange { return NSRange(location: 0, length: 0) } func replaceMatches(in string: NSMutableString, options: NSRegularExpression.MatchingOptions = [], range: NSRange, withTemplate templ: String) -> Int { return 0 } func stringByReplacingMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange, withTemplate templ: String) -> String { return "" } } // --- tests --- // // the focus for these tests is different ways of evaluating regexps. func myRegexpMethodsTests(b: Bool, str_unknown: String) throws { let input = "abcdef" let regex = try Regex(".*") // --- Regex --- _ = try regex.firstMatch(in: input) // $ regex=.* input=input _ = try regex.prefixMatch(in: input) // $ regex=.* input=input _ = try regex.wholeMatch(in: input) // $ regex=.* input=input // --- RangeReplaceableCollection --- var inputVar = input inputVar.replace(regex, with: "") // $ regex=.* input=inputVar _ = input.replacing(regex, with: "") // $ regex=.* input=input inputVar.trimPrefix(regex) // $ regex=.* input=inputVar // --- StringProtocol --- _ = input.range(of: ".*", options: .regularExpression, range: nil, locale: nil) // $ regex=.* input=input _ = input.range(of: ".*", options: .literal, range: nil, locale: nil) // (not a regular expression) _ = input.replacingOccurrences(of: ".*", with: "", options: .regularExpression) // $ regex=.* input=input _ = input.replacingOccurrences(of: ".*", with: "", options: .literal) // (not a regular expression) // --- NSRegularExpression --- let nsregex = try NSRegularExpression(pattern: ".*") _ = nsregex.numberOfMatches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count)) // $ regex=.* input=input nsregex.enumerateMatches(in: input, range: NSMakeRange(0, input.utf16.count), using: {a, b, c in } ) // $ regex=.* input=input _ = nsregex.matches(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input _ = nsregex.firstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input _ = nsregex.rangeOfFirstMatch(in: input, range: NSMakeRange(0, input.utf16.count)) // $ regex=.* input=input _ = nsregex.replaceMatches(in: NSMutableString(string: input), range: NSMakeRange(0, input.utf16.count), withTemplate: "") // $ regex=.* input="call to NSString.init(string:)" _ = nsregex.stringByReplacingMatches(in: input, range: NSMakeRange(0, input.utf16.count), withTemplate: "") // $ regex=.* input=input // --- NSString --- let inputNS = NSString(string: "abcdef") let regexOptions = NSString.CompareOptions.regularExpression let regexOptions2 : NSString.CompareOptions = [.regularExpression, .caseInsensitive] _ = inputNS.range(of: ".*", options: .regularExpression) // $ regex=.* input=inputNS _ = inputNS.range(of: ".*", options: [.regularExpression]) // $ regex=.* input=inputNS _ = inputNS.range(of: ".*", options: regexOptions) // $ regex=.* input=inputNS _ = inputNS.range(of: ".*", options: regexOptions2) // $ regex=.* input=inputNS modes=IGNORECASE _ = inputNS.range(of: ".*", options: .literal) // (not a regular expression) _ = inputNS.replacingOccurrences(of: ".*", with: "", options: .regularExpression, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input=inputNS _ = inputNS.replacingOccurrences(of: ".*", with: "", options: .literal, range: NSMakeRange(0, inputNS.length)) // (not a regular expression) // --- flow --- let either_regex = try Regex(b ? ".*" : ".+") _ = try either_regex.firstMatch(in: input) // $ regex=.* regex=.+ input=input let base_str = "a" let appended_regex = try Regex(base_str + "b") _ = try appended_regex.firstMatch(in: input) // $ input=input MISSING: regex=ab let multiple_evaluated_regex = try Regex(#"([\w.]+)*"#) try _ = multiple_evaluated_regex.firstMatch(in: input) // $ input=input regex=([\w.]+)* try _ = multiple_evaluated_regex.prefixMatch(in: input) // $ input=input regex=([\w.]+)* try _ = multiple_evaluated_regex.wholeMatch(in: input) // $ input=input regex=([\w.]+)* MISSING: redos-vulnerable= // --- escape sequences --- _ = try Regex("\n").firstMatch(in: input) // $ regex=NEWLINE input=input _ = try Regex("\\n").firstMatch(in: input) // $ regex=\n input=input _ = try Regex(#"\n"#).firstMatch(in: input) // $ regex=\n input=input // --- interpolated values --- let str_constant = "aa" _ = try Regex("\(str_constant))|bb").firstMatch(in: input) // $ input=input MISSING: regex=aa|bb _ = try Regex("\(str_unknown))|bb").firstMatch(in: input) // $ input=input // --- multi-line --- _ = try Regex(""" aa|bb """).firstMatch(in: input) // $ input=input regex=aa|bb _ = try Regex(""" aa| bb """).firstMatch(in: input) // $ input=input regex=aa|NEWLINEbb // --- exploring parser correctness --- // ranges _ = try Regex("[a-z]").firstMatch(in: input) // $ input=input regex=[a-z] _ = try Regex("[a-zA-Z]").firstMatch(in: input) // $ input=input regex=[a-zA-Z] // character classes _ = try Regex("[a-]").firstMatch(in: input) // $ input=input regex=[a-] _ = try Regex("[-a]").firstMatch(in: input) // $ input=input regex=[-a] _ = try Regex("[-]").firstMatch(in: input) // $ input=input regex=[-] _ = try Regex("[*]").firstMatch(in: input) // $ input=input regex=[*] _ = try Regex("[^a]").firstMatch(in: input) // $ input=input regex=[^a] _ = try Regex("[a^]").firstMatch(in: input) // $ input=input regex=[a^] _ = try Regex(#"[\\]"#).firstMatch(in: input) // $ input=input regex=[\\] _ = try Regex(#"[\\\]]"#).firstMatch(in: input) // $ input=input regex=[\\\]] _ = try Regex("[:]").firstMatch(in: input) // $ input=input regex=[:] _ = try Regex("[:digit:]").firstMatch(in: input) // $ input=input regex=[:digit:] SPURIOUS: $hasParseFailure _ = try Regex("[:alnum:]").firstMatch(in: input) // $ input=input regex=[:alnum:] SPURIOUS: $hasParseFailure // invalid (Swift doesn't like these regexs) _ = try Regex("[]a]").firstMatch(in: input) // $ regex=[]a] // ^ this is valid in other regex implementations, and is likely harmless to accept _ = try Regex("[:aaaaa:]").firstMatch(in: input) // $ regex=[:aaaaa:] hasParseFailure // --- 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("(?i-s)abc").firstMatch(in: input) // $ input=input regex=(?i-s)abc modes=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]) // $ regex=.* input=input modes=IGNORECASE _ = input.replacingOccurrences(of: ".*", with: "", options: myOptions2) // $ regex=.* input=input modes=IGNORECASE _ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: [.regularExpression, .caseInsensitive], range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" modes=IGNORECASE _ = NSString(string: "abc").replacingOccurrences(of: ".*", with: "", options: myOptions2, range: NSMakeRange(0, inputNS.length)) // $ regex=.* input="call to NSString.init(string:)" modes=IGNORECASE // Regex created but never evaluated _ = try Regex(".*") // $ unevaluated-regex=.* }