質問
Rubyの eval
機能を何度も使用しています。しかし、 eval
は厄介だという人の話を聞いたことがあります。尋ねられたとき、なぜ、どのように、私はそれを使用しない説得力のある理由を得ることができませんでした。彼らは本当に嫌ですか?はいの場合、どのような方法で?可能な「安全」なもの評価するオプション?
解決
ユーザーが送信した、またはユーザーが変更可能な文字列を評価
する場合、これは任意のコードの実行を許可することに相当します。文字列に rm -rf /
などのOS呼び出しが含まれていると想像してください。とはいえ、文字列が適切に制約されていることがわかっている場合、Rubyインタープリターが適切にサンドボックス化されている場合、または理想的にはその両方の場合、 eval
は非常に強力です。
おなじみの場合、問題は SQLインジェクションに類似しています。ここでの解決策は、インジェクションの問題(パラメーター化されたクエリ)の解決策に似ています。つまり、 eval
にしたいステートメントが非常に特殊な形式であることがわかっていて、ユーザーがステートメントのすべてを送信する必要がない場合は、いくつかの変数、数式など、ユーザーからこれらの小さな断片を取得し、必要に応じてサニタイズしてから、適切な場所にプラグインされたユーザー入力で安全なテンプレートステートメントを評価できます。
他のヒント
Rubyには、 eval()
よりも適切なギミックがいくつかあります:
-
#send
があり、名前が文字列としてあるメソッドを呼び出してパラメーターを渡すことができます。 -
yield
を使用すると、コードのブロックを受信メソッドのコンテキストで実行されるメソッドに渡すことができます。 - 多くの場合、単純な
Kernel.const_get(" String")
は、名前を文字列として持つクラスを取得するのに十分です。
それらを詳細に適切に説明することはできないと思うので、Googleに興味がある場合はヒントを示しました。
eval
は安全でないだけでなく(他で指摘されているように)、遅いです。実行されるたびに、 eval
されたコードのASTを新たに解析する必要があります(たとえば、JRubyの場合はバイトコードに変換されます)。ローカリティ(実行中のプログラムが eval
をあまり行わないという仮定の下で、インタープリターの対応する部分が大きいだけでなく、キャッシュが冷たい)
Rubyに eval
が存在するのはなぜですか? 「できるから」ほとんど-実際、 eval
が(LISPプログラミング言語用に)発明されたとき、それはほとんどがショー!さらに重要なのは、プリプロセッサー、デバッガー、またはテンプレートエンジンの作成などのメタプログラミングタスクのために、「インタープリターをインタープリターに追加」したいときに eval
を使用することは正しいことです。このようなアプリケーションの一般的なアイデアは、Rubyコードをマッサージして、その上で eval
を呼び出すことです。これは、ドメイン固有のおもちゃ言語の再発明と実装を確実に打ち負かします。 ://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule "> Greenspunの第10規則。注意点は次のとおりです。たとえば、テンプレートエンジンのコストに注意して、実行時ではなく起動時にすべての eval
を実行します。また、「飼い慣らす」方法がわからない限り、信頼できないコードを eval
しないでください。つまり、能力規律の理論に従って、言語の安全なサブセットを選択して実施します。後者は、非常に難しい作業の多くです(たとえば、 Javaでどのように行われたかを参照;残念ながら、Rubyに対するそのような努力を知りません。)
デバッグが困難になります。最適化が難しくなります。しかし、何よりも、それは通常、あなたがやろうとしていることを行うためのより良い方法があるという兆候です。
eval
で何を達成しようとしているのかを教えていただくと、特定のシナリオに関連するより適切な回答が得られる場合があります。
Evalは非常に強力な機能であり、慎重に使用する必要があります。 Matt Jが指摘したセキュリティの問題に加えて、ランタイムで評価されたコードのデバッグは非常に難しいこともわかります。ランタイムで評価されたコードブロックの問題は、インタープリターが表現するのが難しいため、探すのは困難です。
とはいえ、あなたがその問題に満足していて、セキュリティの問題を心配していないのであれば、ルビーを魅力的にする機能のいずれかを使用することを避けるべきではありません。
特定の状況では、適切に配置された eval
は賢く、必要なコードの量を減らします。 Matt Jが言及したセキュリティ上の懸念に加えて、非常に簡単な質問を1つ自問する必要があります。
すべてが完了したら、他の誰かがあなたのコードを読んで、あなたが何をしたかを理解できますか?
答えがいいえの場合、 eval
で得たものは保守性のために見捨てられます。この問題は、チームで作業している場合にのみ適用されるわけではありませんが、あなたにも適用可能です。数年後ではないにしても、コードの月を振り返って、何をしたかを知りたいです。
" outside"から取得したものを渡す場合 eval
に、あなたは何か間違ったことをしている、それは非常に厄介です。コードを安全にするために十分にエスケープするのは非常に難しいので、非常に安全ではないと思います。ただし、次のコード例のように、evalを使用して重複や他の同様のことを回避している場合は、それを使用しても構いません。
class Foo
def self.define_getters(*symbols)
symbols.each do |symbol|
eval "def #{symbol}; @#{symbol}; end"
end
end
define_getters :foo, :bar, :baz
end
ただし、少なくともRuby 1.9.1では、Rubyには非常に強力なメタプログラミングメソッドがあり、代わりに次のことを行うことができます。
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
ほとんどの場合、これらのメソッドを使用しますが、エスケープする必要はありません。
eval
のもう1つの悪い点は、インタプリタが文字列を解析してから現在のバインディング内でコードを実行する必要があるため(少なくともRubyでは)非常に遅いことです。他のメソッドはC関数を直接呼び出すため、速度が大幅に向上します。