Regex 给定正则表达式的最差输入

Regex 给定正则表达式的最差输入,regex,algorithm,performance-testing,analysis,Regex,Algorithm,Performance Testing,Analysis,我想在我的代码库中自动测试正则表达式 我想保护自己不受邪恶的regexp及其亲属的伤害 为此,我正在寻找一种为给定正则表达式和引擎生成“最坏情况”输入的方法或现有库(基于NFA和DFA的引擎都在范围内) 诚然,正则表达式是一种功能强大的语言,显然(计算上)很难找到任意正则表达式的最差输入,特别是如果使用反向引用,它甚至可能是不可判定的 在我的用例中,我可以找到糟糕的输入(与最糟糕的输入相反),但非常简短。正则表达式最糟糕的输入会因引擎而异。同一个正则表达式和字符串在一个引擎上可能根本不需要时间,

我想在我的代码库中自动测试正则表达式

我想保护自己不受邪恶的regexp及其亲属的伤害

为此,我正在寻找一种为给定正则表达式和引擎生成“最坏情况”输入的方法或现有库(基于NFA和DFA的引擎都在范围内)

诚然,正则表达式是一种功能强大的语言,显然(计算上)很难找到任意正则表达式的最差输入,特别是如果使用反向引用,它甚至可能是不可判定的


在我的用例中,我可以找到糟糕的输入(与最糟糕的输入相反),但非常简短。

正则表达式最糟糕的输入会因引擎而异。同一个正则表达式和字符串在一个引擎上可能根本不需要时间,但在另一个引擎上永远不会完成

发动机之间的差异 发动机类型 对于某些引擎,“最坏”的正则表达式仍然是良性的,在线性时间内运行(或者
O(n*m)
time,此时正则表达式的长度和字符串的长度都可能不同。)当然,这是实现的原因。这些引擎不会后退;相反,他们使用有限状态机(FSM)

请注意,一些回溯实现使用FSM,但仅作为中间步骤。不要让这件事迷惑你;他们不是FSM

大多数旧的正则表达式引擎(如sed)使用FSM匹配。有一些新引擎使用这种实现,例如Go。PCRE甚至有使用这种匹配类型的DFA函数(搜索“DFA”)

还解决了两种实现之间的潜在速度差异

如果你真的担心恶意输入会影响正则表达式的速度,那么考虑使用FSM实现是明智的。不幸的是,FSM没有其他实现那么强大;它缺乏对某些功能的支持,例如反向引用

优化 邪恶其实有点主观。对一个正则表达式引擎有害的东西可能不会对另一个引擎有害。如果引擎得到优化,邪恶的阴谋就会被挫败。考虑到回溯引擎的潜在指数运行时间,优化对于回溯引擎尤其重要

短路 在某些情况下,发动机可能能够快速确定不可能匹配。在对字符串
aaaaaaaaaaaaaa
运行regex
a*b?a*x
时,表示:

你的比赛彻底失败了。这意味着引擎,由于其内部优化,理解您的模式在任何位置都不会匹配,因此甚至没有尝试匹配

请记住,正则表达式是邪恶的,特别是当与该字符串配对时

当然,引擎是智能的,不需要回溯来确定匹配将不起作用。它看到了一件非常明显的事情:正则表达式需要一个
x
来匹配,但是字符串中没有
x

修饰语 我之所以提到这一点,是因为您可能不希望修饰符成为正则表达式性能的一个因素。但事实确实如此

即使是PCRE,一种更优化的实现,在启用了
u
i
修饰符的情况下,也可能会采取更多的步骤。有关这方面的更多信息,请参阅我的问题。最后,我发现只有某些角色触发了这种行为

分析字符串 字符串长度 一般来说,长字符串比短字符串慢。事实上,如果您发现一个长度为x的字符串导致灾难性的回溯,您可以通过增加该字符串的长度使其回溯得更多一些

贪婪与懒惰 比较这些正则表达式的速度:

  • *b
    on
    aaaaaaaa…aaaaaa b
  • aaaaaa…aaaaaa b
  • abaaaaaa…aaaaaa上的
    *b
  • *?b
    on
    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();