«Eval» должен быть противным? - PullRequest
23 голосов
/ 12 марта 2009

Я много раз использовал eval функцию ruby. Но я слышал, как люди говорят, что «1002» противны. Когда меня спросили, почему и как, у меня никогда не было убедительной причины не использовать его. Они действительно противные? Если да, то каким образом? Какие возможные "более безопасные" варианты для eval?

Ответы [ 7 ]

33 голосов
/ 12 марта 2009

Если вы eval используете строку, представленную пользователем или изменяемую пользователем, это равносильно разрешению выполнения произвольного кода. Представьте, что в строке содержится вызов ОС rm -rf / или аналогичный. Тем не менее, в ситуациях, когда вы знаете, что строки соответствующим образом ограничены, или ваш интерпретатор Ruby правильно помещен в песочницу, или в идеале оба, eval может быть чрезвычайно мощным.

Проблема аналогична SQL-инъекция , если вы знакомы. Решение здесь аналогично решению проблемы внедрения (параметризованные запросы). То есть, если известно, что операторы, которые вы хотели бы eval, имеют очень специфическую форму, и пользователь не должен представлять все операторов, только несколько переменных, математическое выражение или что-то подобное, вы можете взять у пользователя эти маленькие кусочки, при необходимости очистить их, а затем оценить безопасный шаблонный шаблон с подключенным пользовательским вводом в соответствующих местах.

11 голосов
/ 12 марта 2009

В Ruby есть несколько уловок, которые могут быть более подходящими, чем eval():

  1. Существует #send, который позволяет вам вызывать метод, имя которого у вас есть в виде строки, и передавать ему параметры.
  2. yield позволяет передавать блок кода методу, который будет выполняться в контексте метода получения.
  3. Часто простого Kernel.const_get("String") достаточно, чтобы получить класс, имя которого у вас есть в виде строки.

Я думаю, что не могу объяснить их должным образом подробно, поэтому я просто дал вам подсказки, если вам интересно, вы будете Google.

10 голосов
/ 27 августа 2012

eval не только небезопасен (как было указано в другом месте), но и медленен. Каждый раз, когда он выполняется, AST eval ed-кода необходимо заново анализировать (и, например, для JRuby, превращенного в байт-код), что является строковой операцией и, вероятно, также плохо для локальности кэша (в предположении что работающая программа не eval много, и соответствующие части интерпретатора, таким образом, кешируются, помимо того, что они большие).

Почему в Ruby вообще есть eval, спросите вы? В основном «потому что мы можем» - фактически, когда был изобретен eval (для языка программирования LISP), это было в основном для шоу ! Более того, использование eval - это правильная вещь, когда вы хотите «добавить интерпретатор в ваш интерпретатор» для задач метапрограммирования, таких как написание препроцессора, отладчика или шаблонизатора. Обычная идея таких приложений - помассировать некоторый код Ruby и вызвать на нем eval, что наверняка превосходит переизобретение и реализацию игрушечного языка, специфичного для предметной области, ловушку, также известную как Десятое правило Гринспуна . Предостережения: остерегайтесь затрат, например, для движка шаблонов, делайте все свои eval во время запуска, а не во время работы; и не eval ненадежный код, если вы не знаете, как «приручить» его, то есть выбрать и применить безопасное подмножество языка в соответствии с теорией дисциплины способностей . Последний - это много действительно сложной работы (см., Например, , как это было сделано для Java ; я, к сожалению, не знаю о таких усилиях для Ruby).

7 голосов
/ 12 марта 2009

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

Если вы сообщите нам, чего пытаетесь достичь с помощью eval, вы можете получить более релевантные ответы, касающиеся вашего конкретного сценария.

6 голосов
/ 12 марта 2009

Eval - невероятно мощная функция, которую следует использовать осторожно. Помимо проблем безопасности, указанных Мэттом J, вы также обнаружите, что отладка кода, оцененного во время выполнения, чрезвычайно сложна. Проблема в блоке кода, оцененного во время выполнения, будет трудно выразить интерпретатору, поэтому ее поиск будет затруднен.

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

5 голосов
/ 12 марта 2009

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

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

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

0 голосов
/ 14 декабря 2009

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

...