なぜeval evilなのか?


141

LispとSchemeのプログラマーは、eval厳密に必要な場合を除いて、通常はそれを避けるべきだと言っています。いくつかのプログラミング言語について同じ推奨事項を見てきましたが、の使用に対する明確な議論のリストはまだ見ていませんeval。使用の潜在的な問題の説明はどこにありますかevalか?

たとえば、私GOTOは手続き型プログラミングの問題を知っています(プログラムが読み取りにくくなり、保守が困難になり、セキュリティの問題が見つけにくくなるなど)。しかし、eval

興味深いことに、GOTO継続に対しても同じ議論が当てはまるはずですが、たとえば、Schemersは継続が「悪」だとは言っていないようです。使用する場合は注意が必要です。彼らはeval継続を使用するコードよりもコードを使用するほうが眉をひそめる可能性がはるかに高いです(私が見る限り、私は間違っている可能性があります)。


5
evalは悪ではありませんが、evalはevalの機能です
Anurag

9
@yar-あなたのコメントは、非常に単一のディスパッチオブジェクト中心の世界観を示していると思います。おそらくほとんどの言語で有効ですが、メソッドがクラスに属さないCommon Lispでは異なり、クラスがJava相互運用機能を通じてのみサポートされるClojureではさらに異なります。Jayはこの質問にSchemeのタグを付けました。Schemeには、クラスやメソッドの概念が組み込まれていません(さまざまな形式のOOがライブラリとして利用できます)。
Zak、

3
@Zak、正解です。私は知っている言語しか知りませんが、スタイルを使用せずにWord文書で作業している場合でも、DRYではありません。私の要点は、自分自身を繰り返さないようにテクノロジーを使用することでした。OOは普遍的ではない、真実...
Dan Rosenstark

4
私はClojureタグをこの質問に自由に追加しました。これは、Clojureユーザーがここに投稿された優れた回答に触れることで利益を得る可能性があるためです。
のMichałMarczyk

...まあ、Clojureの場合、少なくとも1つの追加の理由が適用されます。ClojureScriptおよびその派生物との互換性が失われます。
Charles Duffy 2014

回答:


148

使用すべきでない理由はいくつかありますEVAL

初心者の主な理由は、あなたがそれを必要としないことです。

例(Common Lispを想定):

異なる演算子を使用して式を評価します。

