特定のコードの機能について、他のプログラマーや監査員にはすぐには分からないコードの一部に隠れてしまう動的言語でできる「ニート」なことがたくさんあります。
irb(対話型ルビーシェル)の次のシーケンスを考えます。
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!"
そこで起こったことはfoo
、文字列定数でメソッドを呼び出そうとしたことです。これは失敗しました。次に、Stringクラスを開いてfoo
return メソッドを定義し、"foobar!"
呼び出しました。これはうまくいきました。
これはオープンクラスとして知られており、セキュリティや整合性を備えたルビーでコードを記述するたびに悪夢に見舞われます。確かにそれはあなたに非常にすてきなことを非常に速くさせることができます...しかし、誰かが文字列を保存するたびに、それをファイルに保存するか、ネットワークを介して送信するようにできました。そして、この少しの文字列の再定義は、コードのどこにでも入れることができます。
他の多くの動的言語には、同様のことができるものがあります。PerlにはTie :: Scalarがあり、背後で特定のスカラーの動作を変更できます(これはもう少しわかりやすく、特定のコマンドが必要ですが、他の場所から渡されるスカラーが問題になる可能性があります)。Perl Cookbookにアクセスできる場合は、Recipe 13.15-tieを使用したマジック変数の作成を参照してください。
これらのこと(および他の多くは動的言語の一部であることが多い)のため、コードのセキュリティの静的分析に対する多くのアプローチは機能しません。 PerlとUndecidabilityはこれが事実であることを示しており、構文強調表示に関するこのような些細な問題でさえも指摘しています(実行時に構文強調表示または静的アナライザーを完全に無効にしないように定義できるwhatever / 25 ; # / ; die "this dies!";
ため、課題を提起します)。whatever
これは、クロージャーが定義された環境にアクセスする機能により、Rubyでさらに興味深いものになります(YouTube:RubyをRubyConf 2011からRubyをリーズナブルに保つことをJoshua Ballancoが参照)。MouseTheLuckyDogによるArs Technicaのコメントから、このビデオに気付きました。
次のコードを検討してください。
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
このコードは完全に表示されますが、mal
メソッドはどこか他の場所にある可能性があります...そしてもちろん、オープンクラスでは、どこか別の場所に再定義される可能性があります。
このコードの実行:
〜/ $ ruby foo.rb
バー
> :)
クックス
バー
b.rb:20:in `qux ':MWHWAHAW!(ランタイムエラー)
b.rb:30:から `'
〜/ $ ruby foo.rb
バー
> :)
クックス
b.rb:20:in `bar ':MWHWAHAW!(ランタイムエラー)
b.rb:29:in `'から
このコードでは、クロージャーはそのスコープのクラスで定義されたすべてのメソッドと他のバインディングにアクセスできました。ランダムなメソッドを選択して再定義し、例外を発生させました。(このオブジェクトが何にアクセスできるかについては、Ruby のBindingクラスをご覧ください)
このコンテキストでアクセスできる変数、メソッド、selfの値、および場合によっては反復子ブロックはすべて保持されます。
変数の再定義を示す短いバージョン:
def mal(&block)
block.call
block.binding.eval('a = 43')
end
a = 42
puts a
mal do
puts 1
end
puts a
実行時に生成されるもの:
42
1
43
これは、静的解析を不可能にする上記のオープンクラスよりも優れています。上記で示されているのは、他の場所に渡されるクロージャーは、それが定義された完全な環境を運んでいるということです。これは、ファーストクラス環境として知られています。これは、その時点で利用可能な環境とすべてのバインディングです)。クロージャのスコープで定義された変数を再定義できます。
良いか悪いか、ルビーについて不満を言うか(メソッドの環境にアクセスできるようにしたい用途があります(PerlのSafeを参照))、「政府プロジェクトでルビーを制限する理由」の質問上記のリンク先の動画で実際に回答があります。
とすれば:
- Rubyを使用すると、任意のクロージャーから環境を抽出できます。
- Rubyはクロージャーのスコープ内のすべてのバインディングをキャプチャします
- Rubyはすべてのバインディングをライブおよび可変として維持します
- Rubyには新しいバインディングがあり、古いバインディングをシャドーします(環境のクローンを作成したり、再バインドを禁止したりするのではなく)
これらの4つの設計選択の影響により、どのようなコードが何をするのかを知ることは不可能です。
詳細については、Abstract Heresiesブログをご覧ください。特定の投稿は、そのような議論があったSchemeについてです。(SOに関連:なぜSchemeはファーストクラスの環境をサポートしないのですか?)
しかし、時間が経つにつれて、最初に考えていたよりも、ファーストクラスの環境の方が困難であり、パワーが少ないことに気付きました。この時点で、最高クラスの環境はせいぜい役に立たず、最悪の場合危険であると信じています。
このセクションが、ファーストクラス環境の危険性の側面と、提供されたソリューションからRubyを削除するように求められる理由を示すことを願っています。Rubyが動的言語(他の回答で述べたように、他のプロジェクトでは他の動的言語が許可されている)だけでなく、一部の動的言語の推論をさらに困難にする特定の問題があります。