安全なプログラミング言語とは何ですか?


54

安全なプログラミング言語(PL)が人気を集めています。安全なPLの正式な定義は何でしょうか。たとえば、Cは安全ではありませんが、Javaは安全です。PL自体ではなく、PL実装にプロパティ「safe」を適用する必要があると思います。もしそうなら、安全なPL実装の定義について議論しましょう。この概念を正式にしようとする私自身の試みは奇妙な結果をもたらしたので、他の意見を聞きたいと思います。すべてのPLに安全でないコマンドがあると言ってはいけません。安全なサブセットをいつでも取得できます。


コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
ジル 'SO-悪であるのをやめる'

「いつでも安全なサブセットを取得できます結果の言語がチューリング完全であることをどのようにして確認できますか?(これは通常「プログラミング言語」が意味するものです)
-effeffe

「PL自体ではなく、PL実装に「safe」プロパティを適用する必要があります」-PLの安全な実装が存在する場合は、PLセーフを呼び出すことができます。
ドミトリーグリゴリエフ

回答:


17

何らかの点で言語を「安全」と呼ぶとき、それは正式には、その言語の整形式プログラムが危険と考える何かをすることができないという証拠があることを意味します。「安全」という言葉もあまり正式には使用されませんが、それはここの人々があなたの質問の意味を理解していることです。「安全な」言語に持たせたいプロパティには、さまざまな定義があります。

いくつかの重要なものは次のとおりです。

  • Andrew WrightとMatthias Felleisenの「型の健全性」の定義は、「 Wikipediaを含む多くの場所で「型の安全性」の受け入れられた定義として引用されています。

  • Michael Hicks は、「メモリの安全性」のいくつかの定義をここにリストしています。発生できないエラーの種類のリストもあれば、ポインターを機能として扱うことに基づいているものもあります。Java unsafeは、ガベージコレクターにすべての割り当てと割り当て解除を管理させることにより、これらのエラーが発生しないことを保証します(明示的にマークされた機能を使用しない限り)。Rustは、unsafeアフィン型システムを使用して(ここでも明示的にコードをマークしない限り)同じ保証を行います。

  • 同様に、スレッドセーフコードは通常、データレースやデッドロックなど、スレッドや共有メモリに関連する特定の種類のバグを示すことができないコードとして定義されます。これらのプロパティは多くの場合、言語レベルで適用されます:Rustは型システムでデータの競合が発生しないことを保証し、C ++ std::shared_ptrは複数のスレッド内の同じオブジェクトへのスマートポインターがオブジェクトを途中で削除したり、最後の参照時に削除しないことを保証しますそれに加えて、CおよびC ++ atomicには言語に追加の変数があり、適切に使用すると特定の種類のメモリ一貫性を強制するアトミック操作が保証されます。MPIはプロセス間通信を明示的なメッセージに制限し、OpenMPは異なるスレッドからの変数へのアクセスが安全であることを保証する構文を備えています。

  • メモリが決してリークしないプロパティは、多くの場合、スペースに対して安全と呼ばれます。自動ガベージコレクションは、これを保証する1つの言語機能です。

  • 多くの言語では、その操作の結果が明確に定義され、プログラムが適切に動作することが保証されています。supercatが上記の例を示したように、Cは符号なし算術(安全にラップアラウンドすることを保証)に対してこれを行いますが、符号付き算術ではありません(オーバーフローは任意のバグを引き起こす可能性があります。オーバーフロー)、しかし、その後、言語は時々、無署名の量を署名されたものに静かに変換します。

  • 関数型言語には多くの不変条件があります。たとえば、純粋な関数は副作用を引き起こすことができないなど、整形式プログラムが維持することが保証されています。これらは「安全」と表現される場合とされない場合があります。

  • SPARKやOCamlなどの一部の言語は、プログラムの正確性を証明するように設計されています。これは、バグから「安全」であると記述される場合とされない場合があります。

  • システムが正式なセキュリティモデルに違反できないことを証明します(そのため、「安全であると証明できるシステムはおそらくそうではありません」)。


