Regex 给定正则表达式的最差输入
我想在我的代码库中自动测试正则表达式 我想保护自己不受邪恶的regexp及其亲属的伤害 为此,我正在寻找一种为给定正则表达式和引擎生成“最坏情况”输入的方法或现有库(基于NFA和DFA的引擎都在范围内) 诚然,正则表达式是一种功能强大的语言,显然(计算上)很难找到任意正则表达式的最差输入,特别是如果使用反向引用,它甚至可能是不可判定的Regex 给定正则表达式的最差输入,regex,algorithm,performance-testing,analysis,Regex,Algorithm,Performance Testing,Analysis,我想在我的代码库中自动测试正则表达式 我想保护自己不受邪恶的regexp及其亲属的伤害 为此,我正在寻找一种为给定正则表达式和引擎生成“最坏情况”输入的方法或现有库(基于NFA和DFA的引擎都在范围内) 诚然,正则表达式是一种功能强大的语言,显然(计算上)很难找到任意正则表达式的最差输入,特别是如果使用反向引用,它甚至可能是不可判定的 在我的用例中,我可以找到糟糕的输入(与最糟糕的输入相反),但非常简短。正则表达式最糟糕的输入会因引擎而异。同一个正则表达式和字符串在一个引擎上可能根本不需要时间,
在我的用例中,我可以找到糟糕的输入(与最糟糕的输入相反),但非常简短。正则表达式最糟糕的输入会因引擎而异。同一个正则表达式和字符串在一个引擎上可能根本不需要时间,但在另一个引擎上永远不会完成 发动机之间的差异 发动机类型 对于某些引擎,“最坏”的正则表达式仍然是良性的,在线性时间内运行(或者
O(n*m)
time,此时正则表达式的长度和字符串的长度都可能不同。)当然,这是实现的原因。这些引擎不会后退;相反,他们使用有限状态机(FSM)
请注意,一些回溯实现使用FSM,但仅作为中间步骤。不要让这件事迷惑你;他们不是FSM
大多数旧的正则表达式引擎(如sed)使用FSM匹配。有一些新引擎使用这种实现,例如Go。PCRE甚至有使用这种匹配类型的DFA函数(搜索“DFA”)
还解决了两种实现之间的潜在速度差异
如果你真的担心恶意输入会影响正则表达式的速度,那么考虑使用FSM实现是明智的。不幸的是,FSM没有其他实现那么强大;它缺乏对某些功能的支持,例如反向引用
优化
邪恶其实有点主观。对一个正则表达式引擎有害的东西可能不会对另一个引擎有害。如果引擎得到优化,邪恶的阴谋就会被挫败。考虑到回溯引擎的潜在指数运行时间,优化对于回溯引擎尤其重要
短路
在某些情况下,发动机可能能够快速确定不可能匹配。在对字符串aaaaaaaaaaaaaa
运行regexa*b?a*x
时,表示:
你的比赛彻底失败了。这意味着引擎,由于其内部优化,理解您的模式在任何位置都不会匹配,因此甚至没有尝试匹配
请记住,正则表达式是邪恶的,特别是当与该字符串配对时
当然,引擎是智能的,不需要回溯来确定匹配将不起作用。它看到了一件非常明显的事情:正则表达式需要一个x
来匹配,但是字符串中没有x
修饰语
我之所以提到这一点,是因为您可能不希望修饰符成为正则表达式性能的一个因素。但事实确实如此
即使是PCRE,一种更优化的实现,在启用了u
和i
修饰符的情况下,也可能会采取更多的步骤。有关这方面的更多信息,请参阅我的问题。最后,我发现只有某些角色触发了这种行为
分析字符串
字符串长度
一般来说,长字符串比短字符串慢。事实上,如果您发现一个长度为x的字符串导致灾难性的回溯,您可以通过增加该字符串的长度使其回溯得更多一些
贪婪与懒惰
比较这些正则表达式的速度:
on*b
aaaaaaaa…aaaaaa b
aaaaaa…aaaaaa b
abaaaaaa…aaaaaa上的
*b
on*?b
abaaaaaaaa…aaaaaa
a*b
或a*?b
,则引擎可能会显著优化
蛮力试验
有几个框架是专门设计用来查找正则表达式中的漏洞的。也许值得一试
如果你想尝试制作自己的算法,我会建议你做一件事。尝试字典中的所有字符是不实际的,特别是如果您想测试长字符串
相反,请查看正则表达式以确定应该测试哪些字符。如果您将(a++)
作为正则表达式,那么实际上只有两个匹配项:a
而不是a
。您可以想象,当您生成字符串以使用暴力时,只有两个字符:a
和b
(也称为非a
)
设置超时
如果能确保你的正则表达式在宇宙热死之前完成,那将是一件美妙的事情,对吧?一些正则表达式引擎确实有一种设置超时的方法
:
bool设置时间限制(int$seconds)
设置允许脚本运行的秒数。如果达到该值,脚本将返回一个致命错误。默认限制为30秒,如果存在,则为php.ini
中定义的max\u execution\u time
值
调用时,set\u time\u limit()
从零重新启动超时计数器。换句话说,如果超时是默认的30秒,并且在脚本执行25秒后进行了调用,例如set\u time\u limit(20)
,则脚本将在超时之前运行总共45秒
您不妨访问该链接,因为它正好位于堆栈溢出上。在(a++)
AppDomain domain = AppDomain.CurrentDomain;
// Set a timeout interval of 2 seconds.
domain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(2));
Runnable runnable = new Runnable() {
@Override
public void run()
{
long startTime = System.currentTimeMillis();
Matcher interruptableMatcher = pattern.matcher(new InterruptibleCharSequence(input));
interruptableMatcher.find(); // runs for a long time!
System.out.println("Regex took:" + (System.currentTimeMillis() - startTime) + "ms");
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();