(let ((ops '(+ *)))
  (dolist (op ops)
    (print (eval (list op 1 2 3)))))

それは次のように書く方が良いです:

(let ((ops '(+ *)))
  (dolist (op ops)
    (print (funcall op 1 2 3))))

Lispを学ぶ初心者EVALが必要だと考える例はたくさんありますが、式は評価され、関数の部分も評価できるため、必要ではありません。ほとんどの場合、の使用はEVAL評価者の理解の欠如を示します。

マクロでも同じ問題です。多くの場合、初心者は関数を書くべき場所でマクロを書く-マクロが本当に何のためにあるのかを理解しておらず、関数がすでに仕事をしていることを理解していない。

それはしばしばジョブが使うための間違ったツールでありEVAL、それはしばしば初心者が通常のLisp評価ルールを理解していないことを示しています。

あなたが必要と考えられる場合はEVAL、何かのような場合は、チェックFUNCALLREDUCEまたはAPPLY代わりに使用することができます。

  • FUNCALL -引数を指定して関数を呼び出します。 (funcall '+ 1 2 3)
  • REDUCE -値のリストに対して関数を呼び出し、結果を結合します。 (reduce '+ '(1 2 3))
  • APPLY-リストを引数として関数を呼び出します(apply '+ '(1 2 3))

Q:私は本当にevalを必要としますか、それともコンパイラ/エバリュエータはすでに私が本当に望んでいるものですか?

EVALやや上級のユーザーには避けるべき主な理由:

  • コンパイラはコードの多くの問題をチェックし、より高速なコードを生成できるため、コードがコンパイルされていることを確認したい場合があります。

  • 構築され、評価が必要なコードは、できるだけ早くコンパイルできません。

  • 任意のユーザー入力を評価すると、セキュリティの問題が発生します

  • 評価の一部の使用はEVAL、間違ったタイミングで発生し、ビルドの問題を引き起こす可能性があります

簡単な例で最後のポイントを説明するには:

(defmacro foo (a b)
  (list (if (eql a 3) 'sin 'cos) b))

したがって、最初のパラメーターに基づいてSINまたはを使用するマクロを記述したい場合がありますCOS

(foo 3 4)(sin 4)(foo 1 4)します(cos 4)ます。

今私たちは持っているかもしれません:

(foo (+ 2 1) 4)

これは望ましい結果を与えません。

次にFOO、変数をEVALuateすることでマクロを修復することができます。

(defmacro foo (a b)
  (list (if (eql (eval a) 3) 'sin 'cos) b))

(foo (+ 2 1) 4)

しかし、これはまだ機能しません:

(defun bar (a b)
  (foo a b))

変数の値は、コンパイル時には不明です。

避けるべき一般的な重要な理由EVAL醜いハッキングによく使用されます。


3
ありがとう!最後のポイントが理解できませんでした(評価が間違っていましたか?)-少し詳しく説明してください。
Jay

41
これが本当の答えなので、+ 1。人々は、evalやりたいことをするための特定の言語やライブラリ機能があることを知らないからといって、頼りになる。JSからの同様の例:動的な名前を使用してオブジェクトからプロパティを取得するため、次のようにeval("obj.+" + propName)記述しobj[propName]ます。
Daniel Earwicker、

私はあなたが今何を言っているのか分かります、レイナー!ありがとう!
ジェイ

@ダニエル:"obj.+"?最後に確認したところ、+JSでドット参照を使用している場合は無効です。
Hello71、2011年

2
@Danielはおそらくeval( "obj。" + propName)を意味し、期待どおりに機能するはずです。
claj

41

eval(どの言語でも)チェーンソーが邪悪ではないのと同じように邪悪ではありません。ツールです。これは、誤って使用すると手足を切断して内臓を取り除くことができる(比喩的に言えば)強力なツールですが、プログラマーのツールボックスにある多くのツールについても同じことが言えます。

  • goto と友達
  • ロックベースのスレッド化
  • 継続
  • マクロ(hygenicまたはその他)
  • ポインタ
  • 再起動可能な例外
  • 自己変更コード
  • ...そして何千人ものキャスト。

これらの強力で潜在的に危険なツールのいずれかを使用する必要があることに気付いた場合、「なぜですか」と3回尋ねます。チェーンで。例えば:

「なぜ使用しなければならないのevalですか?」「fooのため」「なぜfooが必要なのですか?」「……だから」

そのチェーンの最後に到達しても、ツールがまだ正しいように見える場合は、実行してください。それから地獄を文書化します。それから地獄をテストします。正確性とセキュリティを何度も何度も再確認します。しかしそれをしなさい。


ありがとう-これは以前evalについて聞いたことがある(「自分で質問する」)が、潜在的な問題が何であるかを聞いたり読んだりしたことがなかった。ここでの回答から、それらが何であるか(セキュリティとパフォーマンスの問題)がわかります。
Jay

8
そしてコードの可読性。Evalはコードの流れを完全に台無しにし、それを理解不能にすることができます。
私の正しい

「ロックベースのスレッド」[sic]がリストに含まれている理由がわかりません。ロックを伴わない並行性の形式があり、ロックの問題は一般的によく知られていますが、ロックを「悪」として使用していると言う人は誰も聞いたことがありません。
asveikau

4
asveikau:ロックベースのスレッド化を正しく行うのは非常に困難です(ロックを使用する実稼働コードの99.44%は悪いと思います)。構成しません。「マルチスレッド」コードをシリアルコードに変換する傾向があります。(これを修正すると、代わりにコードが遅くなり、肥大化します。)STMやアクターモデルなど、ロックベースのスレッド化に代わる優れた方法があり、最低レベル以外のコードで悪用することができます。
私の正しい意見を

「なぜチェーン」:)それが傷つく可能性がある3つのステップの後に必ず停止してください。
szymanowski 2018

27

何が入っているかを正確に知っている限り、評価は問題ありません。その中に入るユーザー入力はすべてチェックおよび検証されなければなりません。100%確実にする方法がわからない場合は、行わないでください。

基本的に、ユーザーは問題の言語の任意のコードを入力でき、実行されます。彼がどれほどのダメージを与えることができるか想像してみてください。


1
したがって、ユーザー入力を直接コピーしないアルゴリズムを使用してユーザー入力に基づいて実際にS式を生成している場合、特定の状況では、マクロや他の手法を使用するよりも簡単で明確であれば、「悪」は何もないと思います" それについて?言い換えると、evalの唯一の問題は、SQLクエリやユーザー入力を直接使用する他の手法と同じですか?
ジェイ

10
それが「悪」と呼ばれる理由は、それを間違って行うことは他のことを間違って行うことよりもはるかに悪いためです。そして、私たちが知っているように、初心者は悪いことをします。
Tor Valamo

3
すべての状況でコードを評価する前にコードを検証する必要があるとは思いません。たとえば、単純なREPLを実装する場合、おそらくチェックされていない入力をevalにフィードするだけで問題はありません(もちろん、WebベースのREPLを作成するときは、サンドボックスが必要ですが、通常はそうではありません。ユーザーのシステムで実行されるCLI-REPL)。
sepp2k 2010

1
私が言ったように、あなたがあなたが評価にフィードするものをフィードするときに何が起こるかを正確に知る必要があります。それが「サンドボックスの制限内でいくつかのコマンドを実行する」ことを意味する場合、それはそれが意味することです。;)
Tor Valamo 2010

@TorValamoは脱獄の話を聞いたことがありますか?
ロイック・フォーレ-ラクロワ

21

「いつ使うべきevalですか?」より良い質問かもしれません。

簡単に言えば、「プログラムが実行時に別のプログラムを作成してから実行する場合」です。遺伝的プログラミングは、使用することが理にかなっていると思われる状況の例ですeval


14

IMO、この質問はLISPに固有のものではありません。これはPHPに対する同じ質問に対する回答であり、LISP、Ruby、および評価のある他の言語に適用されます。

eval()の主な問題は次のとおりです。

  • 安全でない可能性のある入力。信頼できないパラメータを渡すことは失敗する方法です。多くの場合、パラメーター(またはその一部)が完全に信頼されていることを確認するのは簡単なことではありません。
  • トリッキーネス。eval()を使用すると、コードが巧妙になるため、理解が難しくなります。Brian Kernighanの言葉を引用すると、「デバッグは、最初にコードを書くよりも2倍困難です。したがって、コードをできる限り巧妙に書くと、当然のことながら、デバッグするほど賢くありません

eval()の実際の使用に関する主な問題は1つだけです。

  • 十分な考慮なしにそれを使用する経験の浅い開発者。

ここから撮影。

トリッキーな作品は素晴らしいポイントだと思います。ゴルフと簡潔なコードへの執着は常に「賢い」コードをもたらしました(evalは素晴らしいツールです)。しかし、読みやすさのためにコードを書く必要があります。IMOは、自分が賢いことを証明したり、紙を節約したりしないことです(とにかく印刷することはありません)。

次に、LISPにはevalが実行されるコンテキストに関連するいくつかの問題があるため、信頼できないコードはより多くのものにアクセスできます。この問題はとにかくよくあるようです。


3
EVALの「悪質な入力」の問題は、Lisp以外の言語にのみ影響します。これらの言語では、eval()は通常文字列引数を取り、ユーザーの入力は通常スプライスされます。ユーザーは入力に引用符を含めてエスケープできます。生成されたコード。しかし、Lispでは、EVALの引数は文字列ではなく、絶対に無謀でない限り(ユーザーがREAD-FROM-STRINGを使用して入力を解析してS式を作成し、それを次に含める場合を除いて)、ユーザー入力をコードにエスケープすることはできません。引用符なしのEVALコード。引用符を付ける場合、引用符をエスケープする方法はありません)。
アカウント

12

多くの素晴らしい答えがありましたが、ここにラケットの実装者の一人であるマシュー・フラットからの別の見解があります:

http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html

彼はすでにカバーされているポイントの多くを作りますが、それでもそれでも興味深いことに彼のテイクを見つける人もいます。

概要:それが使用されるコンテキストはevalの結果に影響を与えますが、プログラマーによって考慮されないことが多く、予期しない結果につながります。


11

正解は近づかないことです。それはプリミティブであり、7つのプリミティブ(他はcons、car、cdr、if、eq、quote)であるため、私は奇妙なことに気づきます。

On Lispから:「通常、evalを明示的に呼び出すことは、空港のギフトショップで何かを購入するようなものです。

では、いつevalを使用するのですか?通常の用途の1つは、を評価してREPL内にREPLを含めること(loop (print (eval (read))))です。誰もがその使用法で大丈夫です。

ただし、evalとバッククォートを組み合わせることでコンパイルに評価されるマクロに関して関数を定義することもできます。あなたが行く

(eval `(macro ,arg0 ,arg1 ,arg2))))

そしてそれはあなたのために文脈を殺します。

スワンク(emacsスライムの場合)は、これらのケースでいっぱいです。彼らはこのように見えます:

(defun toggle-trace-aux (fspec &rest args)
  (cond ((member fspec (eval '(trace)) :test #'equal)
         (eval `(untrace ,fspec))
         (format nil "~S is now untraced." fspec))
        (t
         (eval `(trace ,@(if args `(:encapsulate nil) (list)) ,fspec ,@args))
         (format nil "~S is now traced." fspec))))

