2010-04-03 2 views
137

Я знаю, что программисты Lisp и Scheme обычно говорят, что следует избегать eval, если это строго необходимо. Я видел ту же рекомендацию для нескольких языков программирования, но я еще не видел список четких аргументов против использования eval. Где я могу найти отчет о потенциальных проблемах использования eval?Почему именно это зло?

Например, я знаю проблемы GOTO в процедурном программировании (делает программы нечитабельными и труднодоступными, затрудняет поиск проблем безопасности и т. Д.), Но я никогда не видел аргументов против eval.

Интересно, что те же аргументы против GOTO должны быть действительны в отношении продолжений, но я вижу, что Schemers, например, не скажут, что продолжения являются «злыми» - вы должны быть осторожны при их использовании. Они гораздо более склонны хмуриться над кодом, используя eval, чем при использовании кода с продолжением (насколько я вижу - я мог ошибаться).

+4

eval не является злом, но зло - это то, что делает eval. – Anurag

+9

@yar - Я думаю, что ваш комментарий указывает на то, что в мире есть очень одноразово-объектно-ориентированное мировоззрение. Вероятно, это допустимо для большинства языков, но будет отличаться в Common Lisp, где методы не относятся к классам и даже более разные в Clojure, где классы поддерживаются только через функции взаимодействия с Java. Jay отметил этот вопрос как Scheme, который не имеет встроенного понятия классов или методов (различные формы OO доступны в виде библиотек). – Zak

+3

@ Zak, вы правы, я знаю только те языки, которые я знаю, но даже если вы работаете с документом Word без использования стилей, вы не являетесь СУХОЙ. Я хотел бы использовать эту технологию, чтобы не повторять себя. OO не универсальна, правда ... –

ответ

25

Eval в порядке, до тех пор, как вы знаете ТОЧНО Что входит в это. Любой входящий в него вход пользователя ДОЛЖЕН быть проверен и проверен и все. Если вы не знаете, как быть на 100% уверенным, тогда не делайте этого.

В принципе, пользователь может ввести любой код для соответствующего языка и выполнить его. Вы можете себе представить, сколько урона он может сделать.

+1

Итак, если я на самом деле * генерирую * S-выражения, основанные на пользовательских вводах, используя алгоритм, который не будет напрямую копировать ввод пользователя, и если это будет проще и понятнее в какой-то конкретной ситуации, чем при использовании макросов или других методов, то я полагаю, что есть ничего «злого» об этом? Другими словами, единственные проблемы с eval одинаковы с SQL-запросами и другими методами, которые напрямую используют пользовательский ввод? – Jay

+10

Причина, по которой это называется «зло», состоит в том, что делать это неправильно, так хуже, чем делать другие вещи неправильно. И, как мы знаем, новички будут делать что-то неправильно. –

+3

Я бы не сказал, что код должен быть валидирован, прежде чем его угадать при любых обстоятельствах. Например, при реализации простого REPL вы, вероятно, просто будете вводить ввод в eval unchecked, и это не будет проблемой (конечно, при написании веб-REPL вам понадобится песочница, но это не так для нормального CLI-REPL, которые запускаются в системе пользователя). – sepp2k

4

Как правило GOTO: Если вы не знаете, что делаете, вы можете сделать беспорядок.

Кроме того, что только из чего-то из известных и безопасных данных возникает проблема, что некоторые языки/реализации не могут оптимизировать код достаточно. Вы можете получить интерпретируемый код внутри eval.

+0

Что это правило имеет отношение к GOTO? Есть ли какая-либо функция на любом языке программирования, с которым вы * не можете сделать беспорядок? – Ken

+2

@Ken: Нет правила GOTO, поэтому кавычки в моем ответе. Есть только догма для людей, которые боятся думать сами за себя. То же самое для eval. Я помню, как ускорить некоторые сценарии Perl, используя eval. Это один из инструментов вашего инструментария. Новички часто используют eval, когда другие языковые конструкции легче/лучше. Но избегать этого полностью, чтобы быть крутым и угодить догматическим людям? – stesch

