Un certain nombre de choses "soignées" peuvent être réalisées dans des langages dynamiques pouvant être insérées dans des parties du code qui ne sont pas immédiatement évidentes pour un autre programmeur ou auditeur quant à la fonctionnalité d'un morceau de code donné.
Considérons cette séquence dans irb (shell ruby interactif):
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!"
Qu'est-il arrivé il y a j'ai essayé d'appeler la méthode foo
dans une constante de chaîne. Cela a échoué. J'ai ensuite ouvert la classe String et défini la méthode foo
o return "foobar!"
, puis je l'ai appelée. Cela a fonctionné.
Ceci est connu comme une classe ouverte et me donne des cauchemars chaque fois que je pense à écrire du code en ruby qui a une sorte de sécurité ou d’intégrité. Bien sûr, cela vous permet de faire des choses intéressantes assez rapidement ... mais je pourrais le faire chaque fois que quelqu'un stocke une chaîne, l'enregistre dans un fichier ou l'envoie sur le réseau. Et ce peu de redéfinition de la chaîne peut être caché n'importe où dans le code.
Beaucoup d'autres langages dynamiques ont des choses similaires qui peuvent être faites. Perl a Tie :: Scalar qui peut, en coulisse, modifier le fonctionnement d'un scalaire donné (ceci est un peu plus évident et nécessite une commande spécifique que vous pouvez voir, mais un scalaire transmis de quelque part pourrait poser problème). Si vous avez accès au livre de recettes Perl, recherchez la recette 13.15 - Créer des variables magiques avec des liens.
À cause de ces choses (et d'autres souvent de langages dynamiques), de nombreuses approches d'analyse statique de la sécurité dans le code ne fonctionnent pas. Perl et Undecidability montrent que tel est le cas et soulignent même de tels problèmes triviaux de surbrillance de la syntaxe ( whatever / 25 ; # / ; die "this dies!";
posent des problèmes, car ils whatever
peuvent être définis pour accepter des arguments ou non lors de l'exécution, supprimant ainsi complètement un surligneur de la syntaxe ou un analyseur statique).
Cela peut devenir encore plus intéressant dans Ruby avec la possibilité d'accéder à l'environnement dans lequel une fermeture a été définie (voir YouTube: Garder Ruby raisonnable de RubyConf 2011 de Joshua Ballanco). J'ai été informé de cette vidéo par un commentaire d'Ars Technica de MouseTheLuckyDog .
Considérons le code suivant:
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
Ce code est entièrement visible, mais la mal
méthode pourrait être ailleurs ... et avec des classes ouvertes, bien sûr, il pourrait être redéfini ailleurs.
Lancer ce code:
~ / $ ruby foo.rb
bar
> :)
qux
bar
b.rb dans 'qux': MWHWAHAW! (Erreur d'exécution)
de b.rb: 30: dans `'
~ / $ ruby foo.rb
bar
> :)
qux
bar: 20: dans `bar ': MWHWAHAW! (Erreur d'exécution)
de b.rb: dans:
Dans ce code, la fermeture pouvait accéder à toutes les méthodes et autres liaisons définies dans la classe de cette portée. Il a choisi une méthode aléatoire et l'a redéfinie pour générer une exception. (voir la classe Binding en Ruby pour avoir une idée de ce à quoi cet objet a accès)
Les variables, les méthodes, la valeur de self et éventuellement un bloc itérateur accessible dans ce contexte sont tous conservés.
Une version plus courte qui montre la redéfinition d'une variable:
def mal(&block)
block.call
block.binding.eval('a = 43')
end
a = 42
puts a
mal do
puts 1
end
puts a
Qui, lors de l'exécution produit:
42
1
43
C’est plus que la classe ouverte mentionnée ci-dessus qui rend l’analyse statique impossible. Ce qui est démontré ci-dessus, c’est qu’une fermeture passée ailleurs entraîne tout l’environnement dans lequel elle a été définie. C’est ce qu’on appelle un environnement de première classe (comme lorsque vous pouvez faire circuler des fonctions, ce sont des fonctions de première classe, c'est l' environnement et toutes les liaisons disponibles à ce moment). On pourrait redéfinir toute variable définie dans le champ d'application de la fermeture.
Bon ou mauvais, se plaindre de rubis ou non (il y a des utilisations où l' on aurait veulent être en mesure d'obtenir à l'environnement d'une méthode (voir sécurité en Perl)), la question de « pourquoi serait rubis se limiter à un projet gouvernemental "est vraiment répondu dans cette vidéo liée ci-dessus.
Étant donné que:
- Ruby permet d'extraire l'environnement de toute fermeture
- Ruby capture toutes les liaisons dans le cadre de la fermeture
- Ruby maintient toutes les liaisons en tant que live et mutable
- Ruby a de nouvelles liaisons avec des liaisons anciennes (plutôt que de cloner l'environnement ou d'interdire la reliure)
Avec les implications de ces quatre choix de conception, il est impossible de savoir ce que fait un morceau de code.
Plus d'informations à ce sujet peuvent être lues sur le blog de Abstract Heresies . Le poste en question concerne le programme où un tel débat a eu lieu. (lié à SO: Pourquoi Scheme ne prend-il pas en charge les environnements de première classe? )
Au fil du temps, cependant, je me suis rendu compte qu'il y avait plus de difficultés et moins de puissance dans les environnements de première classe que je ne le pensais à l'origine. À ce stade, je pense que les environnements de première classe sont au mieux inutiles et dangereux au pire.
J'espère que cette section montre le danger que représentent les environnements de première classe et pourquoi il serait demandé de supprimer Ruby de la solution fournie. Non seulement Ruby est un langage dynamique (comme indiqué plus haut, d'autres langages dynamiques ont été autorisés dans d'autres projets), mais il y a des problèmes spécifiques qui rendent certains langages dynamiques encore plus difficiles à raisonner.