汚いハックだとは思わない。私はいつもそれを使って、マクロを関数に再統合しています。


1
カーネル言語をチェックアウトしたいかもしれません;)
artemonster

7

Lisp evalに関する別のポイント:

  • ローカル環境を失い、グローバル環境下で評価します。
  • 場合によっては、読み取りマクロ「#」を実際に使用するつもりであるのに、evalを使用したくなることがあります。これは読み取り時に評価されます。

グローバルenvの使用はCommon LispとSchemeの両方に当てはまることを理解しています。Clojureにも当てはまりますか?
Jay

2
Scheme(少なくともR7RSの場合、おそらくR6RSの場合も)では、evalに環境を渡す必要があります。
csl 2013年

4

GOTOの「ルール」のように:自分が何をしているかわからない場合は、混乱させることができます。

既知の安全なデータから何かを構築するだけでなく、一部の言語/実装ではコードを十分に最適化できないという問題があります。あなたは内部で解釈されたコードで終わるかもしれませんeval


そのルールはGOTOとどのように関係していますか?混乱させることができないプログラミング言語の機能はありますか?
ケン

2
@ケン:GOTOルールがないため、私の回答には引用符が含まれています。自分で考えるのが怖い人のための教義があります。evalについても同じです。私はevalを使用していくつかのPerlスクリプトを劇的に高速化したことを覚えています。ツールボックスの1つのツールです。初心者は、他の言語構成の方が簡単/優れている場合にevalを使用することがよくあります。しかし、クールで独断的な人々を喜ばせるためだけにそれを完全に回避しますか?
stesch

