'eval'应该是个不好的东西吗?

27
我在Ruby中经常使用eval方法。但是我听到有人说eval很糟糕。当我问为什么以及如何时,我从来没有得到一个令人信服的不使用它的理由。它们真的很糟糕吗?如果是这样,有什么更安全的替代选项呢?

1
更安全的选项取决于您使用 eval 做什么,您能更具体一些吗? - rampion
我没有特定的要求。但是,“更安全”指的是能够避免或减少漏洞并得到相同结果的东西。 - Chirantan
发送(send)通常比评估(eval)更安全使用吗? - David West
“send”并不是一个可替代的方法,因为它只是向对象发送一个消息(即调用一个方法)。而“eval”则可以评估任何代码。 - Chirantan
8个回答

39

如果你正在评估由用户提交或可修改的字符串,那么这等同于允许任意代码执行。想象一下,如果该字符串包含对操作系统调用rm -rf /或类似操作的指令,后果将不堪设想。尽管如此,在您知道字符串已适当受限、Ruby解释器已适当隔离或理想情况下两者都是如此的情况下,eval可以变得非常强大。

这个问题类似于SQL注入问题,如果您熟悉的话。解决方案与注入问题(参数化查询)类似。也就是说,如果您想要使用 eval 的语句是已知为特定形式,并且不需要全部都由用户提交,只需要几个变量、一个数学表达式或类似的内容,您可以从用户那里获取这些小片段,必要时对它们进行消毒处理,然后在适当的位置插入用户输入数据,执行安全模板语句。


1
而且,清理数据已经被证明在所有情况下都非常难以正确地实施。好答案! - krosenvold
5
现今,“rm -rf --no-preserve-root /”是一个十分危险的命令。请注意,如果您在计算机终端中输入此命令并按下“回车”键,则会永久删除您的操作系统及其所有数据,包括所有用户文件和程序。因此,在使用此命令之前,请确保您知道自己在做什么,并且一定要谨慎操作。 - Cole Tobin

13

eval不仅存在安全问题(正如其他地方指出的那样),而且执行速度也很慢。每次执行eval时,需要重新解析eval代码的AST,并且对于例如JRuby而言,还需要将其转换为新的字节码,这是一种字符串密集型操作,也可能对缓存局部性产生负面影响(假设运行程序不会过多使用eval,因此解释器的相关部分也会较冷,除了占用空间较大外)。

你会问,为什么Ruby中会有eval?主要是因为“我们可以” - 实际上,在发明eval时(用于LISP编程语言),它主要是为了炫耀而存在的!点一下链接查看详情。更重要的是,在元编程任务中,当您想要“在您的解释器中添加一个解释器”时,使用eval是正确的选择,例如编写预处理器、调试器或模板引擎。通常的思路是,修改一些Ruby代码并对其调用eval,这比重新发明和实现特定领域的小语言要强得多,这种坑也称为格林斯潘第十条规则。需要注意的是:要注意成本,例如对于模板引擎,应在启动时而不是运行时执行所有eval操作;如果不知道如何“驯服”它,请勿对不受信任的代码进行eval操作,即根据能力学科理论选择并强制执行语言的安全子集。后者是非常困难的工作(请参见例如Java是如何实现的!点一下链接查看详情;不幸的是,我不知道是否有类似Ruby的努力)。


2
我不会说Lisp中的这个构造是“为了炫耀”而发明的。它最初是在理论计算机科学领域中的一个构造,然后被用作解释器的蓝图(请参见您链接到的网站上的McCarty文章)。 - js.

11

Ruby中有几个技巧可能比eval()更合适:

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

我认为我无法详细地解释它们,所以我只是给了你提示,如果你感兴趣,你可以去搜索一下。


7
它会使调试变得困难,优化也变得困难。但最重要的是,通常这意味着有更好的方法来完成你想做的事情。如果你告诉我们你在使用eval时想达到什么目的,你可能会得到与你特定场景相关的更多相关答案。

6

Eval是一项非常强大的功能,但需要谨慎使用。除了Matt J指出的安全问题外,您还会发现调试运行时评估的代码非常困难。运行时评估代码块中的问题将很难被解释器表达 - 因此查找它将会很困难。

话虽如此,如果您对此问题感到舒适,并且不担心安全问题,则不应避免使用使Ruby如此吸引人的功能之一。


5
在某些情况下,恰当地使用eval可以聪明地减少所需代码的数量。除了Matt J提到的安全问题外,你还需要问自己一个非常简单的问题:
最终结果是什么,其他人能读懂你的代码吗?
如果答案是否定的,那么你用eval得到的东西就被放弃了,因为它不易于维护。这个问题不仅适用于团队工作,也适用于你自己——你希望在几个月或几年后回顾你的代码时知道你做了什么。

0

使用eval方法本身并没有什么问题,然而,要求使用它通常是弱架构设计和设计模式决策的标志。

我作为一名全职Ruby开发人员已经工作了12年,我从来没有在我的任何代码库中使用过eval,但是当我在现有的代码库中发现它时,总是为了绕过已经存在的复杂性。

它还带来了巨大的安全风险,因为任何动态值都会根据应用程序用户帐户在实际服务器上进行评估。


-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 函数,因此您应该获得相当大的速度提升。


define_method 已经存在很长时间了 - 这不是 1.9 版本的特性。如果你正在使用 eval,那可能只是因为你不知道适合这项工作的正确工具。 - Chuck
抱歉,我现在才意识到我的意思根本不清楚。在Ruby 1.9中,你有几个新的元编程方法,我并不是特别指define_method。但我同意大多数其他回答帖子的人的观点,OP应该说明为什么要使用eval - sarahhodne
你的第一个 define_getters 可能存在漏洞!请参考 https://dev59.com/JnA75IYBdhLWcg3w-OVA#3003509 中非常相似的例子。 - Marc-André Lafortune

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接