1
これは、バグから「安全」であると記述される場合とされない場合があります。これを少し詳しく説明していただけますか?「バグから」とはどういう意味ですか?
-scaaahu


1
安全性の種類がリストされているため、この回答を受け入れています。私が念頭に置いていたタイプは、メモリの安全性です。
ベロア

この回答には役立つリンクと多くの例が記載されていますが、それらのほとんどは完全に台無しになっています。ガベージコレクションにより、メモリがリークしないか、「安全でない」ブロックを使用しないことが自動的に保証されます。そして、安全性を真剣に考えている言及された言語の唯一のものであるAda / SPARKのほんの短い言葉。
VTT

93

「安全なプログラミング言語」の正式な定義はありません。それは非公式の概念です。むしろ、安全性を提供すると主張する言語は、通常、どのような安全性が主張/保証/提供されているかの正確な正式な声明を提供します。たとえば、言語は、型の安全性、メモリの安全性、または他の同様の保証を提供する場合があります。


13
補足として、OPの投稿のようにCとJavaについて話す場合、CではなくJavaで保証されるのはメモリの安全性です。型の安全性は、両方とも独自の方法で提供されます。(ええ、これを読んでいる多くの人はすでにそれを知っていますが、おそらく知らない人もいます)。
ウォルフラット

17
@Walfratそれはその一部です。Javaにも未定義の動作はありません。これは、「安全」と呼ばれる言語に期待される動作です。型システムに関しては、強い静的型システムは、人々が「安全」と言う傾向があるとは思いません。Pythonのような動的に型付けされた言語は、概して「安全」です。
マックスバラクラ

2
タイプセーフティの私の定義は、それを処理するコンパイルチェックです。それは正式な定義ではないかもしれません。「安全」ではなく「タイプセーフティ」と言ったことに注意してください。私にとって「安全な」言語とは、「タイプ」と「メモリの安全性」の「私の」定義を参照し、最も普及している言語であると思います。もちろん、コンパイルで処理できないCのリフレクション/ボイドポインターのような落とし穴については話していません。セーフの別の可能な定義は、Cのユニタリ化されたポインターのようなセグメントフォールトでクラッシュしないプログラムです。そのようなものは、一般にPythonおよびJavaで許可されています。
ウォルフラット

7
@Walfratただし、構文がきちんと定義されているのは言語だけです。実行が適切に定義されていることを保証するものではありません。JREのクラッシュが発生した回数は、システムとしては「安全」ではないことを示しています。一方、Cでは、MISRAは未定義の動作を回避して言語のより安全なサブセットを取得するための作業を行っており、Cのアセンブラーへのコンパイルはより適切に定義されています。そのため、「安全」と考えるものに依存します。
グラハム

5
@MaxBarraclough-「Javaにも未定義の動作はありません」-Javaには言語定義のC仕様で使用される意味で未定義の動作はありません(ただし、一部のコードは、単一の事前定義値を持たない値、別のスレッドで変更されている変数、または別のスレッドで変更されている間、doubleまたはlong別のスレッドで変更されている間に、特定の方法で別の半分と混合された値の半分を生成しないことが保証されていない)、API仕様ただし、場合によっては未定義の動作があります。
ジュール

41

導入部でBenjamin Pierceの型とプログラミング言語のコピーを手に入れることができれば、彼は「安全な言語」という用語に関するさまざまな観点の良い概要を持っています。

興味深いと思われる用語の解釈の1つは次のとおりです。

「安全な言語は、そのプログラマーのマニュアルによって完全に定義されています。」言語の定義は、言語内のすべてのプログラムの動作を予測するためにプログラマーが理解する必要があるもののセットです。Cのような言語のマニュアルは、特定のCコンパイラがメモリ内の構造をどのようにレイアウトするかの詳細を知らない限り、一部のプログラム(たとえば、未チェックの配列アクセスやポインター演算を含むプログラム)の動作を予測できないため、定義を構成しませんなど。また、同じプログラムを異なるコンパイラで実行すると、動作がまったく異なる場合があります。

