25

我已经多次使用evalruby​​ 的功能。但我听人说evals 很讨厌。当被问及为什么以及如何使用时,我永远无法找到不使用它的令人信服的理由。他们真的很讨厌吗?如果是,以什么方式?评估可能有哪些“更安全”的选项?

4

7 回答 7

38

如果您正在eval输入由用户提交或可由用户修改的字符串,这无异于允许任意代码执行。想象一下,如果字符串包含操作系统调用rm -rf /或类似调用。也就是说,在您知道字符串受到适当约束的情况下,或者您的 Ruby 解释器被适当地沙盒化,或者理想情况下两者eval都具有非常强大的功能。

如果您熟悉的话,这个问题类似于SQL 注入。这里的解决方案类似于注入问题的解决方案(参数化查询)。也就是说,如果你想要的语句eval是一个非常具体的形式,并且不是所有的语句都需要用户提交,只有几个变量、一个数学表达式或类似的,你可以接受这些来自用户的小片段,必要时对其进行清理,然后在适当的位置插入用户输入来评估安全模板语句。

于 2009-03-12T05:02:05.513 回答
13

eval不仅不安全(正如在其他地方指出的那样),而且速度也很慢。每次执行时,evaled 代码的 AST 都需要重新解析(例如 JRuby,转为字节码),这是一个字符串繁重的操作,也可能不利于缓存局部性(假设正在运行程序并不eval多,因此解释器的相应部分是缓存冷的,除了很大)。

你问,为什么eval在 Ruby 中存在?“因为我们可以”主要是 - 事实上,当eval被发明时(对于 LISP 编程语言),它主要是为了展示eval更重要的是,当您想要“将解释器添加到解释器中”时,使用是正确的事情,用于元编程任务,例如编写预处理器、调试器或模板引擎。此类应用程序的常见想法是修改一些 Ruby 代码并调用eval它,这肯定优于重新发明和实现特定领域的玩具语言,这是一个也称为Greenspun 第十规则的陷阱。需要注意的是:注意成本,例如对于模板引擎,eval在启动时而不是运行时完成所有工作;不要eval不受信任的代码,除非您知道如何“驯服”它,即根据能力纪律理论选择和执行语言的安全子集。后者是很多非常困难的工作(例如,请参阅Java 是如何完成的;不幸的是,我不知道对 Ruby 有任何此类努力)。

于 2012-08-27T10:10:56.837 回答
11

在 Ruby 中,有几个噱头可能比eval()

  1. #send它允许您调用名称为字符串的方法并将参数传递给它。
  2. yield允许您将代码块传递给将在接收方法的上下文中执行的方法。
  3. 通常,简单Kernel.const_get("String")的方法就足以让您将其名称作为字符串的类。

我想我无法详细解释它们,所以我只是给了你一些提示,如果你有兴趣,你会谷歌。

于 2009-03-12T17:07:17.833 回答
7

它使调试变得困难。它使优化变得困难。但最重要的是,这通常表明有更好的方法来做你想做的任何事情。

如果您告诉我们您想要完成什么eval,您可能会得到一些与您的特定场景相关的更相关的答案。

于 2009-03-12T05:07:11.803 回答
6

Eval 是一个非常强大的功能,应该小心使用。除了 Matt J 指出的安全问题外,您还会发现调试运行时评估的代码非常困难。运行时评估的代码块中的问题对于解释器来说将难以表达——因此寻找它会很困难。

话虽如此,如果您对这个问题感到满意,并且不担心安全问题,那么您不应该避免使用使 ruby​​ 如此吸引人的功能之一。

于 2009-03-12T05:09:07.213 回答
5

在某些情况下,一个合适的位置eval很聪明,可以减少所需的代码量。除了 Matt J 提到的安全问题之外,您还需要问自己一个非常简单的问题:

当一切都说完了,其他人能读懂你的代码并理解你做了什么吗?

如果答案是否定的,那么您从 an 中获得的东西将eval因可维护性而被放弃。这个问题不仅适用于你在一个团队中工作,而且它也适用于你——你希望能够回顾你的代码几个月,如果不是几年之后,并且知道你做了什么。

于 2009-03-12T05:15:19.990 回答
-2

如果您将从“外部”获得的任何东西传递给eval,那么您做错了什么,这非常令人讨厌。很难将代码转义到足够安全的程度,所以我认为它非常不安全但是,如果您使用 eval 来避免重复或其他类似的事情,例如以下代码示例,则可以使用它。

class Foo
  def self.define_getters(*symbols)
    symbols.each do |symbol|
      eval "def #{symbol}; @#{symbol}; end"
    end
  end

  define_getters :foo, :bar, :baz
end

然而,至少在 Ruby 1.9.1 中,Ruby 具有非常强大的元编程方法,您可以改为执行以下操作:

class Foo
  def self.define_getters(*symbols)
    symbols.each do |symbol|
      define_method(symbol) { instance_variable_get(symbol) }
    end
  end

  define_getters :foo, :bar, :baz
end

对于大多数目的,您希望使用这些方法,并且不需要转义。

另一个不好的事情eval是(至少在 Ruby 中)它非常慢,因为解释器需要解析字符串,然后在当前绑定中执行代码。其他方法直接调用 C 函数,因此您应该获得相当大的速度提升。

于 2009-12-14T17:44:35.960 回答