Ci sono un certo numero di cose "ordinate" che possono essere fatte in linguaggi dinamici che possono essere nascoste in parti del codice che non sono immediatamente ovvie per un altro programmatore o revisore per quanto riguarda la funzionalità di un determinato codice.
Considera questa sequenza in irb (ruby shell interattiva):
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!"
Quello che è successo lì è che ho provato a chiamare il metodo foo
in una costante String. Questo è fallito. Ho quindi aperto la classe String e definito il metodo foo
o return "foobar!"
, quindi l'ho chiamato. Questo ha funzionato.
Questa è conosciuta come una classe aperta e mi dà incubi ogni volta che penso di scrivere codice in ruby che abbia qualsiasi tipo di sicurezza o integrità. Certo, ti consente di fare alcune cose ordinate abbastanza velocemente ... ma potrei farlo in modo che ogni volta che qualcuno memorizzi una stringa, la memorizzi in un file o lo invii in rete. E questo pezzetto di ridefinizione della stringa può essere nascosto in qualsiasi punto del codice.
Molte altre lingue dinamiche hanno cose simili che si possono fare. Perl ha Tie :: Scalar che può dietro le quinte cambiare il modo in cui un determinato scalare funziona (questo è un po 'più ovvio e richiede un comando specifico che puoi vedere, ma uno scalare che viene passato da qualche altra parte potrebbe essere un problema). Se hai accesso al Ricettario Perl, cerca la Ricetta 13.15 - Creazione di variabili magiche con cravatta.
A causa di queste cose (e altre spesso fanno parte di linguaggi dinamici), molti approcci all'analisi statica della sicurezza nel codice non funzionano. Perl e Undecidability mostrano che questo è il caso e sottolinea anche problemi così banali con l'evidenziazione della sintassi ( whatever / 25 ; # / ; die "this dies!";
pone sfide perché whatever
possono essere definite per prendere argomenti o meno in fase di esecuzione sconfiggendo completamente un evidenziatore della sintassi o un analizzatore statico).
Ciò può diventare ancora più interessante in Ruby con la possibilità di accedere all'ambiente in cui è stata definita una chiusura (vedi YouTube: Keeping Ruby Reasonable from RubyConf 2011 di Joshua Ballanco). Sono stato informato di questo video da un commento di Ars Technica di MouseTheLuckyDog .
Considera il seguente codice:
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
Questo codice è completamente visibile, ma il mal
metodo potrebbe essere altrove ... e con le classi aperte, ovviamente, potrebbe essere ridefinito altrove.
In esecuzione questo codice:
~ / $ ruby foo.rb
bar
> :)
qux
bar
b.rb: 20: in `qux ': MWHWAHAW! (RuntimeError)
da b.rb: 30: in ''
~ / $ ruby foo.rb
bar
> :)
qux
b.rb: 20: in `bar ': MWHWAHAW! (RuntimeError)
da b.rb: 29: in ''
In questo codice, la chiusura è stata in grado di accedere a tutti i metodi e altri binding definiti nella classe in quell'ambito. Ha scelto un metodo casuale e lo ha ridefinito per sollevare un'eccezione. (vedi la classe Binding in Ruby per avere un'idea di ciò a cui questo oggetto ha accesso)
Le variabili, i metodi, il valore di sé e possibilmente un blocco iteratore a cui è possibile accedere in questo contesto sono tutti conservati.
Una versione più breve che mostra la ridefinizione di una variabile:
def mal(&block)
block.call
block.binding.eval('a = 43')
end
a = 42
puts a
mal do
puts 1
end
puts a
Che, quando eseguito, produce:
42
1
43
Questo è più della classe aperta che ho menzionato sopra che rende impossibile l'analisi statica. Ciò che è dimostrato sopra è che una chiusura che viene passata da qualche altra parte, porta con sé l'intero ambiente in cui è stata definita. Questo è noto come un ambiente di prima classe (proprio come quando puoi passare attorno a funzioni, sono funzioni di prima classe, questo è l' ambiente e tutti gli attacchi disponibili in quel momento). Si potrebbe ridefinire qualsiasi variabile definita nell'ambito della chiusura.
Buono o cattivo, lamentano ruby o no (ci sono usi in cui si sarebbe vogliono essere in grado di arrivare a l'ambiente di un metodo (vedi sicuro in Perl)), la questione del "perché sarebbe rubino essere limitato in un progetto di governo "si risponde davvero in quel video collegato sopra.
Dato che:
- Ruby consente di estrarre l'ambiente da qualsiasi chiusura
- Ruby cattura tutti gli attacchi nell'ambito della chiusura
- Ruby mantiene tutti gli attacchi come vivi e mutevoli
- Ruby ha nuovi vincoli che oscurano i vecchi vincoli (piuttosto che clonare l'ambiente o vietare la riconversione)
Con le implicazioni di queste quattro scelte di progettazione, è impossibile sapere cosa fa qualsiasi bit di codice.
Maggiori informazioni al riguardo sono disponibili sul blog di Abstract Heresies . Il particolare post riguarda Scheme in cui si è svolto un simile dibattito. (relativo a SO: Perché Scheme non supporta ambienti di prima classe? )
Con il passare del tempo, tuttavia, mi sono reso conto che con ambienti di prima classe c'erano più difficoltà e meno potenza di quanto avessi inizialmente pensato. A questo punto credo che gli ambienti di prima classe siano nella migliore delle ipotesi inutili e pericolosi nella peggiore.
Spero che questa sezione mostri l'aspetto di pericolo degli ambienti di prima classe e perché venga chiesto di rimuovere Ruby dalla soluzione fornita. Non è solo che Ruby è un linguaggio dinamico (come detto altro-risposta, altri linguaggi dinamici sono stati ammessi in altri progetti), ma ci sono problemi specifici che rendono alcuni linguaggi dinamici ancora più difficili da ragionare.