そのため、「安全でない」という用語を使用してプログラミング言語の実装を指すのをためらいます。言語の未定義の用語が異なる実装で異なる動作を生成する場合、実装の1つがより期待される動作を生成する可能性がありますが、「安全」とは呼びません。


7
もちろん、停止問題は、言語に関係なく、動作が言語定義から予測できないプログラムが常に存在することを示しています。したがって、「言語内のすべてのプログラムの動作を予測する」ことに依存する定義は、チューリング完全言語では根本的に欠陥があります。
–MSalters

15
@MSaltersこれは、停止する問題に対する一般的な誤解です。停止問題の決定不能性は、チューリング完全言語で任意のプログラムの動作を機械的に導き出すことが不可能であることを意味します。しかし、いずれかの可能性がある特定のプログラム、動作が予測可能です。この予測を行うコンピュータープログラム作成できないというだけです。
ジル 'SO-悪であるのをやめる'

7
@Giles:そうではありません。すべての非終了プログラムに非終了の証明があると仮定します。次に、終了していないことの証明を列挙して、特定のプログラムが停止するかどうかを調べることができます。したがって、停止の問題は決定可能です。矛盾。したがって、いくつかの非終了プログラムは、証明可能な非終了ではありません。
ケビン

9
@Gilles:私は、多くのプログラムが停止するかしないかが自明であるという事実を完全に認識しています。ただし、ここでの説明は、文字通りすべてのプログラムの動作に関するものです。停止定理の証明は、それが当てはまらないプログラムが少なくとも 1つ存在すること示しています。これは単なる非構造的な証拠であり、どのプログラムが決定不能かはわかりません。
–MSalters

8
@MSalters暗黙のビットは、ステートメントが大規模な緊急行動ではなく、プログラムの小規模行動に関するものだと思います。たとえば、Collat​​z予想を考えます。アルゴリズムの個々のステップは単純で明確に定義されていますが、緊急時の動作(停止するまでの反復回数、および停止する場合)はまったくありません。-「予測」はここでは非公式に使用されています。「任意のプログラム内の特定のステートメントがどのように実行されるかを知る」と書く方が良いかもしれません。
RM

18

Safeはバイナリではなく、連続体です。

非公式の言い方をすれば、安全性とはバグに反対することを意味し、最もよく言及されている2つは次のとおりです。

  • メモリの安全性:言語とその実装により、メモリの解放後の使用、二重解放、境界外アクセスなどのさまざまなメモリ関連エラーが防止されます。
  • 型の安全性:言語とその実装により、未チェックのキャストなど、型に関連するさまざまなエラーが防止されます。

これらは、言語が防止するバグの唯一のクラスではなく、データ競合の自由またはデッドロックの自由がむしろ望ましい、正確さの証明はかなり甘いなどです...

単純に間違ったプログラムはめったに(のみバギー)が「安全でない」とみなされていない、と長期安全性は、一般的に我々の能力に影響を与えるの保証のために予約されている理由プログラムについてを。したがって、未定義の動作を持つC、C ++、またはGoは安全ではありません。

そしてもちろん、開発者が言語保証を維持する責任があり、コンパイラが「ハンドオフ」モードにある領域を意図的に描写する安全でないサブセット(Java、Rust、...)を持つ言語があります。このエスケープハッチ、実際的な定義にもかかわらず、言語は一般にまだ安全と呼ばれています。


7
格子だと思います。
PatJ

1
ほとんどのプログラミング言語の実装には安全でない機能があります(Obj.magicOcamlなど)。そして実際には、これらは本当に必要です
バジル・スタリンケビッチ