3

Eval просто небезопасен. Например вы следующий код:

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

Теперь пользователь заходит на сайт и входит URL http://example.com/file.php?user=); $ is_admin = истина; эхо (

Затем полученный код будет:

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

Он говорил о Лиспе, а не о php – fmsf

+4

@fmsf Он говорил конкретно о Лиспе, но вообще о 'eval' на любом языке, который имеет его. – Skilldrick

+4

@fmsf - на самом деле это вопрос, не зависящий от языка. Это даже относится к статическим скомпилированным языкам, поскольку они могут имитировать eval, вызывая компилятор во время выполнения. –

40

eval (на любом языке) не является злом так же, как бензопила не является злом. Это инструмент. Это мощный инструмент, который при неправильном использовании может разрывать конечности и потрошить (метафорически), но то же самое можно сказать и для ma инструменты юбых в панели инструментов программиста в том числе:

  • goto и друзьями
  • блокировки на основе многопоточности
  • продолжений
  • макросов (гигиенических или других)
  • указатели
  • перезапускаемых исключений
  • самоуправления -модификационный код
  • ... и литой тысячи НСР.

Если вам нужно использовать любой из этих мощных, потенциально опасных инструментов, спросите себя три раза «почему?». в цепочке. Например:

«Почему я должен использовать eval?» «Из-за foo». «Почему нужен foo ?» «Потому что ...»

Если вы дойдете до конца этой цепи, и инструмент по-прежнему выглядит так, как будто это правильно, сделайте это. Извлеките Ад. Испытайте Ад из этого. Проверяйте правильность и безопасность дважды и снова. Но сделай это.

+0

Спасибо - вот что я слышал об eval раньше («спроси себя почему»), но я еще не слышал и не читал, каковы потенциальные проблемы. Теперь я вижу ответы на свои вопросы (проблемы безопасности и производительности). – Jay

+8

И читаемость кода.Eval может полностью винить поток кода и сделать его непонятным. –

+2

-1 потому что ...? Просто любезность флеш здесь, прежде чем сбросить турку? –

139

Существует несколько причин, по которым нельзя использовать EVAL.

Основная причина для начинающих: вам это не нужно.

Пример (предполагается, что Common Lisp):

вычислите выражение с разными операторами:

