【问题标题】:What are the common pitfalls when using Perl's eval?使用 Perl 的 eval 时常见的陷阱是什么?
【发布时间】:2011-11-27 17:52:55
【问题描述】:

与 Perl 的 eval 相关的常见陷阱有哪些,可能会导致您选择使用 Try::Tiny 等模块?

【问题讨论】:

标签: perl exception eval


【解决方案1】:

Perl 的eval 有两种形式,字符串评估和块评估。 String eval 调用编译器来执行源代码。块 eval 将已编译的代码包围在一个将捕获 die 异常的包装器中。 (字符串 eval 也捕获了die 异常,以及任何编译错误)。

Try::Tiny 仅适用于 eval 的块形式,但以下适用于两种形式。

每次调用eval 都会改变$@ 的值。如果 eval 成功或 eval 捕获的错误,它将是 ''

这意味着每次调用 eval 时,都会清除之前的所有错误消息。 Try::Tiny 为您本地化了 $@ 变量,因此成功的 eval 不会清除先前失败的 eval 的消息。

另一个陷阱来自使用$@ 作为检查以确定 eval 是否成功。一个常见的模式是:

eval {...};
if ($@) {
   # deal with error here
}

这依赖于两个假设,首先,$@ 可能包含的任何错误消息都是真值(通常为真),并且在 eval 块和 if 语句之间没有代码。

在视觉上当然后者是正确的,但是如果 eval 块创建了一个对象,并且在 eval 失败后该对象超出了范围,那么该对象的 DESTROY 方法将在 if 语句之前被调用。如果 DESTROY 碰巧在没有本地化 $@ 的情况下调用了 eval 并且它成功了,那么当你的 if 语句运行时,$@ 变量将被清除。

解决这些问题的方法是:

my $return = do {
    local $@;
    my $ret;
    eval {$ret = this_could_fail(); 1} or die "eval failed: $@";
    $ret
};

逐行分开,local $@do 块创建一个新的$@,以防止破坏以前的值。 my $ret 将是评估代码的返回值。在eval块中,$ret被赋值,然后块返回1。这样,无论如何,如果 eval 成功,它将返回 true,如果失败,它将返回 false。在失败的情况下该怎么做取决于你。上面的代码只是死了,但你可以很容易地使用 eval 块的返回值来决定运行其他代码。

由于上面的咒语有点乏味,容易出错。使用像 Try::Tiny 这样的模块可以使您免受这些潜在错误的影响,但每次 eval 会调用更多的函数。知道如何正确使用 eval 很重要,因为如果您必须使用字符串 eval,Try::Tiny 将无济于事。

【讨论】:

  • 在当前版本中修复。
  • 微小的修正 - 如果没有抛出异常,$@ 实际上将是一个空字符串而不是 undef。
  • @Grant McLean => 谢谢,我应该记得的,因为这就是我通常的 repl 处理错误的方式:perl -wE 'say eval, $@ while <>'
【解决方案2】:

【讨论】:

  • 谢谢。虽然我阅读了 Try:Tiny 的介绍,但我没有阅读背景部分。
  • 在当前版本中修复。
【解决方案3】:

除了上面的答案,我还要补充...

  • eval 受全局 $SIG{__DIE__} 处理程序的影响,导致远距离操作。
  • 新手很容易混淆eval BLOCKeval STRING,因为它们看起来做的事情相同,但其中一个是安全漏洞。

Try::Tiny 有它自己的缺陷,最大的缺陷是虽然它看起来像一个块,但实际上它是一个子程序调用。这意味着:

eval {
    ...blah blah...
    return $foo;
};

还有这个:

try {
    ...blah blah...
    return $foo;
};

不要做同样的事情。这些在CAVEATS section of the Try::Tiny docs 中列出。也就是说,我会推荐它而不是 eval

【讨论】:

  • 你是说eval 在 5.14 中仍然无法使用? 真的吗?那会非常令人失望,因为我知道很多工作都在尝试通过修复困扰eval 的任何潜在错误来使Try::Tiny 过时。如果这项努力失败了,那么就会出现一个可怕的问题,因为Try::Tiny 仍然只是一个 CPAN 模块,而不是核心。如果你不能用核心做真正的工作,而且可靠,那是不可接受的情况。
  • @tchrist 忘记了。据我了解,5.14.0 修复了与 $@ 和对象销毁之间的交互有关的一类错误,并且通常使 eval { ... }; if( $@ ) { ... } 更可靠。我相信这可以解决三件事中的两件事 Try::Tiny 修复...而第三件事(错误的 $@)不太可能。它仍然保留了我提到的要点。阻止$SIG{__DIE__} 在 eval 中触发将是一个不错的 5.16 功能。并调低剧情,伙计。
  • eval STRING 称为“安全问题”不仅过于戏剧化;这甚至不是真的。我使用eval STRING 是因为它大约二十三年前首次出现在 perl2 中,我可以向你保证,我从来没有遇到过任何所谓的“安全问题”。当然,愚蠢的程序员可以用它做愚蠢的事情,但这几乎适用于任何事情。如果你生活在一个病态偏执的世界中,你应该使用污点模式和/或安全隔间。在正常情况下,eval STRING 完成了很多有用的工作;参见经典的 rename 程序。
  • @tchrist 并不是所有的安全问题都是代码错误,很多都是由于糟糕的接口导致的程序员错误。 eval 通过相同的功能运行两个非常不同的功能,但显然具有相同的效果。使用eval STRING 非常非常容易,而eval BLOCK 会这样做,从而创建一个安全漏洞。例如,无处不在的eval "require $module"google.com/codesearch#search/…
  • 将受污染的数据传递给eval STRING 的人得到了他应得的。如果$module 是不受信任的用户输入,那么无论您做什么,您都将让该用户指定要运行的代码。不要那样做。无论如何,您不能只用eval BLOCK 替换您引用的字符串版本。您忘记了require 的工作原理。这根本不可能。
【解决方案4】:

在 X11 函数上使用 eval 可能仍然无法保持活动状态。

代码是这样的

eval {    
    @win_arrays = GetWindowsFromPid($pid);
};

脚本将退出

X 请求失败错误:...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 2011-12-13
    • 2011-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多