Question

J'utilise la fonctionnalité eval de ruby ??plusieurs fois. Mais j’ai entendu dire que les eval sont méchants. Lorsqu'on lui a demandé pourquoi et comment, je n'ai jamais pu obtenir une raison convaincante de ne pas l'utiliser. Sont-ils vraiment méchants? Si oui, de quelle manière? Quels sont les moyens " plus sûr " options à évaluer?

Était-ce utile?

La solution

Si vous eval créez une chaîne soumise par l'utilisateur ou modifiable par l'utilisateur, cela revient à autoriser l'exécution de code arbitraire. Imaginez si la chaîne contenait un appel de système d'exploitation à rm -rf / ou similaire. Cela dit, dans les situations où vous savez que les chaînes sont correctement contraintes, ou que votre interprète Ruby est correctement mis en sandbox, ou idéalement les deux, eval peut être extraordinairement puissant.

Le problème est analogue à la injection SQL , si vous êtes familier. La solution proposée ici est similaire à la solution au problème d'injection (requêtes paramétrées). Autrement dit, si les instructions que vous souhaitez eval sont connues pour être d'une forme très spécifique, et non toutes de l'instruction, elles doivent être soumises par l'utilisateur, seul un Quelques variables, une expression mathématique ou similaire, vous pouvez récupérer ces petits éléments de l’utilisateur, les désinfecter si nécessaire, puis évaluer l’instruction de modèle sécurisé avec l’entrée utilisateur branchée aux emplacements appropriés.

Autres conseils

Dans Ruby, plusieurs astuces pourraient être plus appropriées que eval () :

  1. Il existe #send qui vous permet d’appeler une méthode dont vous avez le nom en tant que chaîne et de lui transmettre des paramètres.
  2. yield vous permet de transmettre un bloc de code à une méthode qui sera exécutée dans le contexte de la méthode de réception.
  3. Souvent, le simple Kernel.const_get (" chaîne ") est suffisant pour obtenir la classe dont vous avez le nom sous la forme d'une chaîne.

Je pense que je ne suis pas en mesure de les expliquer correctement en détail, alors je vous ai juste donné des conseils, si cela vous intéresse, vous allez sur Google.

eval n’est pas seulement peu sûr (comme on l’a souligné ailleurs), il est également lent. Chaque fois qu'il est exécuté, l'AST du code eval doit être analysé (et par exemple pour JRuby, converti en bytecode), ce qui est une opération lourde en chaînes de caractères et est probablement aussi mauvais pour le cache. localité (en supposant qu'un programme en cours d'exécution ne < eval ne soit pas très important, et les parties correspondantes de l'interpréteur sont donc en cache-froid, en plus d'être volumineuses).

Pourquoi existe-t-il eval dans Ruby, demandez-vous? "Parce que nous pouvons" pour la plupart - En fait, lorsque eval a été inventé (pour le langage de programmation LISP), il s’agissait de principalement pour montrer ! Plus précisément, utiliser eval est idéal pour "ajouter un interpréteur dans votre interprète", pour des tâches de métaprogrammation telles que l'écriture d'un préprocesseur, d'un débogueur ou d'un moteur de modélisation. L’idée commune pour ce type d’applications est de manipuler du code Ruby et d’appeler eval dessus, ce qui est certainement supérieur à la réinvention et à la mise en œuvre d’un langage de jouet spécifique à un domaine, un piège également appelé La dixième règle de Greenspun . Les mises en garde sont les suivantes: méfiez-vous des coûts, par exemple, pour un moteur de modélisation, effectuez tous vos eval au moment du démarrage et non au moment de l'exécution; et ne pas eval du code non fiable, sauf si vous savez comment apprivoiser & c’est-à-dire sélectionner et appliquer un sous-ensemble sûr de la langue selon la théorie de la discipline de capacité . Ce dernier est un lot de travail très difficile (voir par exemple comment cela a été réalisé pour Java ; je ne suis malheureusement pas au courant d'un tel effort pour Ruby).

Cela rend le débogage difficile. Cela rend l’optimisation difficile. Mais surtout, cela indique généralement qu'il existe une meilleure façon de faire ce que vous essayez de faire.

Si vous nous indiquez ce que vous essayez d'accomplir avec eval , vous obtiendrez peut-être des réponses plus pertinentes concernant votre scénario spécifique.

Eval est une fonctionnalité incroyablement puissante qui doit être utilisée avec précaution. Outre les problèmes de sécurité signalés par Matt J, vous constaterez également que le code évalué par le runtime de débogage est extrêmement difficile. Un problème dans un bloc de code évalué à l'exécution sera difficile à exprimer pour l'interprète - il sera donc difficile de le rechercher.

Cela étant dit, si vous êtes à l'aise avec ce problème et que le problème de sécurité ne vous préoccupe pas, vous ne devriez pas éviter d'utiliser l'une des fonctionnalités qui rendent ruby ??aussi attrayant qu'il est.

Dans certaines situations, un eval bien placé est astucieux et réduit la quantité de code requise. Outre les problèmes de sécurité évoqués par Matt J, vous devez également vous poser une question très simple:

Quand tout est dit et fait, quelqu'un d'autre peut-il lire votre code et comprendre ce que vous avez fait?

Si la réponse est non, alors ce que vous avez gagné avec un eval est abandonné pour des raisons de maintenance. Ce problème s'applique non seulement si vous travaillez en équipe, mais également à vous: vous souhaitez pouvoir consulter votre code dans des mois, voire des années à partir de maintenant, et savoir ce que vous avez fait.

Si vous transmettez quoi que ce soit que vous recevez de l’option " extérieur " Pour eval , vous faites quelque chose de mal et c'est très méchant. Il est très difficile d'échapper suffisamment au code pour qu'il soit sûr, alors je le considère comme très dangereux. Toutefois, si vous utilisez eval pour éviter les doublons ou d’autres tâches similaires, comme l’exemple de code suivant, vous pouvez l’utiliser.

class Foo
  def self.define_getters(*symbols)
    symbols.each do |symbol|
      eval "def #{symbol}; @#{symbol}; end"
    end
  end

  define_getters :foo, :bar, :baz
end

Cependant, au moins dans Ruby 1.9.1, Ruby dispose de méthodes de méta-programmation très puissantes et vous pouvez effectuer les opérations suivantes:

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

Dans la plupart des cas, vous souhaitez utiliser ces méthodes, aucune échappement n'est nécessaire.

L'autre inconvénient de eval est le fait que (du moins en Ruby), il est assez lent, car l'interprète doit analyser la chaîne, puis exécuter le code dans la liaison actuelle. Les autres méthodes appellent directement la fonction C et vous devriez donc obtenir un gain de vitesse considérable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top