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

时间:2016-08-05 09:15:37

标签: regex algorithm performance-testing analysis

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

我想保护免受(a+)+邪恶的正义与他们的亲属的伤害。

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

当然,正则表达式是一种强大的语言,很明显[计算上]很难找到任意正则表达式的最差输入,尤其是。如果使用反向引用,也许它甚至可能是不可判定的。

对于我的用例,我很好找到可怕的输入(而不是最差的输入),但很短。

1 个答案:

答案 0 :(得分:4)

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

引擎之间的差异

引擎类型

对于某些发动机,"最恶劣的"正则表达式仍然是良性的,在线性时间(或O(n*m)时间运行时,正则表达式的长度和字符串的长度可能会有所不同。)当然,原因是实现。这些引擎不会回溯;相反,他们使用有限状态机(FSM)。

请注意,某些回溯实现使用FSM,但仅作为中间步骤。不要让这让你感到困惑;他们不是FSM。

大多数旧的正则表达式引擎(如sed)都使用FSM匹配。有一些使用此实现的新引擎,例如Go。 PCRE甚至具有使用此类匹配的DFA功能(搜索" DFA" here)。

Another answer of mine还解决了两种实现之间潜在的速度差异。

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

优化

邪恶实际上有点主观。一个正则表达式引擎的邪恶可能不是一个不同的引擎邪恶。如果引擎被优化,可以阻止邪恶的情节。鉴于潜在的指数运行时间,优化对于回溯引擎尤为重要。

短路

在某些条件下,引擎可能能够快速确定匹配是不可能的。在针对字符串a*b?a*x运行正则表达式aaaaaaaaaaaaaaaaaaaaaaaaaa时,Regex101说:

  

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

请记住,Wikipedia表示正则表达式是邪恶的,尤其是在与该字符串配对时。

当然,引擎很聪明,不需要回溯以确定匹配不会起作用。它看到了一些非常明显的东西:正则表达式需要x才能匹配,但字符串中不存在x

调节剂

我提到这一点是因为您可能不希望修饰符成为正则表达式性能的一个因素。但他们是。

即使PCRE是更优化的实现之一,启用ui修饰符也可能需要更多步骤。有关详细信息,请参阅我的问题here。最后,我发现只有某些字符会触发这种行为。

分析字符串

字符串长度

通常,长字符串比短字符串慢。事实上,如果你发现一个长度为x的字符串会导致灾难性的回溯,你可以通过增加字符串的长度来使其回溯一点。

贪婪与懒惰

比较这些正则表达式的速度:

  • .*b aaaaaaaa...aaaaab
  • .*?b aaaaaaaa...aaaaab
  • .*b abaaaaaaa...aaaaa
  • .*?b abaaaaaaa...aaaaa

基本上,当您认为需要匹配很多时,贪婪匹配是最好的。当你只需要匹配时,懒惰匹配是最好的。

请注意,如果您将正则表达式更改为a*ba*?b,则引擎可能会大大优化。

暴力测试

有几个框架专门用于尝试查找正则表达式中的漏洞。试一试可能是值得的。

如果您想尝试制作自己的算法,我会建议一件事。尝试字典中的所有字符是不切实际的,特别是如果你想测试长字符串。

相反,请查看正则表达式以确定应测试哪些字符。如果您的正则表达式为(a+)+,那么匹配中只有两件事:a而不是a。你真的可以想象当你用蛮力生成字符串时,只有两个字符:ab(又名a)。

设置超时

能够在宇宙热死之前确保你的正则表达式完成真是太棒了,对吧?一些正则表达式引擎确实有一种设置超时的方法。

.NET

AppDomain domain = AppDomain.CurrentDomain;
  // Set a timeout interval of 2 seconds.
  domain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(2));

Java

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();

PHP

  

bool set_time_limit ( int $seconds )

     

设置允许脚本运行的秒数。如果达到此值,脚本将返回致命错误。默认限制为30秒,如果存在,则max_execution_time中定义的php.ini值。

     

调用时,set_time_limit()从零开始重启超时计数器。换句话说,如果超时是默认的30秒,并且脚本执行了25秒,则进行set_time_limit(20)之类的调用,脚本将在超时之前运行总共45秒。

Perl

您也可以访问该链接,因为它正好在Stack Overflow上。