“eval”应该是令人讨厌的吗?

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

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

如果您熟悉,问题类似于SQL注入 。 这里的解决方案类似于注入问题的解决方案(参数化查询)。 也就是说,如果您想要eval的语句是非常特定的forms,并且并非所有语句都需要由用户提交,只有少数变量,数学表达式或类似forms,您可以接受来自用户的这些小块,必要时对它们进行清理,然后使用插入适当位置的用户输入来评估安全模板语句。

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

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

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

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

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

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

如果您告诉我们您正在尝试使用eval完成什么,您可能会得到一些与您的特定方案相关的更相关的答案。

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

话虽这么说,如果你对这个问题感到满意,并且不关心安全问题,那么你就不应该避免使用使ruby成为吸引人的function之一。

在某些情况下,良好的eval是聪明的,并减少了所需的代码量。 除了Matt J提到的安全问题之外,您还需要问自己一个非常简单的问题:

如果一切都说完了,那么其他人是否可以阅读您的代码并了解您的所作所为?

如果答案是否定的,那么你对eval所获得的东西就会被抛弃以保持可维护性。 这个问题不仅适用于您在团队中工作,而且适用于您 – 您希望能够回顾您的代码数月,如果不是几年,也知道您做了什么。

如果你把你从“外面”得到的任何东西都传递给了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函数,因此您应该获得相当大的速度提升。