Я использовал функцию eval
Ruby много раз. Но я слышал, как люди говорили, что eval
противны. Когда его спросили, почему и как, я никогда не смогу получить убедительную причину не использовать его. Действительно ли они противны? Если да, то каким образом? Каковы возможные "безопасные" варианты для eval?
Предполагается, что "eval" является противным?
Ответ 1
Если вы eval
введите строку, представленную пользователем или модифицируемую пользователем, это равносильно разрешению произвольного выполнения кода. Представьте, что строка содержала вызов ОС на rm -rf /
или аналогичный. Тем не менее, в ситуациях, когда вы знаете, что строки соответствующим образом ограничены, или ваш интерпретатор Ruby изолирован соответствующим образом, или, в идеале, оба параметра eval
могут быть чрезвычайно мощными.
Проблема аналогична SQL-инъекции, если вы знакомы. Решение здесь похоже на решение проблемы впрыска (параметризованные запросы). То есть, если утверждения, которые вы хотели бы eval
, как известно, имеют очень специфическую форму, и не все инструкции должны быть представлены пользователем, только несколько переменных, математическое выражение или подобное, вы можете возьмите эти маленькие кусочки от пользователя, при необходимости обработайте их, затем оцените инструкцию безопасного шаблона с подключенным пользователем в соответствующих местах.
Ответ 2
В Ruby есть несколько трюков, которые могут быть более подходящими, чем eval()
:
- Существует
#send
, который позволяет вам вызвать метод, имя которого у вас есть как строка, и передать ему параметры. -
yield
позволяет передать блок кода методу, который будет выполняться в контексте метода приема. - Часто простой
Kernel.const_get("String")
достаточно, чтобы получить класс, имя которого у вас есть как строка.
Я думаю, что не могу объяснить их должным образом подробно, поэтому я просто дал вам подсказки, если вам интересно, что вы будете google.
Ответ 3
eval
не только небезопасен (как было указано в другом месте), но и медленным. Каждый раз, когда он выполняется, AST-код eval
ed должен быть проанализирован (и, например, JRuby, обращен к байт-коду) заново, что является тяжелой операцией, а также, вероятно, плохо для локализации кэша (в предположении что запущенная программа не имеет eval
много, и соответствующие части интерпретатора, таким образом, являются кэш-холодными, в дополнение к большому).
Почему в Ruby существует eval
вообще, спросите вы? "Потому что мы можем" в основном - на самом деле, когда eval
был изобретен (для языка программирования LISP), он был в основном для показа! Более того, использование eval
- это правильная вещь, когда вы хотите "добавить переводчика в свой интерпретатор", для задач метапрограммирования, таких как запись препроцессора, отладчика или механизма шаблонов. Общей идеей для таких приложений является массаж некоторого кода Ruby и вызов eval
на нем, и он уверен, что будет изобретать и внедрять язык игрушек, специфичный для домена, ловушку, также известную как Greenspun Десятое правило. Оговорки: остерегайтесь затрат, например, для механизма шаблонов, все ваши eval
ing во время запуска не запускаются; и не eval
ненадежный код, если вы не знаете, как его "приручить", т.е. выбрать и обеспечить безопасное подмножество языка в соответствии с теорией дисциплины возможностей, К сожалению, это очень сложная работа (см., Например, как это было сделано для Java, к сожалению я не знаю о таких усилиях для Ruby).
Ответ 4
Это затрудняет отладку. Это затрудняет оптимизацию. Но, прежде всего, это обычно признак того, что есть лучший способ сделать то, что вы пытаетесь сделать.
Если вы сообщите нам, что вы пытаетесь выполнить с помощью eval
, вы можете получить более релевантные ответы, относящиеся к вашему конкретному сценарию.
Ответ 5
Eval - невероятно мощная функция, которую следует использовать осторожно. Помимо проблем безопасности, отмеченных Matt J, вы также обнаружите, что отлаживаемый код времени исполнения очень сложный. Проблема в проверенном кодовом блоке времени исполнения будет затруднительна для интерпретатора, поэтому поиск этого будет затруднен.
Если вы согласны с этой проблемой и не обеспокоены проблемой безопасности, вам не следует избегать использования одной из функций, которая делает рубин привлекательным, как есть.
Ответ 6
В определенных ситуациях хорошо расположенный eval
умный и уменьшает количество требуемого кода. В дополнение к проблемам безопасности, о которых упоминал Мэтт Дж, вам также нужно задать себе один очень простой вопрос:
Когда все будет сказано и сделано, кто-нибудь еще сможет прочитать ваш код и понять, что вы сделали?
Если ответ отрицательный, то то, что вы получили с помощью 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, и поэтому вы должны получить довольно высокую скорость.