Domanda

Ho usato molte volte la funzione eval di ruby. Ma ho sentito gente dire che eval è sgradevole. Alla domanda, perché e come, non potrei mai avere una ragione convincente per non usarlo. Sono davvero cattivi? Se sì, in che modo? Cosa sono possibili "più sicuri" opzioni per valutare?

È stato utile?

Soluzione

Se stai eval usando una stringa inviata o modificabile dall'utente, ciò equivale a consentire l'esecuzione di codice arbitrario. Immagina se la stringa contenesse una chiamata del sistema operativo a rm -rf / o simile. Detto questo, in situazioni in cui sai che le stringhe sono opportunamente vincolate, o il tuo interprete Ruby è sottoposto a sandbox in modo appropriato, o idealmente entrambi, eval può essere straordinariamente potente.

Il problema è analogo a SQL injection , se hai familiarità. La soluzione qui è simile alla soluzione al problema di iniezione (query con parametri). Cioè, se le dichiarazioni che si desidera eval sono note per avere una forma molto specifica e non tutte le dichiarazioni devono essere inviate dall'utente, solo un poche variabili, un'espressione matematica o simili, puoi prendere questi piccoli pezzi dall'utente, disinfettarli se necessario, quindi valutare l'istruzione modello sicura con l'input dell'utente inserito nei punti appropriati.

Altri suggerimenti

In Ruby ci sono diversi espedienti che potrebbero essere più appropriati di eval () :

  1. C'è #send che ti permette di chiamare un metodo il cui nome hai come stringa e passargli i parametri.
  2. yield consente di passare un blocco di codice a un metodo che verrà eseguito nel contesto del metodo di ricezione.
  3. Spesso il semplice Kernel.const_get (" String ") è sufficiente per ottenere la classe il cui nome hai come stringa.

Penso di non essere in grado di spiegarli correttamente in dettaglio, quindi ti ho appena dato i suggerimenti, se sei interessato andrai su Google.

eval non è solo insicuro (come è stato sottolineato altrove), è anche lento. Ogni volta che viene eseguito, l'AST del codice eval deve essere nuovamente analizzato (e per esempio JRuby, trasformato in bytecode), che è un'operazione pesante per le stringhe ed è probabilmente anche dannosa per la cache località (partendo dal presupposto che un programma in esecuzione non eval e che le parti corrispondenti dell'interprete siano quindi fredde nella cache, oltre ad essere grandi).

Perché c'è eval in Ruby, chiedi? " Perché possiamo " principalmente - In effetti, quando è stato inventato eval (per il linguaggio di programmazione LISP), era principalmente per mostrare ! Più precisamente, l'uso di eval è la cosa giusta quando si desidera " aggiungere un interprete nel proprio interprete " ;, per attività di metaprogrammazione come la scrittura di un preprocessore, un debugger o un motore di template. L'idea comune per tali applicazioni è quella di massaggiare un po 'di codice Ruby e chiamare eval , e sicuramente batte reinventare e implementare un linguaggio giocattolo specifico per il dominio, una trappola nota anche come Decima regola di Greenspun . Le avvertenze sono: attenzione ai costi, ad esempio per un motore di template, fare tutto il eval ing all'avvio, non in fase di esecuzione; e non eval codice non attendibile a meno che tu non sappia come "domare" esso, cioè selezionare e applicare un sottoinsieme sicuro della lingua secondo la teoria della disciplina della capacità . Quest'ultimo è un lotto di lavoro davvero difficile (vedi ad esempio come è stato fatto per Java ; sfortunatamente non sono a conoscenza di tali sforzi per Ruby).

Rende difficile il debug. Rende difficile l'ottimizzazione. Ma soprattutto, di solito è un segno che c'è un modo migliore per fare qualsiasi cosa tu stia cercando di fare.

Se ci dici che cosa stai cercando di realizzare con eval , potresti ottenere alcune risposte più pertinenti relative al tuo scenario specifico.

Eval è una funzione incredibilmente potente che dovrebbe essere usata con attenzione. Oltre ai problemi di sicurezza segnalati da Matt J, troverete anche che il debug del codice valutato in fase di esecuzione è estremamente difficile. Un problema in un blocco di codice valutato in fase di esecuzione sarà difficile da interpretare per l'interprete, quindi cercarlo sarà difficile.

Detto questo, se ti senti a tuo agio con questo problema e non sei preoccupato per il problema di sicurezza, non dovresti evitare di utilizzare una delle funzionalità che rende il rubino tanto attraente quanto lo è.

In alcune situazioni, un eval ben posizionato è intelligente e riduce la quantità di codice richiesto. Oltre ai problemi di sicurezza che sono stati menzionati da Matt J, è necessario porsi una domanda molto semplice:

Quando tutto è stato detto e fatto, chiunque altro può leggere il tuo codice e capire cosa hai fatto?

Se la risposta è no, ciò che hai ottenuto con un eval viene abbandonato per manutenibilità. Questo problema non è applicabile solo se lavori in gruppo, ma è applicabile anche a te: vuoi essere in grado di guardare indietro al tuo codice tra mesi, se non tra anni, e sapere cosa hai fatto.

Se stai passando qualcosa che ottieni da " outside " a eval , stai facendo qualcosa di sbagliato ed è molto brutto. È molto difficile sfuggire al codice abbastanza da renderlo sicuro, quindi lo considero abbastanza pericoloso. Tuttavia, se stai usando eval per evitare duplicazioni o altre cose simili, come nell'esempio di codice seguente, è corretto 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

Tuttavia, almeno in Ruby 1.9.1, Ruby ha metodi di meta-programmazione davvero potenti e puoi invece fare quanto segue:

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

Per la maggior parte degli scopi, si desidera utilizzare questi metodi e non è necessario scappare.

L'altra cosa negativa di eval è il fatto che (almeno in Ruby), è piuttosto lento, poiché l'interprete deve analizzare la stringa e quindi eseguire il codice all'interno dell'associazione corrente. Gli altri metodi chiamano direttamente la funzione C e quindi dovresti ottenere un notevole aumento di velocità.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top