(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, но им это не нужно - поскольку выражения оцениваются, а также можно оценить удовольствие часть. В большинстве случаев использование EVAL показывает отсутствие понимания оценщика.

Это та же проблема с макросами. Часто начинающие пишут макросы, где они должны писать функции - не понимая, какие макросы действительно нужны, и не понимают, что функция уже выполняет эту работу.

Часто бывает неправильным инструментом для использования EVAL, и это часто указывает, что новичок не понимает обычные правила оценки Лиспа.

Если вы думаете, что вам нужно EVAL, а затем проверить, если что-то вроде FUNCALL, REDUCE или APPLY может быть использован вместо.

  • FUNCALL - вызвать функцию с аргументами: (funcall '+ 1 2 3)
  • REDUCE - вызвать функцию в списке значений и объединить результаты: (reduce '+ '(1 2 3))
  • APPLY - вызвать функцию со списком в качестве аргументов: (apply '+ '(1 2 3)) ,

Вопрос: мне действительно нужен eval или уже компилятор/оценщик, что я действительно хочу?

Основные причины, чтобы избежать EVAL чуть более продвинутых пользователей:

  • вы хотите, чтобы убедиться, что ваш код компилируется, поскольку компилятор может проверить код для многих проблем и генерирует более быстрый код, иногда MUCH MUCH MUCH (это коэффициент 1000 ;-)) более быстрый код

  • код, который был сконструирован и нуждается в оценке, не может быть скомпилирован как можно раньше.

  • Eval произвольного пользовательского ввода открывает Проблемы безопасности

  • некоторые используют оценки с EVAL может произойти в самое неподходящее время и создать строить проблемы

Чтобы объяснить последнюю точку с упрощенным Пример:

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

Итак, я могу написать макрос, который на основе первого параметра использует eit ее SIN или COS.

(foo 3 4)(sin 4) и (foo 1 4)(cos 4).

Теперь мы можем иметь:

(foo (+ 2 1) 4) 

Это не дает желаемого результата.

один, то может потребоваться ремонт макро FOO, вычислив переменную:

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

(foo (+ 2 1) 4) 

Но это по-прежнему не работает:

(defun bar (a b) 
    (foo a b)) 

Значение переменной просто не известна время компиляции.

Общая причина, по которой необходимо избегать EVAL: часто используется для уродливых хаков.

+3

Спасибо! Я просто не понял последнего момента (оценка в неподходящее время?) - купите, пожалуйста, немного? – Jay

+39

+1, так как это реальный ответ - люди отказываются от 'eval' просто потому, что не знают, что есть определенный язык или функция библиотеки, чтобы делать то, что они хотят делать. Аналогичный пример из JS: я хочу получить свойство объекта, используя динамическое имя, поэтому я пишу: 'eval (" obj. + "+ PropName)', когда я мог бы написать 'obj [propName]'. –

+0

Я понимаю, что вы имеете в виду сейчас, Райнер! Thansk! – Jay

13

IMO, Этот вопрос не относится к LISP. Вот ответ на тот же вопрос для PHP, и это относится к LISP, Ruby, и другой другой язык, который имеет Eval:

Основные проблемы с Eval() являются:

  • Потенциальный небезопасный ввод. Передача ненадежного параметра - это путь к ошибке . Часто нет тривиальной задачи , чтобы убедиться, что параметр (или его часть ) полностью доверен.
  • Trickyness. Использование eval() делает код умным, поэтому сложнее .Цитирую Керниган "Отладка в два раза так сложно, как писать код в первую очередь. Поэтому, если вы пишете код, как умно, как это возможно, вы, по определению, не достаточно умны, чтобы отладить его "

Основная проблема с фактическим использованием Eval() только один:

  • неопытные разработчики, которые используют его без достаточного учета.

Взятые из here.

Я думаю, что эта штука хитрости - удивительный момент. Одержимость кодовым гольф-полем и сжатым кодом всегда приводила к «умному» коду (для которого evals - отличный инструмент). Но вы должны написать свой код для удобочитаемости, IMO, а не для того, чтобы продемонстрировать, что вы умный, и не для экономии бумаги (вы все равно не будете ее печатать).

Затем в LISP существует некоторая проблема, связанная с контекстом, в котором выполняется eval, поэтому ненадежный код может получить доступ к большему количеству вещей; эта проблема, похоже, является общей.

+4

+1 для «не зависит от языка» –

+2

Проблема «злого ввода» с EVAL влияет только на языки, отличные от Lisp, потому что в этих языках eval() обычно принимает строковый аргумент, и вход пользователя обычно соединяется. Пользователь может включать цитату в свой ввод и выходить в сгенерированный код. Но в Lisp аргумент EVAL не является строкой, и пользовательский ввод не может убежать в код, если вы не абсолютно безрассудны (как в то же время вы проанализировали ввод с помощью READ-FROM-STRING для создания S-выражения, которое затем включаете в код EVAL, не цитируя его. Если вы его цитируете, нет возможности избежать цитаты). –

20

«Когда следует использовать eval?» может быть лучшим вопросом.

Короткий ответ: «Когда ваша программа предназначена для записи другой программы во время выполнения, а затем ее запуска». Genetic programming - пример ситуации, когда, вероятно, имеет смысл использовать eval.

11

Канонический ответ - держаться подальше. Который я нахожу странным, потому что он примитивен, и из семи примитивов (другие - минусы, автомобиль, cdr, if, eq и quote), он получает далеко и меньше наименьшее количество пользы и любви.

От На Лиспе:. «Обычно, вызов Eval явно, как покупать что-то в сувенирном магазине аэропорта Выждав до последнего момента, вы должны платить высокие цены на ограниченный выбор второсортных товаров. "

Итак, когда я использую eval? Одно нормальное использование - иметь REPL в вашем REPL, оценивая (loop (print (eval (read)))). В этом все прекрасно.

Но вы также можете определить функции в терминах макросов, которые будут оцениваться. после компиляция путем объединения eval с backquote. Вы идете

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

и оно убьет контекст для вас.

Суон (для emacs slime) полон этих случаев. Они выглядят так:

(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)))) 

