问题 停止失控的正则表达式


有没有办法阻止失控的正则表达式?

我对如何修改它的建议不感兴趣。我知道它可以修改,所以它不会破坏等,但我正在运行一个正则表达式对数千个输入,所以修改它意味着我需要重新测试它 所有 输入。不太实际。

所以确切的问题是: 是否有某种形式的计时器,我可以用来终止一个需要超过X秒才能完成的正则表达式?


1396
2018-05-29 15:30


起源

从我自己的测试显示给我的实际情况来看,你实际上无法从正则表达式引擎中发出警报。但是,有一个模块:Sys :: SigAction,它有一个功能,“timeout_call“这将在正在进行时中断正则表达式引擎。但是,中断正在进行的RE引擎是不安全的,并且可能导致seg-fault。 - DavidO
@DavidO然后简单的fork应该做。 - Сухой27
@mpapec ...由“简单”的一些定义。 ;)是的, fork 有时是一个很好的选择。我将在下面更新我的答案,将其作为建议。 - DavidO
@mpapec:不,我认为fork / alarm建议非常合理。这个问题在关闭的过程中太糟糕了(希望这不会成功),因为这不是一个糟糕的问题。用例可能具有可疑的优点,但是存在时间约束匹配合理的合法情况。 BTW:我认为在Perl-5.20中已经弃用了线程。 - DavidO
@DavidO re: threads have been deprecated in Perl-5.20 世界即将结束。 :) - Сухой27


答案:


Perl的内置功能 alarm 不足以打破长时间运行的正则表达式,因为Perl不提供内部操作码内警报超时的机会。 alarm 根本无法穿透它。

在某些情况下,最明显的解决方案是 fork 一个子进程,并在它耗尽太长时间后使用它 alarm。此PerlMonks帖子演示了如何超时分叉进程: 回复:脚本超时

CPAN上有一个Perl模块叫做 SYS ::的sigaction 有一个叫做的函数 timeout_call, 哪一个  使用不安全信号中断长时间运行的正则表达式。但是,RE引擎并未设计为中断,并且可能处于不稳定状态,这可能导致大约10%的时间出现seg-fault。

下面是一些示例代码,演示了Sys :: SigAction成功打破了正则表达式引擎,以及演示Perl的 alarm 无法这样做:

use Sys::SigAction 'timeout_call';
use Time::HiRes;


sub run_re {
  my $string = ('a' x 64 ) . 'b';

  if( $string =~ m/(a*a*a*a*a*a*a*a*a*a*a*a*)*[^Bb]$/ ) {
    print "Whoops!\n";
  }
  else {
    print "Ok!\n";
  }
}

print "Sys::SigAction::timeout_call:\n";
my $t = time();
timeout_call(2,\&run_re);
print time() - $t, " seconds.\n";

print "alarm:\n";
$t = time();

eval {
  local $SIG{ALRM} = sub { die "alarm\n" };
  alarm 2;
  run_re();
  alarm 0;
};

if( $@ ) {
  die unless $@ eq "alarm\n";
}
else {
  print time() - $t, " seconds.\n";
}

输出将是:

$ ./mytest.pl
Sys::SigAction::timeout_call:
Complex regular subexpression recursion limit (32766) exceeded at ./mytest.pl line 11.
2 seconds.
alarm:
Complex regular subexpression recursion limit (32766) exceeded at ./mytest.pl line 11.
^C

你会注意到在第二个电话中 - 那个应该超时的电话 alarm,我终于不得不 ctrl-C 因为它 alarm 不足以打破RE引擎。

Sys :: SigAction的一个重要警告是,即使它能够打破长时间运行的正则表达式,因为RE引擎不是为这种中断而设计的,整个过程可能会变得不稳定,从而导致段错误。虽然每次都不会发生,但它可能会发生。这可能不是你想要的。

我不知道你的正则表达式是什么样的,但是它是否适合于允许的语法 RE2发动机,你可以使用Perl模块, 重新::引擎:: RE2 使用RE2 C ++库。该引擎保证线性时间搜索,但它提供的功能不如Perl的内置引擎。 RE2方法首先通过提供线性时间保证来避免整个问题。

但是,如果你不能使用RE2(可能是因为你的正则表达式的语义要求太高),fork / alarm方法可能是确保你保持控制的最安全的方法。

顺便说一句,这个问题和我的答案的一个版本被交叉 PerlMonks


10
2018-05-29 16:43