为什么是eval邪恶?

我知道Lisp和Scheme程序员通常会说,除非严格需要,否则应该避免使用eval。 我已经看到几种编程语言的相同的build议,但我还没有看到一个明确的反对使用eval的参数列表。 我在哪里可以find使用eval的潜在问题的帐户?

例如,我知道过程编程中的GOTO问题(使程序不可读,难以维护,难以find安全问题等),但是我从来没有看到反对eval的论点。

有趣的是,反对GOTO的同样的论点应该是有效的反对延续,但是我看到Schemers,例如,不会说延续是“邪恶的” – 你应该小心使用它们。 他们更有可能使用eval而不是使用代码使用代码(据我所知 – 我可能是错的)。

为什么不使用EVAL有几个原因。

初学者的主要原因是:你不需要它。

示例(假设为Common Lisp):

使用不同的运算符评估expression式:

 (let ((ops '(+ *))) (dolist (op ops) (print (eval (list op 1 2 3))))) 

最好写成:

 (let ((ops '(+ *))) (dolist (op ops) (print (funcall op 1 2 3)))) 

有很多例子,初学者学习Lisp认为他们需要EVAL ,但他们不需要它 – 因为expression式被评估,并且也可以评估函数部分。 大多数情况下,使用EVAL显示对评估者缺乏了解。

这与macros是一样的问题。 通常初学者编写macros,在那里写函数 – 不理解macros是为了什么,不理解函数已经完成了这个工作。

对于使用EVAL的工作来说,这往往是错误的工具,它经常表明初学者不理解通常的Lisp评估规则。

如果您认为您需要EVAL ,那么请检查是否可以使用FUNCALLREDUCEAPPLY

  • FUNCALL – 用参数调用函数: (funcall '+ 1 2 3)
  • REDUCE – 在值列表上调用函数并合并结果: (reduce '+ '(1 2 3))
  • APPLY – 用一个列表作为参数调用一个函数: (apply '+ '(1 2 3))

问:我真的需要eval还是编译器/评估器已经是我真正想要的?

避免稍微高级用户的EVAL主要原因是:

  • 你要确保你的代码是编译的,因为编译器可以检查代码中的许多问题,并生成更快的代码,有时甚至更多(这是因素1000 ;-))更快的代码

  • 那么构build和需要评估的代码就不能尽早编译。

  • 评估任意用户input会导致安全问题

  • 有些使用EVAL进行评估可能会在错误的时间发生并产生构build问题

用一个简单的例子来解释最后一点:

 (defmacro foo (ab) (list (if (eql a 3) 'sin 'cos) b)) 

所以,我可能想写一个基于第一个参数的macros使用SINCOS

(foo 3 4)确实(sin 4)(foo 1 4)确实(cos 4)

现在我们可能有:

 (foo (+ 2 1) 4) 

这并没有给出预期的结果。

然后可以通过EVALuatingvariables来修复macrosFOO

 (defmacro foo (ab) (list (if (eql (eval a) 3) 'sin 'cos) b)) (foo (+ 2 1) 4) 

但是这仍然不起作用:

 (defun bar (ab) (foo ab)) 

variables的值在编译时只是未知的。

避免EVAL一般重要原因:它经常用于丑陋的黑客。

eval (任何语言)都不是邪恶的,就像电锯不是邪恶的一样。 这是一个工具。 它恰好是一个强大的工具,当被滥用时,可以切断肢体和剔骨(隐喻地说),但是对于程序员的工具箱中的许多工具也是如此:

  • goto和朋友
  • 基于锁的线程
  • 延续
  • macros(hygenic或其他)
  • 指针
  • 可重启的exception
  • 自修改代码
  • …和数千人的阵容。

如果你发现自己不得不使用任何这些强大的,有潜在危险的工具,问自己三次“为什么? 在一个链。 例如:

“为什么我必须使用eval ?” “因为富。” “为什么有必要?” “因为……”

如果你到了那个链条的末端,这个工具看起来仍然是正确的,那就去做吧。 记下地狱。 testing它的地狱。 仔细检查一遍又一遍的正确性和安全性。 但是这样做。

Eval是好的,只要你确切地知道什么是进入它。 任何进入它的用户input都必须经过检查和确认。 如果你不知道如何100%确定,那就不要这样做。

基本上,用户可以键入任何有关该语言的代码,并执行。 你可以想象自己可以做多less伤害。

“我应该什么时候使用eval ?” 可能是一个更好的问题。

简短的答案是“当你的程序打算在运行时写另一个程序,然后运行它”。 遗传编程是一个使用eval很有意义的例子。

国际海事组织, 这个问题不是特定于LISP的 。 这里是关于PHP的同一个问题的答案,它适用于LISP,Ruby和其他具有eval的语言:

eval()的主要问题是:

  • 潜在的不安全的input。 传递一个不可信的参数是一种失败的方法。 确保一个参数(或其一部分)是完全可信的往往不是一件容易的事情。
  • Trickyness。 使用eval()可以使代码更加灵活,因此更难以遵循。 引用Brian Kernighan“ debugging比编写代码要困难一倍,因此,如果您尽可能巧妙地编写代码,那么根据定义,您的debugging不够聪明

实际使用eval()的主要问题只有一个:

  • 缺乏经验的开发者没有足够的考虑就使用它。

从这里采取。

我觉得棘手的一块是一个惊人的观点。 对代码高尔夫和简洁的代码的痴迷总是导致“聪明”的代码(为此,evals是一个很好的工具)。 但是,为了便于阅读,你应该编写你的代码,而不是为了certificate你是聪明人,而不是为了节省纸张 (反正你不会打印它)。

然后在LISP中,有一些与运行eval的上下文有关的问题,所以不可信的代码可以访问更多的东西; 无论如何,这个问题似乎是常见的。

有很多很好的答案,但这里还有另外一个来自球拍实施者Matthew Flatt的观点:

http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html

他提出了许多已经涵盖的观点,但有些人可能会发现他的观点很有趣。

简介:使用它的上下文会影响eval的结果,但程序员经常不会考虑这个结果,从而导致意外的结果。

规范的答案是远离。 我觉得这很奇怪,因为它是一个原始的,在七个原始的(其他的是cons,car,cdr,if,eq和quote)的情况下,它的用途和爱情远远不够。

来自Lisp :“通常情况下,明确地打电话给eval就像是在机场的礼品店里买东西,等到最后一刻,就要付出高昂的价格来购买有限的二手货。

那么我什么时候使用eval? 一个正常的使用是通过评估(loop (print (eval (read))))在你的REPL中有一个REPL。 每个人都很好用。

但是,你也可以用macros来定义函数,这些macros将编译之后通过将eval和反引用结合起来进行评估。 你走

 (eval `(macro ,arg0 ,arg1 ,arg2)))) 

它会为你消灭上下文。

Swank(用于emacs粘液)充满了这些情况。 他们看起来像这样:

 (defun toggle-trace-aux (fspec &rest args) (cond ((member fspec (eval '(trace)) :test #'equal) (eval `(untrace ,fspec)) (format nil "~S is now untraced." fspec)) (t (eval `(trace ,@(if args `(:encapsulate nil) (list)) ,fspec ,@args)) (format nil "~S is now traced." fspec)))) 

我不认为这是一个肮脏的黑客。 我一直使用它来将macros重新集成到函数中。

另外几个关于Lisp的评论:

  • 它在全球环境下进行评估,失去了当地的环境。
  • 有时你可能会试图使用eval,当你真的打算使用读macros“#”。 在阅读时进行评估。

就像GOTO的“规则”一样:如果你不知道自己在做什么,你可以弄得一团糟。

除了仅从已知和安全的数据中构build出一些东西之外,还有一些问题是某些语言/实现不能充分地优化代码。 你可能会在eval内部解释代码。

Eval只是不安全的。 例如,您有以下代码:

 eval(' hello('.$_GET['user'].'); '); 

现在用户来到您的网站并inputurl http://example.com/file.php?user= ); $ is_admin = true; echo(

那么结果代码将是:

 hello();$is_admin=true;echo(); 

Eval不是邪恶的。 评估并不复杂。 这是一个编译你传递给它的列表的函数。 在大多数其他语言中,编译任意代码将意味着学习语言的AST并在编译器内部进行挖掘以找出编译器API。 在lisp中,您只需调用eval。

你什么时候使用它? 无论何时你需要编译一些东西,通常是一个在运行时接受,生成或修改任意代码的程序

你什么时候不该用它? 所有其他情况。

为什么你不需要使用它? 因为你会以不必要的复杂方式做某些事情,这可能会导致可读性,性能和debugging方面的问题。

是的,但是如果我是初学者,我怎么知道我是否应该使用它? 总是尝试实现你所需要的function。 如果这不起作用,请添加macros。 如果这仍然不起作用,那么评估!

按照这些规则,你永远不会做邪恶与eval 🙂

我非常喜欢Zak的回答 ,他已经掌握了这个问题的实质:在编写新语言,脚本或修改语言时使用eval 。 他没有进一步解释,所以我举个例子:

 (eval (read-line)) 

在这个简单的Lisp程序中,用户被提示input,然后他们input的内容被评估。 要编译这个程序, 整个符号定义必须存在,因为你不知道用户可以input哪些函数,所以你必须包含所有的符号定义。 这意味着如果你编译这个简单的程序,生成的二进制文件将是巨大的。

原则上,你甚至不能因为这个原因考虑这个可编辑的陈述。 一般来说,一旦你使用eval ,你在一个解释的环境中运行,代码不能再被编译。 如果你不使用eval,那么你可以像C程序一样编译一个Lisp或Scheme程序。 因此,在提交使用eval之前,您要确保您需要并且需要在解释的环境中。