当给出长而完整的字符串时,C#dotnetcore正则表达式将挂起

时间:2019-07-01 15:49:36

标签: c# regex .net-core

当它试图与一个长而完整的字符串匹配时,我有一个正则表达式挂起。这是一个示例控制台应用程序:

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example
{
    public static void Main()
    {
        Stopwatch sw;
        string pattern = @"(?:(?:https?|ftps?):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?";
        string input = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

        Console.WriteLine("Press any key to match regex.");
        Console.ReadKey();
        Console.WriteLine("Starting regex...");

        sw = Stopwatch.StartNew();
        Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
        sw.Stop();


        Console.WriteLine($"Regex completed in {sw.Elapsed}. Press any key to exit.");
        Console.ReadKey();
    }
}

正则表达式用于在用户生成的注释中查找URL。提供普通评论后,它将立即处理。一个100字的lorem ipsum大约需要36ms的处理时间。一旦引入了一个长而完整的字符串,正则表达式就会挂起,据我所知,它永远不会完成处理。该字符串不需要重复相同的字符。

任何帮助或见识将不胜感激。

1 个答案:

答案 0 :(得分:3)

您的正则表达式的主要问题是,在* / +量化组中有一些可选模式以及强制性一种模式,请参见(?:[a-z\u00a1-\uffff0-9]+-?)*。当正则表达式引擎开始尝试所有可能的路由来匹配字符串时,这可能导致(带有长的不匹配字符串)行为,并且可能会出现太多的路由,以至于系统似乎死机了:灾难性的回溯

因此,如果您打算使用简化的解决方案,则应避免使用类似的模式,请使用

(?:(?:https?|ftp)://)(?:-\.)?[^\s/?.#-]+(?:\.[^\s/?.#-]+)*(?:/\S*)?

其中(?:[^\s/?\.#-]+\.?)+被展开为[^\s/?.#-]+(?:\.[^\s/?.#-]+)*。虽然时间更长,但是与可选模式位于量化组内部相比,引擎的故障要快得多。

如果要修复原始图案,请使用

string pattern = @"(?:(?:http|ftp)s?://)?(?:\S+(?::\S*)?@)?(?:(?!1(?:0|27)(?:\.\d{1,3}){3})(?!1(?:69\.254|92\.168|72\.(?:1[6-9]|2\d|3[0-1]))(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:[a-z\u00a1-\uffff0-9]+(?:-[a-z\u00a1-\uffff0-9]+)*)(?:\.[a-z\u00a1-\uffff0-9]+(?:-[a-z\u00a1-\uffff0-9]+)*)*(?:\.[a-z\u00a1-\uffff]{2,}))(?::\d{2,5})?(?:/\S*)?";

检查this regex matches以及如何(?:[a-z\u00a1-\uffff0-9]+-?)*作为[a-z\u00a1-\uffff0-9]+(?:-[a-z\u00a1-\uffff0-9]+)*展开以匹配模式,以便每个后续模式都不能匹配相同的字符。我还合并了一些负面的前瞻性和常见的“后缀”。请注意,(?:\S+(?::\S*)?@)?保持不变,因为它可能需要匹配所有:直到其余匹配模式之前的最后: