Hay una serie de cosas 'ordenadas' que se pueden hacer en lenguajes dinámicos que se pueden guardar en partes del código que no son inmediatamente obvias para otro programador o auditor en cuanto a la funcionalidad de un determinado código.
Considere esta secuencia en irb (shell ruby interactivo):
irb(main):001:0> "bar".foo
NoMethodError: undefined method `foo' for "bar":String
from (irb):1
from /usr/bin/irb:12:in `<main>'
irb(main):002:0> class String
irb(main):003:1> def foo
irb(main):004:2> "foobar!"
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> "bar".foo
=> "foobar!"
Lo que sucedió allí es que intenté llamar al método foo
en una constante de cadena. Esto falló. Luego abrí la clase String y definí el método foo
o return "foobar!"
, y luego lo llamé. Esto funcionó.
Esto se conoce como una clase abierta y me da pesadillas cada vez que pienso en escribir código en ruby que tenga algún tipo de seguridad o integridad. Claro que te permite hacer algunas cosas ordenadas bastante rápido ... pero podría hacerlo de manera que cada vez que alguien almacenara una cadena, la almacenara en un archivo o la enviara a través de la red. Y esta pequeña parte de la redefinición de la cadena se puede guardar en cualquier parte del código.
Muchos otros lenguajes dinámicos tienen cosas similares que se pueden hacer. Perl tiene Tie :: Scalar que puede cambiar detrás de escena cómo funciona un escalar dado (esto es un poco más obvio y requiere un comando específico que puede ver, pero un escalar que se pasa desde otro lugar podría ser un problema). Si tiene acceso al Perl Cookbook, busque la Receta 13.15 - Creación de variables mágicas con empate.
Debido a estas cosas (y otras a menudo son parte de lenguajes dinámicos), muchos enfoques para el análisis estático de la seguridad en el código no funcionan. Perl and Undecidability muestra que este es el caso y señala incluso estos problemas triviales con el resaltado de sintaxis ( whatever / 25 ; # / ; die "this dies!";
plantea desafíos porque whatever
se pueden definir para tomar argumentos o no en tiempo de ejecución derrotando por completo un resaltador de sintaxis o un analizador estático).
Esto puede ser aún más interesante en Ruby con la capacidad de acceder al entorno en el que se definió un cierre (ver YouTube: Mantener a Ruby Razonable de RubyConf 2011 por Joshua Ballanco). Fui informado de este video de un comentario de Ars Technica por MouseTheLuckyDog .
Considere el siguiente código:
def mal(&block)
puts ">:)"
block.call
t = block.binding.eval('(self.methods - Object.methods).sample')
block.binding.eval <<-END
def #{t.to_s}
raise 'MWHWAHAW!'
end
END
end
class Foo
def bar
puts "bar"
end
def qux
mal do
puts "qux"
end
end
end
f = Foo.new
f.bar
f.qux
f.bar
f.qux
Este código es completamente visible, pero el mal
método podría estar en otro lugar ... y con clases abiertas, por supuesto, podría redefinirse en otro lugar.
Ejecutando este código:
~ / $ ruby foo.rb
bar
> :)
qux
bar
b.rb: 20: en `qux ': MWHWAHAW! (Error de tiempo de ejecución)
de b.rb: 30: en ''
~ / $ ruby foo.rb
bar
> :)
qux
b.rb: 20: en `bar ': MWHWAHAW! (Error de tiempo de ejecución)
de b.rb: 29: en ''
En este código, el cierre pudo acceder a todos los métodos y otros enlaces definidos en la clase en ese ámbito. Escogió un método aleatorio y lo redefinió para generar una excepción. (vea la clase Binding en Ruby para tener una idea de a qué tiene acceso este objeto)
Las variables, los métodos, el valor de sí mismo y posiblemente un bloque iterador al que se pueda acceder en este contexto se conservan.
Una versión más corta que muestra la redefinición de una variable:
def mal(&block)
block.call
block.binding.eval('a = 43')
end
a = 42
puts a
mal do
puts 1
end
puts a
Que, cuando se ejecuta produce:
42
1
43
Esto es más que la clase abierta que mencioné anteriormente que hace imposible el análisis estático. Lo que se demostró anteriormente es que un cierre que se pasa a otro lugar, lleva consigo el entorno completo en el que se definió. Esto se conoce como un entorno de primera clase (al igual que cuando puede pasar funciones, son funciones de primera clase, Este es el entorno y todos los enlaces disponibles en ese momento). Se podría redefinir cualquier variable que se definió en el alcance del cierre.
Bueno o malo, quejándose de rubí o no (hay usos donde uno quieren ser capaces de conseguir en el entorno de un método (véase Segura en Perl)), la cuestión de "¿por qué el rubí estar restringido en un proyecto del gobierno "Realmente se responde en ese video vinculado anteriormente.
Dado que:
- Ruby permite extraer el medio ambiente de cualquier cierre.
- Ruby captura todos los enlaces en el alcance del cierre.
- Ruby mantiene todos los enlaces como vivos y mutables.
- Ruby tiene nuevos enlaces que siguen los viejos enlaces (en lugar de clonar el entorno o prohibir volver a enlazar)
Con las implicaciones de estas cuatro opciones de diseño, es imposible saber qué hace cualquier parte del código.
Puede leer más sobre esto en el blog Abstract Heresies . La publicación particular es sobre Scheme, donde se tuvo ese debate. (relacionado con SO: ¿Por qué Scheme no admite entornos de primera clase? )
Con el tiempo, sin embargo, me di cuenta de que había más dificultades y menos poder con entornos de primera clase de lo que había pensado originalmente. En este punto, creo que los entornos de primera clase son inútiles en el mejor de los casos y peligrosos en el peor.
Espero que esta sección muestre el aspecto peligroso de los entornos de primera clase y por qué se le pediría que elimine a Ruby de la solución provista. No se trata solo de que Ruby sea un lenguaje dinámico (como se mencionó en otra respuesta, se han permitido otros lenguajes dinámicos en otros proyectos), sino que hay problemas específicos que hacen que algunos lenguajes dinámicos sean aún más difíciles de razonar.