É 'eval' suposto ser desagradável?
Pergunta
Eu tenho usado o recurso eval
de rubi vezes muitos um. pessoas, mas eu ouvi dizendo eval
s são desagradáveis. Quando perguntado, por que e como, eu nunca poderia ter uma razão convincente para não usá-lo. eles são realmente desagradável? Se sim, de que maneira? Quais são as possíveis opções "seguras" para eval?
Solução
Se você está eval
ing uma string apresentado por, ou modificável pelo usuário, isso equivale a permitir a execução de código arbitrário. Imagine-se a cadeia continha uma chamada de sistema operacional para rm -rf /
ou similar. Dito isto, em situações onde você sabe que as cordas são apropriadamente restrita, ou o seu interpretador Ruby está no modo seguro de forma adequada, ou, idealmente ambos, eval
pode ser extremamente poderosa.
O problema é análogo ao SQL injeção , se você está familiarizado. A solução aqui é semelhante à solução para o problema de injecção (consultas parametrizadas). Ou seja, se as declarações que você gostaria de eval
são conhecidos por serem de uma forma muito específica, e não todas da declaração precisa ser enviadas pelo usuário, apenas algumas variáveis, uma expressão matemática, ou similar, você pode tomar nestes pequenos pedaços do usuário, higienizar-los se necessário, em seguida, avaliar a declaração modelo de seguro com a entrada do usuário conectado nos lugares apropriados.
Outras dicas
Em Ruby existem vários truques que podem ser mais apropriado do que eval()
:
- Há
#send
que lhe permite chamar um método cujo nome você tem como cordas e passar parâmetros para ele. -
yield
permite que você passe um bloco de código a um método que será executado no contexto do método de recepção. - Muitas vezes, o
Kernel.const_get("String")
simples é suficiente para obter a classe, cujo nome você tem como string.
Eu acho que eu não sou capaz de explicá-los adequadamente em detalhes, então eu apenas dei-lhe as dicas, se você estiver interessado você google.
eval
não só é inseguro (como tem sido apontado em outro lugar), também é lento. Cada vez que é executado, o AST das necessidades de código eval
ed a ser analisado (e para, por exemplo JRuby, virou-se para bytecode) de novo, que é uma operação corda-pesado e também é provavelmente ruim para localidade cache (sob a suposição de que uma corrida programa não eval
muito, e as partes correspondentes do intérprete são, portanto, cache-frio, além de ser grande).
Por que há eval
em tudo em Ruby, você pergunta? "Porque nós podemos" principalmente - Na verdade, quando eval
foi inventado (para a linguagem de programação LISP), foi principalmente para a mostra ! Mais ao ponto, usando eval
é a coisa certa quando você quiser "adicionar um intérprete em seu intérprete", para metaprogramação tarefas como escrever um pré-processador, um depurador ou um motor de templates. A idéia comum para tais aplicações é a massagem algum código e chamada eval
Ruby on-la, e com certeza é melhor reinventar e implementar uma linguagem de brinquedo de domínio específico, uma armadilha também conhecido como regra décimo de Greenspun . As advertências são: cuidado com os custos, por exemplo, para um motor de templates, fazer todas as suas eval
ing no momento da inicialização não executar tempo; e fazer código não eval
não confiável se você não sabe como "domar"-lo, ou seja, selecionar e aplicar um subconjunto segura do idioma de acordo com a teoria de capacidade de disciplina . O último é uma muito de trabalho realmente difícil (ver, por exemplo como isso foi feito para Java , eu não estou ciente de qualquer esforço para ruby ??infelizmente)
Ele torna a depuração difícil. Faz otimização difícil. Mas acima de tudo, é geralmente um sinal de que há uma maneira melhor de fazer o que você está tentando fazer.
Se você nos diga o que você está tentando realizar com eval
, você pode obter algumas respostas mais relevantes relacionados com a sua situação específica.
Eval é um recurso incrivelmente poderoso que deve ser usado com cuidado. Além das questões de segurança apontado por Matt J, você também vai descobrir que a depuração do código de tempo de execução avaliado é extremamente difícil. Um problema em um bloco de código de tempo de execução avaliada será difícil para o intérprete de expressar -. Modo procurando será difícil
Dito isto, se você está confortável com essa questão, e não estão preocupados com a questão da segurança, então você não deve evitar o uso de uma das características que torna ruby ??tão atraente como ela é.
Em certas situações, uma eval
bem colocado é inteligente e reduz a quantidade de código necessário. Além das preocupações de segurança que foram mencionados por Matt J, você também precisa perguntar a si mesmo uma pergunta muito simples:
Quando está tudo dito e feito, lata qualquer outra pessoa ler o seu código e entender o que você fez?
Se a resposta é não, então o que você ganhou com um eval
é abandonado para manutenção. Esta questão não só é aplicável se você trabalhar em uma equipe, mas também é aplicável a você -. Você quer ser capaz de olhar para trás em seus meses de código, se não anos a partir de agora, e sabe o que fez
Se você estiver passando qualquer coisa que você começa a partir do "fora" para eval
, você está fazendo algo errado, e é muito desagradável. É muito difícil escapar o suficiente código para que ele seja seguro, então eu considero que é bastante inseguro. No entanto, se você estiver usando eval para evitar duplicação ou outras coisas semelhantes, como exemplo de código a seguir, que é ok para usá-lo.
class Foo
def self.define_getters(*symbols)
symbols.each do |symbol|
eval "def #{symbol}; @#{symbol}; end"
end
end
define_getters :foo, :bar, :baz
end
No entanto, pelo menos em Ruby 1.9.1, Ruby tem realmente poderosos métodos de meta-programação, e você pode fazer o seguinte em vez disso:
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
Na maioria dos casos, você quer usar esses métodos, e não há como escapar é necessário.
A outra coisa ruim sobre eval
é o fato de que (pelo menos em Ruby), é bastante lento, como o intérprete precisa analisar a cadeia, e em seguida, executar o código dentro a ligação atual. Os outros métodos chama a função C diretamente, e, portanto, você deve obter um bom aumento de velocidade.