4
@BasileStarynkevitch:確かに。C関数を呼び出すには、GCされたオブジェクトを「固定」する必要があり、両側の署名が一致することを手動で確認する必要があるため、FFIのある言語には必ずある程度の安全性が含まれると考えます。
マチューM.

15

DWの答えに異論はありませんが、「安全」な部分の1つは対処されていないと思います。

前述のように、促進される安全性には複数のタイプがあります。なぜ複数の概念があるのか​​を理解するのは良いことだと思います。各概念は、プログラムが特に特定の種類のバグに苦しむという考えに関連付けられており、プログラマーは、言語がプログラマーのバグをブロックした場合、この特定の種類のバグを作成できないと考えています。

したがって、これらの異なる概念には異なるクラスのバグがあり、これらのクラスは相互に排他的ではなく、これらのクラスはすべての形式のバグをカバーしないことに注意してください。DWの2つの例を挙げると、特定のメモリロケーションが特定のオブジェクトを保持しているかどうかという質問は、型の安全性とメモリの安全性の両方の問題です。

「安全な言語」に対するさらなる批判は、危険と見なされる特定の構成要素を禁止することで、プログラマーが代替案を考え出す必要があるという観察から得られます。経験的に、安全性は優れたライブラリーによってより良く達成されます。すでにフィールドテスト済みのコードを使用すると、新しいバグを作成する手間が省けます。


10
ソフトウェアエンジニアリングは実際には科学ではないため、このサイトではあまり話題になりませんが、あなたの経験的な声明には同意しません。適切なライブラリを使用しても、間違った使用から保護されないため、安全でない言語であなたを救うことはありません。安全な言語を使用すると、ライブラリの作成者からより多くの保証を得ることができ、それらが正しく使用されていることをより確実にすることができます。
ジル 'SO-悪であるのをやめる'

3
私はこれについてMSaltersと一緒にいます。-「安全な言語を使用すると、ライブラリの作成者からより多くの保証を得ることができ、それらが正しく使用されていることをより確実にすることができます。」これは、すべての実用的な目的のための非セキュリティです。
キャプテンジラフ

9

CとJavaの基本的な違いは、Javaの特定の容易に識別可能な機能(Unsafe名前空間など)を回避した場合、「誤った」アクションを含め、試行可能なすべてのアクションは限られた範囲の結果しか得られないことです。これにより、少なくともUnsafe名前空間を使用せずにJava でできることは制限されますが、誤ったプログラム、またはより重要なことに、正しく処理されるプログラムによって引き起こされる可能性のある損害を制限することも可能になります有効なファイルですが、エラーのあるファイルから特に保護されていません。

従来、Cコンパイラは、「通常」の場合、標準定義の方法で多くのアクションを処理し、多くのコーナーケースを「環境に特有の方法で」処理していました。数値のオーバーフローが発生した場合にショートして発火するCPUを使用していて、CPUが発火しないようにしたい場合は、数値のオーバーフローを回避するコードを記述する必要があります。ただし、2の補数形式で値を完全に切り捨てるCPUを使用している場合、そのような切り捨てによって許容可能な動作が発生する場合にオーバーフローを回避する必要はありません。

現代のCはさらに一歩進んでいます:標準が要件を課さない数値オーバーフローのようなものの動作を自然に定義するプラットフォームをターゲットにしている場合でも、プログラムの一部のオーバーフローは他の部分の動作に影響する可能性があります時間と因果関係の法則に縛られない任意の方法でプログラム。たとえば、次のようなものを考えます:

 uint32_t test(uint16_t x)
 {
   if (x < 50000) foo(x);
   return x*x; // Note x will promote to "int" if that type is >16 bits.
 }

