2009-03-12 7 views
21

He estado usando la característica eval de ruby ​​muchas veces. Pero he escuchado gente diciendo que eval s son desagradables. Cuando me preguntan, por qué y cómo, nunca podría obtener una razón convincente para no usarlo. ¿Son realmente desagradables? Si es así, ¿de qué manera? ¿Cuáles son las posibles opciones "más seguras" para evaluar?¿Se supone que 'eval' es desagradable?

+1

Las opciones más seguras dependen de lo que esté utilizando eval para hacer, ¿puede ser más específico? – rampion

+0

No hay nada específico que quiera pedir. Pero por "más seguro" me refiero a algo que le da los mismos resultados evitando o reduciendo las vulnerabilidades. – Chirantan

+0

se envía generalmente más seguro de usar que eval? –

Respuesta

29

Si está eval escribiendo una cadena enviada por, o modificable por el usuario, esto es equivalente a permitir la ejecución de código arbitrario. Imagine si la cadena contiene una llamada al sistema operativo a rm -rf / o similar. Dicho esto, en situaciones en las que se sabe que las cadenas están limitadas adecuadamente, o que el intérprete de Ruby está en una zona de pruebas adecuada, o idealmente ambas, eval puede ser extraordinariamente poderosa.

El problema es análogo a SQL injection, 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 desea eval son de una forma muy específica, y no , todas las de la declaración deben ser enviadas por el usuario, solo unas pocas variables, una expresión matemática o similar, puede tome estas piezas pequeñas del usuario, desinféctelas si es necesario, luego evalúe la declaración de la plantilla segura con la entrada del usuario conectada en los lugares apropiados.

+0

Y la desinfección ha demostrado ser extraordinariamente difícil de hacer * correctamente * para * todos * casos. ¡Buena respuesta! – krosenvold

+2

'rm -rf --no-preserve-root /' hoy en día. –

7

Hace que la depuración sea difícil. Hace la optimización difícil. Pero, sobre todo, generalmente es una señal de que hay una mejor manera de hacer lo que sea que intentes hacer.

Si nos dice lo que está tratando de lograr con eval, puede obtener respuestas más relevantes relacionadas con su situación específica.

5

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 el código evaluado de tiempo de ejecución de depuració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 será difícil buscarlo.

Dicho esto, si se siente cómodo con ese problema y no está preocupado por el problema de seguridad, entonces no debe evitar el uso de una de las características que hace que Ruby sea tan atractivo como lo es.

5

En ciertas situaciones, un bien ubicado eval es inteligente y reduce la cantidad de código requerido. Además de las preocupaciones de seguridad que mencionó 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 abandona para su mantenimiento. Este problema no solo es aplicable si trabaja en equipo, sino que también es aplicable a usted; desea poder mirar hacia atrás en los meses de su código, si no en los próximos años, y saber lo que hizo.

10

En Rubí hay varios trucos que podrían ser más apropiados que eval():

  1. Hay #send que le permite llamar a un método cuyo nombre tiene como cadena y pasar parámetros a la misma.
  2. 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.
  3. 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 le acabo de dar las pistas, si está interesado, va a googlear.

-1

Si está pasando todo lo que obtiene del "exterior" al eval, está haciendo algo mal, y es muy desagradable. Es muy difícil de escapar el código lo suficiente como para que sea seguro, por lo que lo consideraría bastante inseguro. Sin embargo, si usa 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 muy poderosos métodos de programación meta, 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 propósitos, que desea utilizar estos métodos, y no se necesita escapar.

Lo malo 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 directamente a la función C y, por lo tanto, debe obtener un aumento considerable de la velocidad.

+0

'define_method' ha existido por mucho tiempo; no es una característica 1.9.Si está utilizando 'eval', probablemente solo signifique que no conoce la herramienta correcta para el trabajo. – Chuck

+0

Vaya, lo siento, ahora veo que no estaba claro para nada lo que quise decir. En Ruby 1.9 tiene varios métodos nuevos de meta-programación, no me refería específicamente a 'definir_metodo'. Pero estoy de acuerdo con la mayoría de las otras personas que respondieron la publicación, el OP debe indicar por qué quiere usar 'eval'. – henrikhodne

+0

-1. ¡Su primer 'define_getters' puede ser una vulnerabilidad! Ver un ejemplo muy similar en http://stackoverflow.com/questions/3003328/how-do-i-use-class-eval/3003509#3003509 –

8

eval no solo es inseguro (como se ha señalado en otra parte), sino que también es lento. Cada vez que se ejecuta, la AST del código de edición eval debe analizarse (y, por ejemplo, JRuby, se volvió a bytecode) de nuevo, que es una operación de cadena pesada y probablemente también sea mala para la localidad de caché (bajo la suposición de que el programa en ejecución no es mucho eval, y las partes correspondientes del intérprete son, por lo tanto, caché-fría, además de ser grandes).

¿Por qué hay eval en absoluto en Ruby, usted pregunta? "Porque podemos" principalmente - De hecho, cuando se inventó eval (para el lenguaje de programación LISP), era mostly for show! Más concretamente, usar eval es Lo correcto 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 masajes a un código de Ruby y llamar al eval, y seguro que es mejor que reinventar e implementar un lenguaje de juguete específico del dominio, un error también conocido como Greenspun's Tenth Rule. Las advertencias son: tenga cuidado con los costos, por ejemplo, para un motor de plantillas, haga todos sus eval en el momento del inicio, no en el tiempo de ejecución; y no codifique eval que no es de confianza, a menos que sepa cómo "domarlo", es decir, seleccionar y aplicar un subconjunto seguro del lenguaje de acuerdo con la teoría de capability discipline. Este último es un lote de trabajo realmente difícil (ver, por ejemplo, how that was done for Java; no estoy al tanto de ningún esfuerzo por Ruby por desgracia).

+1

No diría que fue inventado "para mostrar" en Lisp. Primero fue una construcción en el ámbito de la informática teórica que luego se utilizó como modelo de un intérprete (ver el [artículo de McCarty] (http://www-formal.stanford.edu/jmc/history/lisp/node3). html) en el sitio al que está enlazando). –

Cuestiones relacionadas