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?
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 ()
:
- C'è
#send
che ti permette di chiamare un metodo il cui nome hai come stringa e passargli i parametri. -
yield
consente di passare un blocco di codice a un metodo che verrà eseguito nel contesto del metodo di ricezione. - 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à.