【问题标题】:How to avoid infinite loops in the .NET RegEx class?如何避免 .NET RegEx 类中的无限循环?
【发布时间】:2010-11-15 02:22:28
【问题描述】:

有一个简单的任务来获取 XPath 表达式并返回与(可能)选择的节点的父节点匹配的前缀。

例子:

/aaa/bbb       =>   /aaa
/aaa/bbb/ccc   =>   /aaa/bbb
/aaa/bbb/ccc[@x='1' and @y="/aaa[name='z']"] => /aaa/bbb

因为方括号内的模式可能包含引号内的括号,所以我决定尝试使用正则表达式来实现这一点。这是一个代码sn-p:

string input =
    "/aaa/bbb/ccc[@x='1' and @y=\"/aaa[name='z'] \"]";
                                            //  ^-- remove space for no loop
string pattern = @"/[a-zA-Z0-9]+(\[([^]]*(]"")?)+])?$";

System.Text.RegularExpressions.Regex re =
    new System.Text.RegularExpressions.Regex(pattern);
bool ismatch = re.IsMatch(input); // <== Infinite loop in here
// some code based on the match

因为模式相当规则,我寻找'/'后跟标识符,后跟一个匹配字符串末尾的可选组 (....)?$

该代码似乎可以工作,但使用不同的输入字符串值,我发现只需插入一个空格(在注释中显示的位置),.NET IsMatch 函数就会进入无限循环,将所有它得到的 CPU。

现在不管这个正则表达式模式是否是最好的(我有更复杂但简化了它以显示问题),这似乎表明使用正则表达式和任何不平凡的东西可能是非常危险的。

我错过了什么吗?有没有办法防止正则表达式匹配中的无限循环?

【问题讨论】:

  • 一般来说,这不就相当于停机问题吗?

标签: .net regex infinite-loop


【解决方案1】:

好的,那么让我们分解一下:

Input: /aaa/bbb/ccc[@x='1' and @y="/aaa[name='z'] "]
Pattern: /[a-zA-Z0-9]+(\[([^]]*(]")?)+])?$

(我假设您的 C# 转义字符串中的意思是 \",而不是 ""... 从 VB.NET 翻译?)

首先,/[a-zA-Z0-9]+ 将吞噬第一个方括号,离开:

Input: [@x='1' and @y="/aaa[name='z'] "]

外部组 (\[([^]]*(]"")?)+])?$" 如果在 EOL 之前有 0 或 1 个实例,则应该匹配。所以让我们打破内部,看看它是否匹配任何东西。

“[”立即被吞噬,留下:

Input: @x='1' and @y="/aaa[name='z'] "]
Pattern: ([^]]*(]")?)+]

分解模式:匹配 0 个或多个非] 字符,然后匹配 "] 0 或 1 次,一直这样做直到你不能。然后尝试找到并吞下一个]

模式匹配基于 [^]]* 直到到达 ]

由于 ]" 之间有一个空格,它不能吞噬这两个字符中的任何一个,但是 之后的 ? (]") 无论如何都允许它返回 true。

现在我们已经成功匹配 ([^]]*(]")?) 一次,但是 + 表示我们应该尝试继续匹配任何数字我们可以做到的次数。

这给我们留下了:

Input: ] "]

这里的问题是这个输入可以匹配 ([^]]*(]")?) 无限次而不会被吞噬,并且“ +" 将迫使它继续尝试。

您实际上是在匹配“1 个或多个”情况,您可以匹配“0 或 1”的某事物,然后是“0 或 1”的其他事物。由于剩余输入中不存在这两个子模式,因此它会在无限循环中不断匹配 [^]]\* 的 0 和 (]")? 的 0。

输入永远不会被吞噬,“+”之后的其余模式永远不会被评估。

(希望我在上面得到了 SO-escape-of-regex-escape。)

【讨论】:

  • 嗯,这很有成效(对我来说)-谢谢理查德。我得出的结论是: 1. 从外部源获取正则表达式模式是危险的,并且可以轻松地处理应用程序 2. .NET 中的正则表达式不会检测到无限循环,也没有提供限制处理的方法 3. 不同的正则表达式引擎可以给出不同的结果,因此即使语法相同,某些语义也可能不同(可移植性说明)谢谢。
  • 我认为您看到的差异是由于正则表达式的不同方言,而不是其他引擎中花哨的无限循环检测。核心问题是包装可以无限次匹配 empty text 的内容。如果输入正确,任何 (x?)+ 或 (x?)* 的变体都可能是危险的。重构你的模式应该能让你得到你需要的东西,而不会产生无限循环的可能性。不管是哪种语言,教训是始终对任意用户输入进行防御性编程。
  • 这里的问题是这个输入可以匹配 ([^]]*(]")?) 无限次而不会被吞噬,而“+”将迫使它继续尝试. 为什么????
  • 我自己遇到了一个复杂的模式。当然让我不想再使用正则表达式。你有一些东西里面可能有地雷,而不是因为一个错误。对于足够复杂的模式,您永远无法确定地雷是否会袭击。
【解决方案2】:

要回答最初的问题(即如何使用正则表达式避免无限循环),这在 .Net 4.5 中变得很容易,因为您可以简单地将超时传递给正则表达式方法。有一个内部计时器会在超时到期时停止正则表达式循环并引发 RegexMatchTimeoutException

例如,您将执行以下操作

string input = "/aaa/bbb/ccc[@x='1' and @y=\"/aaa[name='z'] \"]";
string pattern = @"/[a-zA-Z0-9]+(\[([^]]*(]"")?)+])?$";
bool ismatch = Regex.IsMatch(input, pattern, RegexOptions.None, TimeSpan.FromSeconds(5));

您可以查看MSDN了解更多详情

【讨论】:

  • 谢谢! - 这当然是有用且相关的信息,但最初的意图仍然是了解如何避免这种情况) - 很高兴知道 5 秒后它会引发异常,但最好是正确地开始编写...
【解决方案3】:

这里的问题是这个输入可以匹配 ([^]]*(]")?) 无限次而不会被吞噬,而“+”会迫使它继续尝试。

这是 .NET 的 RegEx 实现中的一大错误。正则表达式不能那样工作。当你把它们变成自动机时,你会自动得到这样一个事实,一个空字符串的无限重复仍然是一个空字符串。

换句话说,任何没有错误的正则表达式引擎都会立即执行这个无限循环并继续执行其余的正则表达式。

如果您愿意,正则表达式是一种有限的语言,可以(并且很容易)检测和避免这种无限循环。

【讨论】:

    【解决方案4】:

    这表明将 code 与任何不重要的东西一起使用可能是有风险的。您创建了可能导致无限循环的代码,RegEx 编译器必须这样做。自从前 20 个 IF X=0 THEN GOTO 10 以来,没有什么新的事情没有完成。

    如果您在特定的边缘情况下担心这一点,您可以为 RegEx 生成一个线程,然后在一段合理的执行时间后将其终止。

    【讨论】:

    • 我发现这个答案很有成效。我尝试过的其他 RegEx 引擎没有陷入无限循环(例如,尝试regular-expressions.info/javascriptexample.html 的在线 JavaScript RegEx 测试器,您会发现它工作得很好)。这个正则表达式很简单,我认为它的 expected 失败模式(当没有找到匹配项时)是一个无限循环并不简单。线程的想法也没有用。我应该在提供外部 RegEx 的任何地方使用这个想法吗?我不这么认为。我认为这可能是 RegEx 中的一个错误(或者是一个巨大的漏洞)。
    猜你喜欢
    • 2019-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-23
    相关资源
    最近更新 更多