是“评估”吗? 应该是令人讨厌的?
我已经在 ruby 中多次使用了 eval 方法。 但我听到有人说 eval
很糟糕。 当被问到为什么以及如何使用时,我永远找不到令人信服的理由不使用它。 他们真的很讨厌吗? 如果是的话,以什么方式? 有哪些可能的“更安全”的评估选项?
I have been using the eval
method in ruby many times. But I have heard people saying eval
is nasty. When asked, why and how, I could never get a convincing reason not to use it. Are they really nasty? and if so, in what way? What are possible "safer" options to eval?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
如果您正在
eval
由用户提交或可修改的字符串,则这相当于允许任意代码执行。 想象一下,如果该字符串包含对 rm -rf / 或类似内容的操作系统调用。 也就是说,在您知道字符串受到适当约束,或者您的 Ruby 解释器被适当沙箱化,或者理想情况下两者兼而有之的情况下,eval
可能会非常强大。如果您熟悉的话,该问题类似于 SQL 注入。 这里的解决方案与注入问题的解决方案(参数化查询)类似。 也就是说,如果已知您想要
评估
的语句具有非常具体的形式,并且用户不需要提交该语句的全部,则只需提交一个一些变量、数学表达式或类似的内容,您可以从用户那里获取这些小片段,必要时对其进行清理,然后在适当的位置插入用户输入来评估安全模板语句。If you are
eval
ing a string submitted by, or modifiable by the user, this is tantamount to allowing arbitrary code execution. Imagine if the string contained an OS call torm -rf /
or similar. That said, in situations where you know the strings are appropriately constrained, or your Ruby interpreter is sandboxed appropriately, or ideally both,eval
can be extraordinarily powerful.The problem is analogous to SQL injection, if you're familiar. The solution here is similar to the solution to the injection problem (parameterized queries). That is, if the statements you would like to
eval
are known to be of a very specific form, and not all of the statement need be submitted by the user, only a few variables, a math expression, or similar, you can take in these small pieces from the user, sanitize them if necessary, then evaluate the safe template statement with the user input plugged in in the appropriate places.eval
不仅不安全(正如其他地方所指出的),而且速度也很慢。 每次执行时,需要重新解析eval
代码的 AST(例如 JRuby,转换为字节码),这是一个大量字符串操作,而且可能对缓存不利局部性(假设正在运行的程序不会进行大量eval
,因此解释器的相应部分除了很大之外,也是缓存冷的)。你可能会问,为什么 Ruby 中有
eval
呢? “因为我们可以”——事实上,当eval
被发明时(针对 LISP 编程语言),它是 主要是为了表演! 更重要的是,当您想要“将解释器添加到解释器中”以执行元编程任务(例如编写预处理器、调试器或模板引擎)时,使用 eval 是正确的选择。 此类应用程序的共同想法是处理一些 Ruby 代码并对其调用eval
,它肯定胜过重新发明和实现特定于域的玩具语言,这种陷阱也称为 格林斯潘第十条规则。 警告是:注意成本,例如对于模板引擎,在启动时而不是运行时进行所有评估
; 并且不要评估
不受信任的代码,除非您知道如何“驯服”它,即根据能力规则。 后者是很多非常困难的工作(参见例如如何为 Java 完成这一工作; 不幸的是,我不知道 Ruby 有任何这样的努力)。eval
is not only insecure (as has been pointed out elsewhere), it's also slow. Every time it is executed, the AST of theeval
ed code needs to be parsed (and for eg JRuby, turned to bytecode) anew, which is a string-heavy operation and is also probably bad for cache locality (under the assumption that a running program doesn'teval
a lot, and the corresponding parts of the interpreter are thus cache-cold, in addition to being large).Why is there
eval
at all in Ruby, you ask? "Because we can" mostly - In fact, wheneval
was invented (for the LISP programming language), it was mostly for show! More to the point, usingeval
is The Right Thing when you want to "add an interpreter into your interpreter", for metaprogramming tasks such as writing a preprocessor, a debugger or a templating engine. The common idea for such applications is to massage some Ruby code and calleval
on it, and it sure beats reinventing and implementing a domain-specific toy language, a pitfall also known as Greenspun's Tenth Rule. The caveats are: beware of the costs, eg for a templating engine, do all youreval
ing at startup time not run time; and don'teval
untrusted code unless you know how to "tame" it, ie select and enforce a safe subset of the language according to the theory of capability discipline. The latter is a lot of really difficult work (see eg how that was done for Java; I'm not aware of any such effort for Ruby unfortunately).在 Ruby 中,有几个可能比 eval() 更合适的技巧:
#send
允许您调用名称为字符串的方法并传递参数到它。yield
允许您将代码块传递给将在接收方法的上下文中执行的方法。Kernel.const_get("String")
就足以获取名称为字符串的类。我想我无法详细地解释它们,所以我只是给你提示,如果你有兴趣,你可以谷歌一下。
In Ruby there are several gimmicks that might be more appropriate than
eval()
:#send
which allows you to call a method whose name you have as string and pass parameters to it.yield
allows you to pass a block of code to a method which will be executed in the context of the receiving method.Kernel.const_get("String")
is sufficient to get the class whose name you have as string.I think I am not able to explain them properly in detail, so I just gave you the hints, if you're interested you'll google.
这使得调试变得困难。 这使得优化变得困难。 但最重要的是,这通常表明有更好的方法来做你想做的事情。
如果您告诉我们您想通过
eval
实现什么目标,您可能会得到一些与您的具体场景相关的更相关的答案。It makes debugging difficult. It makes optimization difficult. But most of all, it's usually a sign that there is a better way to do whatever you are trying to do.
If you tell us what you are trying to accomplish with
eval
, you may get some more relevant answers relating to your specific scenario.Eval 是一个非常强大的功能,应该谨慎使用。 除了 Matt J 指出的安全问题之外,您还会发现调试运行时评估的代码极其困难。 运行时评估的代码块中的问题对于解释器来说将很难表达 - 因此寻找它会很困难。
话虽这么说,如果您对这个问题感到满意,并且不担心安全问题,那么您不应该避免使用使 Ruby 如此吸引人的功能之一。
Eval is an incredibly powerful feature which should be used carefully. Besides the security issues pointed out by Matt J, you will also find that debugging runtime evaluated code is extremely difficult. A problem in a runtime evaluated code block will be difficult for the interpreter to express - so looking for it will be difficult.
That being said, if you are comfortable with that issue, and are not concerned about the security issue, then you should not avoid using one of the features that makes ruby as appealing as it is.
在某些情况下,放置得当的
eval
很聪明,可以减少所需的代码量。 除了 Matt J 提到的安全问题之外,您还需要问自己一个非常简单的问题:当一切都说完了之后,其他人可以阅读您的代码并理解您所做的事情吗?
如果答案是否定的,那么您通过
eval
获得的成果将因可维护性而被放弃。 这个问题不仅适用于您在团队中工作的情况,而且也适用于您 - 您希望能够在几个月(如果不是几年后)回顾您的代码,并了解您做了什么。In certain situations, a well-placed
eval
is clever and reduces the amount of code required. In addition to the security concerns that have been mentioned by Matt J, you also need to ask yourself one very simple question:When it's all said and done, can anyone else read your code and understand what you did?
If the answer is no, then what you've gained with an
eval
is forsaken for maintainability. This issue is not only applicable if you work in a team, but it is also applicable to you - you want to be able to look back at your code months, if not years from now, and know what you did.eval
方法本质上没有任何问题,但是,要求使用它通常是架构设计和设计模式决策薄弱的标志。我作为 Ruby 开发人员已经全职工作了 12 年,我从来没有在任何存储库中使用过 eval,但是当我在现有存储库中发现它时,它总是绕过已经存在的复杂性。
它还带来巨大的安全风险,因为任何动态值都是使用应用程序用户帐户针对实际服务器进行评估的。
There's nothing inherently wrong with the
eval
method, however, requiring its use is often a sign of weak architecture design and design pattern decisions.I have been working as a Ruby developer for 12 years full-time and I've never had to use eval in any of my repositories but when I found it in an existing repository it was always to bypass already existing complexity.
It also poses a huge security risk as any dynamic value is evaluated against the actual server using the applications user account.
如果您将从“外部”获得的任何内容传递给
eval
,那么您就做错了,而且非常令人讨厌。 非常很难转义代码以使其安全,因此我认为它非常不安全。 但是,如果您使用 eval 来避免重复或其他类似的事情,如下面的代码示例,则可以使用它。然而,至少在 Ruby 1.9.1 中,Ruby 拥有非常强大的元编程方法,您可以执行以下操作:
对于大多数用途,您希望使用这些方法,并且不需要转义。
eval
的另一个坏处是(至少在 Ruby 中)它非常慢,因为解释器需要解析字符串,然后执行当前绑定内的代码。 其他方法直接调用 C 函数,因此您应该获得相当大的速度提升。If you are passing anything that you get from the "outside" to
eval
, your are doing something wrong, and it's very nasty. It's very hard to escape the code enough for it to be safe, so I'd consider it quite unsafe. However, if you're using eval for avoiding duplication or other similar things, like the following code example, it's ok to use it.However, at least in Ruby 1.9.1, Ruby has really powerful meta-programming methods, and you could do the following instead:
For most purposes, you want to use these methods, and no escaping is needed.
The other bad thing about
eval
is the fact that (at least in Ruby), it's quite slow, as the interpreter needs to parse the string, and then execute the code inside the current binding. The other methods calls the C function directly, and therefore you should get quite a speed boost.