【问题标题】:Is 'eval' supposed to be nasty?'eval'应该是讨厌的吗?
【发布时间】:2026-02-06 02:05:01
【问题描述】:

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

【问题讨论】:

  • 更安全的选项取决于您使用 eval 执行的操作,您能更具体一点吗?
  • 我没有什么特别的要求。但我所说的“更安全”是指可以让您获得相同结果的东西,避免或减少漏洞。
  • 通常使用 send 比 eval 更安全吗?
  • send 并不是真正的替代方案,因为 send 只向对象发送消息(即调用方法)。 eval 评估代码。任何代码。

标签: ruby eval


【解决方案1】:

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

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

【讨论】:

  • 事实证明,对于所有种情况,要真正进行正确的消毒是非常困难的。不错的答案!
  • rm -rf --no-preserve-root / 如今。
【解决方案2】:

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

你问,为什么 Ruby 中有 eval? “因为我们可以”主要是 - 事实上,当 eval 被发明(用于 LISP 编程语言)时,它就是 mostly for show!更重要的是,当您想要“将解释器添加到解释器中”时,使用eval 是正确的选择,用于元编程任务,例如编写预处理器、调试器或模板引擎。此类应用程序的常见想法是修改一些 Ruby 代码并在其上调用 eval,这肯定比重新发明和实现特定领域的玩具语言(也称为 Greenspun's Tenth Rule 的陷阱)要好。需要注意的是:注意成本,例如对于模板引擎,在启动时而不是运行时执行所有evaling;并且不要eval 不受信任的代码,除非您知道如何“驯服”它,即根据capability discipline 的理论选择并强制执行语言的安全子集。后者是很多非常困难的工作(参见例如how that was done for Java;不幸的是,我不知道Ruby有任何此类努力)。

【讨论】:

  • 我不会说它是在 Lisp 中“为了展示”而发明的。它最初是理论计算机科学领域的一个构造,然后被用作解释器的蓝图(请参阅您链接到的站点上的 McCarty article)。
【解决方案3】:

在 Ruby 中,有几个噱头可能比 eval() 更合适:

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

        如果答案是否定的,那么您使用eval 获得的东西将被放弃以实现可维护性。这个问题不仅适用于您在团队中工作,而且也适用于您 - 您希望能够回顾您的代码几个月(如果不是几年),并知道您做了什么。

        【讨论】:

          【解决方案7】:

          如果您将从“外部”获得的任何东西传递给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,这可能只是意味着您不知道适合这项工作的工具。
          • 哎呀,抱歉,我现在明白了,我的意思完全不清楚。在 Ruby 1.9 中,您有几个新的元编程方法,我并不是要专门提到 define_method。但我同意其他大多数人的观点,OP 应该说明他为什么要使用eval
          • -1。您的第一个 define_getters 可能是一个漏洞!在*.com/questions/3003328/how-do-i-use-class-eval/… 中查看一个非常相似的示例
          最近更新 更多