4

評価は安全ではありません。たとえば、次のコードがあるとします。

eval('
hello('.$_GET['user'].');
');

これで、ユーザーがサイトにアクセスし、http: //example.com/file.php?user =); $ is_admin = true; echo(

次に、結果のコードは次のようになります。

hello();$is_admin=true;echo();

6
彼はLispについて話していて、phpではないと思った
fmsf

4
@fmsf彼はLispについて具体的に話していましたが、一般的にevalはそれを持っている任意の言語で話していました。
Skilldrick 2010

4
@fmsf-これは実際には言語に依存しない質問です。実行時にコンパイラを呼び出すことでevalをシミュレートできるため、静的コンパイル言語にも適用されます。
Daniel Earwicker、

1
その場合、言語は重複しています。私はここでこのようなものをたくさん見ました。
fmsf

9
PHP evalはLisp evalとは異なります。見て、それは文字列を操作し、URLの悪用はテキストの括弧を閉じて別の括弧を開くことができるかどうかに依存します。Lisp evalはこの種の影響を受けません。サンドボックスを適切にサンドボックス化すれば、ネットワークからの入力として得られるデータを評価できます(そのための構造は、それを実行するのに十分簡単です)。
Kaz 2013年

2

評価は悪ではありません。評価は複雑ではありません。渡したリストをまとめる関数です。他のほとんどの言語では、任意のコードをコンパイルすることは、言語のASTを学習し、コンパイラの内部を調べてコンパイラAPIを理解することを意味します。LISPでは、evalを呼び出すだけです。

いつ使用すべきですか?何かをコンパイルする必要があるときはいつでも、通常、実行時に任意のコードを受け入れ、生成または変更するプログラム。

いつそれを使うべきではないのですか?他のすべての場合。

必要のないときに使用しない方がいいのはなぜですか。読みやすさ、パフォーマンス、デバッグに問題を引き起こす可能性のある、不必要に複雑な方法で何かをしているためです。

ええ、でも、もし私が初心者なら、それを使うべきかどうかどうやって知るのですか?関数で必要なものを常に実装するようにしてください。それが機能しない場合は、マクロを追加します。それでも機能しない場合は、eval!

これらのルールに従ってください。evalで悪を行うことは決してありません:)


0

好き ザックの答えがとても好きで、彼は問題の本質に達しました。evalは、新しい言語、スクリプト、または言語の変更を書いているときに使用されます。彼はこれ以上説明しないので、例を挙げましょう。

(eval (read-line))

この単純なLispプログラムでは、ユーザーは入力を求められ、入力した内容が評価されます。これが機能するために全体のシンボル定義のセットを、あなたがそれらすべてを含める必要がありますので、ユーザは、入力した可能性がある機能見当がつかないので、プログラムがコンパイルされた場合に存在しなければなりません。つまり、この単純なプログラムをコンパイルすると、結果のバイナリは巨大になります。

原則として、このため、これをコンパイル可能なステートメントと見なすこともできません。一般に、evalを使用すると、インタプリタ環境で動作しているため、コードをコンパイルできなくなります。evalを使用しない場合は、Cプログラムと同じようにLispまたはSchemeプログラムをコンパイルできます。したがって、evalの使用をコミットする前に、インタープリター環境にいる必要があることを確認する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.