Go 正则表达式中没有灾难性的回溯吗?

Posted

技术标签:

【中文标题】Go 正则表达式中没有灾难性的回溯吗?【英文标题】:Is there no catastrophic backtracking in Go regex? 【发布时间】:2021-12-19 20:24:11 【问题描述】:

今天在 regex101.com 上测试这个正则表达式时, ^([a-z0-9]+(-)*)*([a-z0-9])$ 当我在这个字符串上测试它时出现“灾难性回溯”错误:

带有风味的php

aaaaaaaaaa-aaaaT

与风味 Python:

aaaaaaaaaa-aaaaT

使用 ECMAScript 风格,这个较长的字符串会出现“可能是灾难性回溯的迹象”的超时

aaaaaaaaaaa-aaaaaaaaaaaaaaaaaT

带有风格的 Java 8 超时字符串

aaaaaaaaaaa-aaaaaaaaaaaaaT

但是风味 Go 没有给出更长的此类字符串的错误或超时事件。相反,它显示no match (0.0ms)

那么当我在 Go 中使用我的正则表达式时,我可以忽略该错误/警告吗?

我也对此原因感兴趣,但以上是我的关键问题。

【问题讨论】:

Go 的 stdlib 正则表达式引擎不支持回溯,所以灾难性的回溯是不可能的。 还有更多信息en.wikipedia.org/wiki/RE2_(software) 将其更改为^(?:[a-z0-9]-?)*[a-z0-9]$,您将不会出现任何回溯溢出。 可以证明:每个NFA(可能需要回溯(可能导致ReDoS))都可以转换为DFA(不需要回溯),这显然是sln在上面所做的并且是 Go 在后台执行的操作。 这个问题正在meta讨论。 【参考方案1】:

是的,在 Go 中使用正则表达式时可以安全地忽略灾难性回溯警告。

Go 对正则表达式使用 RE2 算法,而 RE2 不使用回溯,因此在 Go 中不会出现问题。 https://en.wikipedia.org/wiki/Regular_expression#Implementations_and_running_times 有更多关于正则表达式匹配的替代实现的信息。 Go (RE2) 对输入字符串长度和正则表达式字符串长度具有线性性能:O(mn)。

但是,使用回溯的其他语言/库可能具有指数运行时间,具体取决于正则表达式和输入字符串。 regex101.com 显示了针对输入字符串运行正则表达式的步骤数,您可以看到随着 (a*)*$aaaaaaaaaaaaaaaaX 这样的字符串增加正则表达式的字符串长度,步数呈指数增长。 regex101.com 上的调试器可以一次显示模式匹配执行的一个步骤,因此您可以看到回溯如何处理呈指数增长的备选方案。

@sln 提供了一个替代我原来的正则表达式的替代方法,它消除了指数回溯。 将之前/之后的正则表达式简化为aX,用于输入字符串aaaaaaaaaaaaaaaaZ ^(a+X*)*a$ 大约需要 300,000 步(每增加一个 a 就加倍)但是 ^(aX*)*a$ 大约需要 100 步

我不知道将易受攻击的正则表达式映射到安全正则表达式的任何通用方法 - 除非@sln 关心提供服务;-)

原始正则表达式的目的是检查输入字符串是否仅包含 [a-z0-9] 和 -,同时以 [a-z0-9] 开头和结尾。 a, a-b, ab--c, a-b--aa---bbb, ...

【讨论】:

以上是关于Go 正则表达式中没有灾难性的回溯吗?的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式模式灾难性回溯

这个正则表达式不应该发生灾难性的回溯

正则表达式灾难性回溯

如何彻底避免正则表达式的灾难性回溯?

如何使这个正则表达式不会导致“灾难性回溯”?

使用正则表达式模式时的灾难性回溯错误