¿Se supone que 'eval' es desagradable?
Pregunta
He estado usando la función eval
de ruby ??muchas veces. Pero he escuchado a personas decir que eval
s son desagradables. Cuando me preguntaron por qué y cómo, nunca pude encontrar una razón convincente para no usarlo. ¿Son realmente desagradables? Si es así, ¿de qué manera? ¿Qué son posibles "más seguros"? opciones para evaluar?
Solución
Si está eval
utilizando una cadena enviada o modificable por el usuario, esto equivale a permitir la ejecución de código arbitrario. Imagine si la cadena contiene una llamada del sistema operativo a rm -rf /
o similar. Dicho esto, en situaciones en las que sabe que las cadenas están restringidas adecuadamente, o su intérprete de Ruby está protegido adecuadamente, o idealmente ambos, eval
puede ser extraordinariamente poderoso.
El problema es análogo a inyección SQL , si está familiarizado. La solución aquí es similar a la solución al problema de inyección (consultas parametrizadas). Es decir, si se sabe que las declaraciones que le gustaría eval
son de una forma muy específica, y no all de la declaración deben ser enviadas por el usuario, solo un pocas variables, una expresión matemática o similar, puede tomar estas pequeñas piezas del usuario, desinfectarlas si es necesario, luego evaluar la declaración de plantilla segura con la entrada del usuario enchufada en los lugares apropiados.
Otros consejos
En Ruby hay varios trucos que podrían ser más apropiados que eval ()
:
- Hay
#send
que le permite llamar a un método cuyo nombre tiene como cadena y pasarle parámetros. -
yield
le permite pasar un bloque de código a un método que se ejecutará en el contexto del método de recepción. - A menudo, el simple
Kernel.const_get (" String ")
es suficiente para obtener la clase cuyo nombre tiene como cadena.
Creo que no soy capaz de explicarlos adecuadamente en detalle, así que solo te di las pistas, si te interesa, googlearás.
eval
no solo es inseguro (como se ha señalado en otra parte), también es lento. Cada vez que se ejecuta, el AST del código ed eval
debe analizarse (y, por ejemplo, JRuby, convertirse en bytecode) de nuevo, lo que es una operación de cadena pesada y probablemente también sea mala para el caché localidad (bajo el supuesto de que un programa en ejecución no eval
mucho, y las partes correspondientes del intérprete son, por lo tanto, frías en caché, además de ser grandes).
¿Por qué hay eval
en Ruby, preguntas? "Porque podemos" sobre todo: de hecho, cuando se inventó eval
(para el lenguaje de programación LISP), fue principalmente para mostrar ! Más concretamente, el uso de eval
es The Right Thing cuando desea " agregar un intérprete a su intérprete " ;, para tareas de metaprogramación como escribir un preprocesador, un depurador o un motor de plantillas. La idea común para tales aplicaciones es dar un poco de código Ruby y llamar a eval
, y seguramente supera reinventar e implementar un lenguaje de juguete específico de dominio, un escollo también conocido como Décima regla de Greenspun . Las advertencias son: tenga cuidado con los costos, por ejemplo, para un motor de plantillas, realice todas sus eval
al momento del inicio, no al tiempo de ejecución; y no eval
código no confiable a menos que sepa cómo "domesticar" es decir, seleccionar y aplicar un subconjunto seguro del lenguaje de acuerdo con la teoría de disciplina de capacidad . Este último es un lote de trabajo realmente difícil (vea, por ejemplo, cómo se hizo eso para Java ; desafortunadamente no estoy al tanto de tal esfuerzo para Ruby).
Hace que la depuración sea difícil. Hace que la optimización sea difícil. Pero, sobre todo, generalmente es una señal de que hay una mejor manera de hacer lo que intente hacer.
Si nos dice lo que está tratando de lograr con eval
, puede obtener algunas respuestas más relevantes relacionadas con su escenario específico.
Eval es una característica increíblemente poderosa que debe usarse con cuidado. Además de los problemas de seguridad señalados por Matt J, también encontrará que la depuración del código evaluado en tiempo de ejecución es extremadamente difícil. Un problema en un bloque de código evaluado en tiempo de ejecución será difícil de expresar para el intérprete, por lo que buscarlo será difícil.
Dicho esto, si te sientes cómodo con ese problema y no te preocupa el problema de seguridad, entonces no debes evitar usar una de las características que hace que Ruby sea tan atractivo como es.
En ciertas situaciones, un eval
bien ubicado es inteligente y reduce la cantidad de código requerido. Además de las preocupaciones de seguridad que ha mencionado Matt J, también debe hacerse una pregunta muy simple:
Cuando todo está dicho y hecho, ¿alguien más puede leer su código y entender lo que hizo?
Si la respuesta es no, entonces lo que ha ganado con un eval
se deja de lado por mantenimiento. Este problema no solo es aplicable si trabaja en un equipo, sino que también es aplicable a usted: desea poder revisar su código meses, si no años, y saber lo que hizo.
Si está pasando algo de lo que obtiene del "exterior" a eval
, estás haciendo algo mal, y es muy desagradable. Es muy difícil escapar del código lo suficiente como para que sea seguro, por lo que lo consideraría bastante inseguro. Sin embargo, si está utilizando eval para evitar la duplicación u otras cosas similares, como el siguiente ejemplo de código, está bien usarlo.
class Foo
def self.define_getters(*symbols)
symbols.each do |symbol|
eval "def #{symbol}; @#{symbol}; end"
end
end
define_getters :foo, :bar, :baz
end
Sin embargo, al menos en Ruby 1.9.1, Ruby tiene métodos de metaprogramación realmente potentes, y podría hacer lo siguiente en su lugar:
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
Para la mayoría de los propósitos, desea utilizar estos métodos, y no se necesita escapar.
La otra cosa mala de eval
es el hecho de que (al menos en Ruby) es bastante lento, ya que el intérprete necesita analizar la cadena y luego ejecutar el código dentro del enlace actual. Los otros métodos llaman a la función C directamente y, por lo tanto, debería obtener un gran aumento de velocidad.