![](/img/trans.png)
[英]How can I make the regular expression not result in “catastrophic backtracking”?
[英]How can I make this regular expression not result in “catastrophic backtracking”?
我正在尝试使用我从http://daringfireball.net/2010/07/improved_regex_for_matching_urls获得的匹配正则表达式的URL
(?xi)
\b
( # Capture 1: entire matched URL
(?:
https?:// # http or https protocol
| # or
www\d{0,3}[.] # "www.", "www1.", "www2." … "www999."
| # or
[a-z0-9.\-]+[.][a-z]{2,4}/ # looks like domain name followed by a slash
)
(?: # One or more:
[^\s()<>]+ # Run of non-space, non-()<>
| # or
\(([^\s()<>]+|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels
)+
(?: # End with:
\(([^\s()<>]+|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels
| # or
[^\s`!()\[\]{};:'".,<>?«»“”‘’] # not a space or one of these punct chars
)
)
根据另一个问题的答案,似乎有些案例导致这个正则表达式灾难性地回溯 。 例如:
var re = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i;
re.test("http://google.com/?q=(AAAAAAAAAAAAAAAAAAAAAAAAAAAAA)")
...可能需要很长时间才能执行(例如在Chrome中)
在我看来,问题在于这部分代码:
(?: # One or more:
[^\s()<>]+ # Run of non-space, non-()<>
| # or
\(([^\s()<>]+|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels
)+
......似乎大致相当于(.+|\\((.+|(\\(.+\\)))*\\))+
,看起来它包含(.+)+
我可以做出改变以避免这种情况吗?
将其更改为以下内容可以防止灾难性的回溯:
(?xi)
\b
( # Capture 1: entire matched URL
(?:
https?:// # http or https protocol
| # or
www\d{0,3}[.] # "www.", "www1.", "www2." … "www999."
| # or
[a-z0-9.\-]+[.][a-z]{2,4}/ # looks like domain name followed by a slash
)
(?: # One or more:
[^\s()<>]+ # Run of non-space, non-()<>
| # or
\(([^\s()<>]|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels
)+
(?: # End with:
\(([^\s()<>]|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels
| # or
[^\s`!()\[\]{};:'".,<>?«»“”‘’] # not a space or one of these punct chars
)
)
唯一的变化是在正则表达式的每个“平衡的parens”部分中删除第一个[^\\s()<>]
之后的+
。
以下是使用JS进行测试的单行版本:
var re = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i;
re.test("http://google.com/?q=(AAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
原始正则表达式的问题部分是平衡括号部分,以简化回溯发生原因的解释我将完全删除它的嵌套括号部分,因为它在这里不相关:
\(([^\s()<>]+|(\([^\s()<>]+\)))*\) # original
\(([^\s()<>]+)*\) # expanded below
\( # literal '('
( # start group, repeat zero or more times
[^\s()<>]+ # one or more non-special characters
)* # end group
\) # literal ')'
考虑一下这里发生的字符串'(AAAAA'
,文字(
将匹配然后AAAAA
将由该组消费,并且)
将无法匹配。此时该组将放弃一个A
,留下AAAA
捕获并且此时尝试继续比赛。由于该组有一个*
跟随它,该组可以匹配多次,所以现在你可以([^\\s()<>]+)*
匹配AAAA
,然后A
第二次通过。当这次失败时,额外的A
将被原始捕获放弃并被第二次捕获消耗。
这将持续很长时间,导致以下尝试匹配,其中每个逗号分隔的组指示组匹配的不同时间,以及实例匹配的字符数:
AAAAA
AAAA, A
AAA, AA
AAA, A, A
AA, AAA
AA, AA, A
AA, A, AA
AA, A, A, A
....
我可能算错了,但我确定它在确定正则表达式无法匹配之前最多可增加16个步骤。 当您继续向字符串添加其他字符时,计算出来的步骤数呈指数增长。
通过删除+
并将其更改为\\(([^\\s()<>])*\\)
,您将避免此回溯方案。
重新添加交替以检查嵌套括号不会导致任何问题。
请注意,您可能希望在字符串的末尾添加某种锚点,因为目前"http://google.com/?q=(AAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
将匹配到(
因此re.test(...)
之前re.test(...)
因为http://google.com/?q=
匹配而返回true
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.