Я не думаю, что это грязный хак. Я все время использую его для реинтеграции макросов в функции.

+1

Возможно, вы захотите проверить язык ядра;) – artemonster

7

Еще одна пара точек на Лисп Eval:

  • Он оценивает в рамках глобальной окружающей среды, теряя местный контекст.
  • Иногда у вас может возникнуть соблазн использовать eval, когда вы действительно хотели использовать read-macro '#.' который оценивается во время чтения.
+0

Я понимаю, что использование глобального env верно для Common Lisp и Scheme; это также верно для Clojure? – Jay

+1

@Jay, http://stackoverflow.com/questions/6221716/variable-scope-eval-in-clojure – desudesudesu

+2

В схеме (по крайней мере для R7RS, возможно, также для R6RS) вы должны передать среду для eval. – csl

12

Там было много больших ответов, но здесь другой взять от Матфея Флэтт, один из исполнителей Ракетка:

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

Он делает многие из пунктов, которые уже были охвачены, но некоторые люди тем не менее могут найти его интерес.

Резюме: Контекст, в котором он используется, влияет на результат eval, но часто не рассматривается программистами, что приводит к неожиданным результатам.

2

Eval не является злом. Eval не сложно. Это функция, которая компилирует список, который вы передаете ему. В большинстве других языков компиляция произвольного кода означала бы изучение АСТ языка и поиск во внутренности компилятора для определения API компилятора. В lisp, вы просто позвоните eval.

Когда вы должны использовать его? Всякий раз, когда вам нужно что-то компилировать, обычно программа, которая принимает, генерирует или изменяет произвольный код во время выполнения.

Когда вы не должны использовать его? Все остальные случаи.

Почему бы вам не использовать его, когда вам это не нужно? Потому что вы делаете что-то излишне сложным способом, что может вызвать проблемы для читаемости, производительности и отладки.

Да, но если я новичок, как узнать, следует ли мне его использовать? Всегда старайтесь выполнять то, что вам нужно, с функциями. Если это не работает, добавьте макросы. Если это все еще не работает, тогда eval!

Следуйте этим правилам, и вы никогда не будете делать зло Eval :)

0

Мне нравится Zak's answer очень много, и он получил по сути дела: Eval используется, когда вы пишете новый язык , сценарий или изменение языка. Он не объясняет далее, так что я приведу пример:

(eval (read-line)) 

В этой простой программы на Лиспе, пользователю предлагается для ввода, а затем все, что они входят оценивается. Для этого необходимо выполнить набор определений символов, если программа скомпилирована, поскольку вы не знаете, какие функции пользователь может ввести, поэтому вы должны включить их все. Это означает, что если вы скомпилируете эту простую программу, результирующий двоичный файл будет гигантским.

В принципе, вы даже не можете считать это компилятивным утверждением по этой причине. В общем, как только вы используете eval, вы работаете в интерпретируемой среде, и код больше не может быть скомпилирован. Если вы не используете eval, тогда вы можете скомпилировать программу Lisp или Scheme, как программу C.Поэтому вы хотите убедиться, что хотите и должны находиться в интерпретируемой среде, прежде чем совершать использование eval.

Смежные вопросы