上記のような「最新」のCコンパイラは、xが46340を超えるとx * xの計算がオーバーフローするため、「foo」の呼び出しを無条件に実行できると結論付ける可能性があります。xが範囲外の場合にプログラムを異常終了させることを許容できる場合、またはそのような場合に関数が値を返すようにしても、範囲外のxでfoo()を呼び出すと、はるかに大きな損傷を引き起こす可能性があることに注意してくださいそれらの可能性のいずれか。従来のCは、プログラマーおよび基盤となるプラットフォームが提供したものを超える安全装置を提供しませんでしたが、安全装置が予期しない状況による損傷を制限することを可能にしました。Modern Cは、すべてを制御下に保つのに100%効果のない安全装置をバイパスします。


3
@DavidThornley:たぶん私の例は微妙すぎました。場合はint32ビットであり、その後、x署名に昇進しますint。場合の理論的根拠から判断すると、標準の著者は非奇妙な実装は、いくつかの特定の例と同等ファッションの外側に符号付きと符号なしの型を扱うだろうと予想、しかし、GCCは、時々 、「最適化し、」ブレークという方法でuint16_tuint16_tINT_MAXを超えた乗算利回り結果、結果が符号なしの値として使用されている場合でも。
-supercat

4
良い例え。これが、(GCCまたはClangで)常にコンパイルする必要がある理由の1つ-Wconversionです。
デイビスラー

2
@Davislor:ああ、ゴッドボルトがコンパイラー・バージョンのリストの順序を逆にしたことに気付いたので、リストでgccの最後のバージョンを選択すると、最古ではなく最新のものが得られます。警告はreturn x+1;問題にならないような多くの状況にフラグを立てる傾向があるため、警告は特に役立つとは思いません。結果をuint32_tにキャストすると、問題を修正せずにメッセージが抑制されます。
-supercat

2
@supercatコンパイラがテストを別の場所に戻す必要がある場合、テストの削除は無意味です。
user253751

3
@immibis:「checked guess」ディレクティブを使用すると、コンパイラーは多くのテスト、またはループ内で何度も実行されるチェックを、ループの外側に引き上げられる単一のテストに置き換えることができます。コンパイラーが要件を満たすために必要なチェックを「最適化」しないようにするために、プログラムが要件を満たすためにマシンコードで不要なチェックを追加することをプログラマーに要求するよりも優れています。
supercat

7

言語には、いくつかの正確さの層があります。抽象化の順序:

  • エラーのないプログラムはほとんどありません(正確性が証明できるプログラムのみ)。したがって、エラーの封じ込めが最も具体的な安全面であると既に述べています。Javaや.netなどの仮想マシンで実行される言語は、この点で一般的に安全です。プログラムエラーは通常、定義された方法でインターセプトおよび処理されます。1
  • 次のレベルでは、実行時ではなくコンパイル時にエラーが検出されるため、言語がより安全になります。構文的に正しいプログラムも、可能な限り意味的に正しいものでなければなりません。もちろん、コンパイラーは全体像を知ることができないため、これは詳細レベルに関係します。強力で表現力豊かなデータ型は、このレベルの安全性の1つの側面です。言語は特定の種類のエラーを作りにくくするべきだと言うことができます(タイプエラー、範囲外アクセス、初期化されていない変数など)。長さ情報を運ぶ配列のような実行時の型情報はエラーを回避します。私は大学でAda 83をプログラムしましたが、コンパイルAdaプログラムには通常、対応するCプログラムよりも桁違いに少ないエラーが含まれていることがわかりました。明示的な変換なしでは割り当てられない整数型をユーザー定義するAdaの機能を利用してください。足とメートルが混同されたために宇宙船全体がクラッシュしました。

  • 次のレベルでは、言語は定型コードを回避する手段を提供する必要があります。独自のコンテナ、そのソート、または連結を記述する必要がある場合、または独自のコンテナを記述する必要があるstring::trim()場合は、間違いを犯します。抽象化レベルが上がるので、この基準には適切な言語と言語の標準ライブラリが含まれます。

  • 最近では、言語は言語レベルでの並行プログラミングの手段を提供する必要があります。並行性を正しくするのは難しく、おそらく言語サポートなしで正しく行うことは不可能です。

  • この言語は、モジュール化とコラボレーションの手段を提供する必要があります。上記の強力で精巧なユーザー定義型は、表現力豊かなAPIの作成に役立ちます。

