Há várias coisas "legais" que podem ser feitas em linguagens dinâmicas que podem ser escondidas em partes do código que não são imediatamente óbvias para outro programador ou auditor quanto à funcionalidade de um determinado pedaço de código.
Considere esta sequência no irb (ruby shell interativo):
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!"
O que aconteceu lá é que tentei chamar o método foo
em uma constante String. Isso falhou. Abri a classe String e defini o método foo
o return "foobar!"
e, em seguida, o chamei. Isso funcionou.
Isso é conhecido como classe aberta e me dá pesadelos toda vez que penso em escrever código em ruby que tenha qualquer tipo de segurança ou integridade. Claro que isso permite que você faça algumas coisas legais bem rápido ... mas eu poderia fazer isso toda vez que alguém armazenasse uma string, a armazenasse em um arquivo ou a enviasse pela rede. E esse pequeno pedaço de redefinição da String pode ser colocado em qualquer lugar do código.
Muitas outras linguagens dinâmicas têm coisas semelhantes que podem ser feitas. O Perl tem o Tie :: Scalar que pode nos bastidores mudar o funcionamento de um determinado escalar (isso é um pouco mais óbvio e requer um comando específico que você pode ver, mas um escalar que é passado de outro lugar pode ser um problema). Se você tiver acesso ao Perl Cookbook, procure a Receita 13.15 - Criando variáveis mágicas com gravata.
Devido a essas coisas (e outras geralmente fazem parte de linguagens dinâmicas), muitas abordagens para a análise estática da segurança no código não funcionam. Perl e Indecidibilidade mostram que esse é o caso e apontam até esses problemas triviais com destaque da sintaxe ( whatever / 25 ; # / ; die "this dies!";
apresenta desafios porque whatever
podem ser definidos para receber argumentos ou não em tempo de execução, derrotando completamente um marca-texto ou analisador estático da sintaxe).
Isso pode se tornar ainda mais interessante no Ruby com a capacidade de acessar o ambiente em que um fechamento foi definido (consulte YouTube: Mantendo o Ruby Reasonable from RubyConf 2011 por Joshua Ballanco). Fui informado deste vídeo de um comentário da Ars Technica por MouseTheLuckyDog .
Considere o seguinte código:
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
Este código é totalmente visível, mas o mal
método pode estar em outro lugar ... e com classes abertas, é claro, pode ser redefinido em outro lugar.
Executando este código:
~ / $ ruby foo.rb
Barra
> :)
qux
Barra
b.rb: 20: em `qux ': MWHWAHAW! (RuntimeError)
de b.rb: 30: em ''
~ / $ ruby foo.rb
Barra
> :)
qux
b.rb: 20: no `bar ': MWHWAHAW! (RuntimeError)
de b.rb: 29: em ''
Nesse código, o fechamento pôde acessar todos os métodos e outras ligações definidas na classe nesse escopo. Ele escolheu um método aleatório e o redefiniu para gerar uma exceção. (veja a classe Binding no Ruby para ter uma ideia do que esse objeto tem acesso)
As variáveis, métodos, valor de si próprio e, possivelmente, um bloco iterador que pode ser acessado nesse contexto são mantidos.
Uma versão mais curta que mostra a redefinição de uma variável:
def mal(&block)
block.call
block.binding.eval('a = 43')
end
a = 42
puts a
mal do
puts 1
end
puts a
Que, quando executado, produz:
42.
1
43
Isso é mais do que a classe aberta que mencionei acima, que torna impossível a análise estática. O que foi demonstrado acima é que um fechamento que é passado em outro lugar leva consigo o ambiente completo em que foi definido. Isso é conhecido como ambiente de primeira classe (assim como quando você pode passar funções, elas são funções de primeira classe, este é o ambiente e todas as ligações disponíveis naquele momento). Pode-se redefinir qualquer variável definida no escopo do fechamento.
Bom ou mau, queixando-se de rubi ou não (há usos onde se poderia desejar para ser capaz de chegar ao ambiente de um método (ver Seguro em Perl)), a questão do "por que rubi ser restringida em um projeto do governo "realmente é respondido no vídeo vinculado acima.
Dado que:
- Ruby permite extrair o ambiente de qualquer fechamento
- O Ruby captura todas as ligações no escopo do fechamento
- O Ruby mantém todas as ligações como ativas e mutáveis
- O Ruby tem novas ligações que ocultam as antigas (em vez de clonar o ambiente ou proibir a religação)
Com as implicações dessas quatro opções de design, é impossível saber o que qualquer parte do código faz.
Mais sobre isso pode ser lido no blog Abstract Heresies . O post em particular é sobre o Scheme em que esse debate foi realizado. (relacionado com SO: por que o Scheme não suporta ambientes de primeira classe? )
Com o tempo, porém, percebi que havia mais dificuldade e menos energia nos ambientes de primeira classe do que eu pensava originalmente. Neste ponto, acredito que os ambientes de primeira classe são inúteis, na melhor das hipóteses, e perigosos, na pior das hipóteses.
Espero que esta seção mostre o aspecto de perigo dos ambientes de primeira classe e por que seria solicitado a remover o Ruby da solução fornecida. Não é apenas porque Ruby é uma linguagem dinâmica (como mencionado em outras respostas, outras linguagens dinâmicas foram permitidas em outros projetos), mas também por questões específicas que tornam ainda mais difícil pensar em algumas linguagens dinâmicas.