Я использовал eval
особенность рубина много раз. Но я слышал, как люди говорили, что eval
противны. Когда меня спрашивали, почему и как, я никогда не мог найти убедительной причины не использовать его. Они действительно противные? Если да, то каким образом? Какие возможные «более безопасные» варианты для eval?
"Eval" должен быть противным?
Ответы (7)
Если вы eval
обрабатываете строку, отправленную пользователем или изменяемую пользователем, это равносильно разрешению выполнения произвольного кода. Представьте, что строка содержит вызов ОС к rm -rf /
или подобному. Тем не менее, в ситуациях, когда вы знаете, что строки надлежащим образом ограничены, или ваш интерпретатор Ruby соответствующим образом изолирован в песочнице, или в идеале и то, и другое, eval
может быть чрезвычайно мощным.
Проблема аналогична SQL-инъекции, если вы знакомы. Решение здесь аналогично решению проблемы внедрения (параметризованные запросы). То есть, если известно, что операторы, которые вы хотите eval
, имеют очень конкретную форму, и не все операторы должны быть отправлены пользователем, только несколько переменных, математическое выражение, или что-то подобное, вы можете взять эти маленькие кусочки от пользователя, очистить их, если необходимо, затем оценить безопасный шаблонный оператор, вставив пользовательский ввод в соответствующие места.
rm -rf --no-preserve-root /
в наши дни.
- person Cole Johnson; 17.02.2015
В Ruby есть несколько уловок, которые могут быть более подходящими, чем eval()
:
- Существует
#send
, который позволяет вам вызывать метод, имя которого у вас есть в виде строки, и передавать ему параметры. yield
позволяет передать блок кода методу, который будет выполняться в контексте метода-получателя.- Часто простого
Kernel.const_get("String")
достаточно, чтобы получить класс, имя которого у вас есть в виде строки.
Думаю, я не могу подробно их объяснить, поэтому я просто дал вам подсказки, если вам интересно, вы можете погуглить.
eval
не только небезопасен (как уже указывалось в другом месте), но и медленен. Каждый раз, когда он выполняется, AST кода eval
ed необходимо заново анализировать (и, например, для JRuby, преобразованного в байт-код), что представляет собой операцию с большим количеством строк и также, вероятно, плохо влияет на локальность кеша (в предположении, что запущенный программа не очень много eval
, и соответствующие части интерпретатора, таким образом, не только имеют большой размер, но и слишком холодны для кеша).
Вы спросите, почему в Ruby вообще есть eval
? "Потому что мы можем" в основном - На самом деле, когда eval
был изобретен (для языка программирования LISP), он был в основном для шоу! Более того, использование eval
- это то, что нужно, когда вы хотите «добавить интерпретатор в свой интерпретатор» для задач метапрограммирования, таких как написание препроцессора, отладчика или движка шаблонов. Общая идея для таких приложений состоит в том, чтобы массировать некоторый код Ruby и вызывать для него eval
, и это определенно лучше, чем изобретать заново и внедрять специфичный для предметной области язык игрушек, - ловушка, также известная как Десятое правило Гринспана. Предостережения: остерегайтесь затрат, например, для движка шаблонов, делайте все eval
во время запуска, а не во время выполнения; и не eval
ненадежный код, если вы не знаете, как его "приручить", т.е. выбрать и обеспечить безопасное подмножество языка в соответствии с теорией дисциплина возможностей. Последнее - это много действительно сложной работы (см., Например, как это было сделано для Java; к сожалению, мне не известно о каких-либо подобных усилиях для Ruby).
Это затрудняет отладку. Это затрудняет оптимизацию. Но, прежде всего, это обычно знак того, что есть лучший способ делать то, что вы пытаетесь сделать.
Если вы расскажете нам, чего вы пытаетесь достичь с помощью eval
, вы можете получить более релевантные ответы, относящиеся к вашему конкретному сценарию.
Eval - невероятно мощная функция, которую следует использовать с осторожностью. Помимо проблем безопасности, указанных Мэттом Дж., Вы также обнаружите, что отладка кода, оцениваемого во время выполнения, чрезвычайно сложна. Интерпретатору будет сложно выразить проблему в блоке кода, оцениваемом во время выполнения, поэтому будет сложно ее найти.
При этом, если вас устраивает эта проблема и вас не беспокоит проблема безопасности, вам не следует избегать использования одной из функций, которая делает Ruby столь же привлекательным, как он есть.
В определенных ситуациях удачно размещенный eval
является умным и сокращает объем необходимого кода. В дополнение к проблемам безопасности, упомянутым Мэттом Дж., Вам также необходимо задать себе один очень простой вопрос:
Когда все будет сказано и сделано, сможет ли кто-нибудь еще прочитать ваш код и понять, что вы сделали?
Если ответ отрицательный, то то, что вы получили с eval
, заброшено из-за ремонтопригодности. Эта проблема не только применима, если вы работаете в команде, но также применима к вам - вы хотите иметь возможность оглянуться на свой код через несколько месяцев, если не лет, и знать, что вы сделали.
Если вы передаете 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 напрямую, и поэтому вы должны получить значительный прирост скорости.
define_method
существует уже давно - это не функция 1.9. Если вы используете eval
, это, вероятно, просто означает, что вы не знаете, какой инструмент подходит для работы.
- person Chuck; 14.12.2009
define_method
. Но я согласен с большинством других людей, ответивших на этот пост, OP должен указать, почему он хочет использовать eval
.
- person sarahhodne; 14.12.2009
define_getters
может быть уязвимостью! См. Очень похожий пример в stackoverflow.com/questions/3003328/how-do-i-use-class-eval/
- person Marc-André Lafortune; 31.01.2013
send
на самом деле не является альтернативой, поскольку send только отправляет сообщение объекту (то есть вызывает метод).eval
оценивает код. Любой код. - person Chirantan   schedule 19.03.2014