言語定義はやや直交して理解できるはずです。言語とライブラリを十分に文書化する必要があります。悪いまたは欠落しているドキュメントは、悪いプログラムや間違ったプログラムにつながります。


1しかし、通常、仮想マシンの正確性は証明できないため、このような言語は逆説的に、非常に厳しい安全要件に適さない場合があります。


1
+1レイヤーごとの明確な説明。あなたのための質問は、足やメートルは1が自明エイダで避けることができた、混乱しているので全体の宇宙船がクラッシュしています。、あなたは単純な数学エラーのために失われた火星探査について話しているのですか?あなたは彼らがその宇宙船に使用していた言語を知っていますか?
scaaahu

2
@scaaahuはい、私はそれについて言及していたと思います。いいえ、私は言語を知りません。実際、レポートを読んで、プローブによって送信されたデータは、地球上のソフトウェアによって処理され、データファイルを生成し、推力レベルを決定するために使用されたようです。このシナリオでは、単純な言語入力は適用できません。ところで、彼らは地上のソフトウェアとデータファイルの形式に複数の問題があり、問題の早期発見を妨げる混乱がありました。そのため、この話は強力な型付けの直接的な議論ではなく、依然として警告的な話です。
ピーター-モニカの復活

1

すべてのPLに安全でないコマンドがあると言ってはいけません。安全なサブセットをいつでも取得できます。

私が知っているすべての言語には、(コンパイルして)実行できる違法なプログラムを書く方法があります。そして、私が知っているすべての言語には安全なサブセットがあります。それで、あなたの質問は本当に何ですか?


安全性は多次元的で主観的です。

一部の言語には、「安全でない」多くの操作があります。他の人はそのような操作が少ないです。一部の言語では、何かを行うデフォルトの方法は本質的に安全ではありません。その他では、デフォルトの方法が安全です。一部の言語では、明示的な「安全でない」サブセットがあります。他の言語では、そのようなサブセットはまったくありません。

一部の言語では、「安全」とはメモリの安全性のみを指します。これは、メモリアクセス違反が困難または不可能になる標準ライブラリおよび/またはランタイムが提供するサービスです。他の言語では、「安全性」にはスレッドの安全性が明示的に含まれています。他の言語では、「安全」とは、プログラムがクラッシュしないことの保証(あらゆる種類のキャッチされない例外を許可しないことを含む要件)を指します。最後に、多くの言語で「安全」とは型の安全を指します。型システムが特定の方法で一貫している場合、それは「サウンド」と呼ばれます(偶然、JavaとC#には完全に健全な型システムはありません)。

また、一部の言語では、「安全」のすべての異なる意味が型安全のサブセットと見なされます(たとえば、RustとPonyは型システムのプロパティを通じてスレッド安全を実現します)。


-1

この答えはもう少し広範です。ここ数十年、安全と安全という言葉は、英語を話す社会の特定の政治志向の部分によって破壊されており、その一般的な用法にはほとんど定義がありません。ただし、技術的なテーマについては、「安全」と「安全」を次のように定義します。何かの意図しない使用を防止するデバイス、または偶発的な使用を大幅に困難にするデバイス、およびそのようなデバイスの保護下にある状態。
したがって、安全な言語には、特定のクラスのバグを制限するためのデバイスがあります。もちろん、制限には不便や場合によっては不可能なこともありますが、それは「安全でない」言語がバグになるということではありません。例えば、私はフォークに安全コルクを持っていませんし、何十年もの間、食事中に私の目を刺すことを避けるために多くの努力なしに管理していました。確かに、コルクを使用して費やされるよりも少ない労力。したがって、安全性には、判断する必要のあるコストが伴います。(コルクフォークはスティーブマーティンのキャラクターへの参照です)

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