静的コード解析における抽象解釈

抽象解釈の解説:格子理論からInferとAstréeまで

あなたが書いたすべてのテストに合格する2つのプログラムを考えてみましょう。片方は正しいプログラムです。もう片方には、特定の入力の組み合わせが同時に到着した場合にのみ発生するゼロ除算エラーがあります。そして、あなたのテストではそのような組み合わせは決して発生しません。従来のテストでは、どちらが正しいプログラムでどちらが間違っているのかを判別することはできません。しかし、抽象解釈ならそれが可能です。

抽象解釈とは、静的解析ツールがプログラムを実行することなく、そのプログラムのあらゆる動作を推論できるようにする数学的枠組みです。これは、FacebookのInferが大規模なヌルポインタバグを検出する技術、エアバスの飛行制御ソフトウェアを形式的に検証するAstréeアナライザー、そして健全性を謳うすべての静的解析ツールの基盤となっています。健全性とは、プログラムが解析に合格すれば、チェック対象のエラークラスが実際に存在しないという保証のことです。抽象解釈の仕組みを理解することで、なぜ一部のツールが他のツールでは見逃すバグを見つけるのか、そしてなぜそのような保証には特定のトレードオフが伴うのかが理解できます。

実行せずにコードを分析する

SMART TS XL ポートフォリオ内のすべての言語に対して、構造静的解析を同時に適用します。

詳細情報

抽象解釈とは何か?

抽象解釈は、1977年にパトリック・クーゾーとラディア・クーゾーによって開発されたプログラム近似理論である。その核心となる考え方は、一般的に決定不能であるすべての可能なプログラム状態の正確な集合を計算する代わりに、解析しやすい簡略化された数学的領域を用いて安全な過剰近似を計算するというものである。

ここでいう「抽象的」という言葉は、曖昧さや概念的な意味ではありません。それは、具体的な数値の集合を、不要な詳細を捨てつつ、関心のある特性を保持するより単純な表現に抽象化する、特定の数学的操作を指します。具体的な整数値としては、 42 符号解析の抽象化においては、単に「正」となる。この抽象化によって情報は失われる(正確な値は分からなくなる)が、扱いやすさは向上する(任意の整数の符号は、正、負、ゼロの3つのいずれかになる)。

プログラム解析においてこれが有用なのは、それに伴う保証があるからです。解析によって抽象化された領域にエラーが見つからない場合、具体的な実行においてもエラーは存在しません。潜在的なエラーが見つかった場合、そのエラーは実際に発生するかどうかは分かりませんが、真のエラーが隠蔽されることはありません。これが健全性です。

抽象解釈 vs. AST分析 vs. 動的分析

これらの用語はしばしば混同されます。「ASTコード分析」はこの記事の検索結果にも表示されますが、これらは異なる事柄を指しています。

抽象構文木(AST)は、ソースコードの文法構造を表すデータ構造です。すべてのコンパイラとリンタはASTを構築します。これは、構文解析、リファクタリングツール、およびパターンベースの静的解析の基盤となります。ASTベースの解析はパターンを検出します。ルール(パラメータが多すぎる関数、連結によって構築されたSQL文字列など)に一致するコードはフラグ付けされます。値や実行時動作については推論しません。

抽象解釈は、プログラムを実行せずに実行時動作について推論します。入力として抽象構文木(AST)を使用しますが、それをはるかに超えた推論を行います。具体的には、プログラム内での値の流れ方、変数が取り得る範囲、特定の呼び出し箇所でポインタがNULLになる可能性、ループが終了するかどうかなどをモデル化します。AST解析はパターンマッチングであり、抽象解釈は動作推論です。

ほとんどのリンター(ESLint、Checkstyle、Pylintなど)は主にAST(抽象構文木)に基づいています。ほとんどの形式検証ツール(Infer、Astrée、Polyspaceなど)は抽象解釈を使用します。動的解析(プログラムを実行して実際の動作を観察する)では、特定の入力によって引き起こされるバグのみが検出されます。一方、抽象解釈では、プログラムを実行することなく、考えられるすべての入力に対してバグを検出できます。

静力学解析の背後にある数学的原理

「静的解析ツールの背後にある数学的原理は何ですか」というクエリが検索結果に直接表示されます。以下に、その簡潔な回答を示します。

抽象的解釈は、3つの数学的構造に基づいている。

格子。格子とは、要素の任意のペアに対して最小上界(結合)と最大下界(交点)が存在する、部分的に順序付けられた集合です。静的解析では、格子は抽象領域、つまり可能な抽象値の集合を表し、それらが持つ情報の量によって順序付けられます。符号解析の場合、格子は次のようになります。

        ⊤ (unknown -- could be anything)
       / \
   pos   neg
       \ /
        0
        |
        ⊥ (unreachable -- no possible value)

格子を上に進むと精度が低下し(情報が少なくなる)、下に進むと精度が向上します(情報が多くなる)。最上位の要素⊤は「有用な情報は何も得られない」という意味です。最下位の要素⊥は「この状態には到達できない」という意味です。

ガロア接続。ガロア接続とは、具体的な領域(実際のプログラム値)と抽象的な領域(簡略化された表現)との間の形式的な関係です。これは、具体的な値をその抽象的な表現にマッピングする抽象化関数αと、抽象的な値をそれが表す具体的な値の集合にマッピングする具体化関数γという2つの関数から構成されます。

重要な特性:抽象領域は安全な過剰近似でなければならない。 γ(α(S)) ⊇ S あらゆる具体的な集合 S に対して、抽象化には実際に発生する値よりも多くの値が含まれる可能性があり、それが偽陽性の原因となりますが、実際に発生する値を除外してはなりません。実際の値を除外すると、実際のバグを見逃すことになります。

固定小数点反復。ループを含むプログラムの場合、解析は安定状態に達するまで反復する必要があります。次のようなループの場合:

c

int x = 0;
while (condition) {
    x = x + 1;
}

最初の反復では、 x is {0}1つのループ本体の後、 x かもしれない {0, 1}2 年後、 {0, 1, 2}この集合は増え続け、決して自然に安定することはありません。解決策は 拡大: より広い近似にジャンプすることで収束を強制する演算子 (通常、 [0, +∞) (区間分析の場合)。分析では次に 絞り込む 精度をある程度回復するため。

この固定小数点演算こそが、ループを含むすべての実行パスにおいて抽象解釈を完全にするものであり、単純なパターンマッチングよりも計算コストが高くなる理由でもある。

抽象領域:何を近似するかを選択する

抽象領域は、分析で何が検出できるか、何が検出できないかを決定します。異なる領域は、プログラムの動作に関する異なる質問に答えます。

抽象ドメイン追跡対象使用例何が欠けているか
サイン分析値が正、負、またはゼロであるかゼロ除算検出正確な値、オーバーフロー条件
区間分析数値の上限と下限バッファオーバーフロー、配列アクセスの安全性変数間の関係
八角形の領域変数ペア間の線形関係より高精度なオーバーフロー検出非線形関係
ポインタ解析ポインタがヌルになる可能性があるか、または互いにエイリアスになる可能性があるかヌル参照解除、解放後使用オブジェクトのライフタイム、ヒープの形状
汚染分析値が信頼できない情報源から来ているかどうかSQLインジェクション、XSS検出制御を通じた暗黙の流れ
多面体領域任意の線形算術制約ループ境界検証パフォーマンスコストは指数関数的に増加する

ドメイン間のトレードオフは常に精度とパフォーマンスのどちらを優先するかという問題です。区間ドメインは高速で、ほとんどの数値バグを検出できます。多面体ドメインは精度がはるかに高いものの、変数の数に対して指数関数的に複雑になります。実用的な静的解析ツールは、対象アプリケーションに合わせてトレードオフのバランスが取れるドメインを選択します。安全性が重視される組み込みシステムでは、より低速で高精度な解析が許容されますが、CI/CDに統合されたリンターは数秒で完了する必要があります。

3つの実用ツールが抽象解釈をどのように活用するか

理論を単独で説明するのではなく、具体的なツールを用いることで、その応用が明確になる。

Facebook Inferは、双方向アブダクションと呼ばれる抽象解釈手法を用いて、Java、C、C++、Objective-Cのコードを解析し、ヌルポインタの逆参照、リソースリーク、競合状態を検出します。双方向アブダクションは関数の事前条件と事後条件を自動的に検出するため、手動での仕様記述なしにプロシージャ間解析が可能です。Inferは、数百万行規模のコードベースにも対応し、検出対象となるエラーの種類に対して健全性を維持できるため、Facebook、Spotify、Mozillaをはじめとする数十もの大規模組織のCI環境で活用されています。

Astréeは、数値抽象領域を用いた抽象解釈によって、C言語プログラムにおける実行時エラーの不存在を証明します。エアバス社はAstréeを用いてA380の主要飛行制御ソフトウェアを正式に検証し、制御システム全体における実行時エラーの不存在を証明しました。これは、従来のテストプログラムでは実現できなかった保証です。Astréeは、チェック対象のエラークラスにおいて偽陰性をゼロとしますが、手動による確認が必要な偽陽性を生成する場合があります。

Polyspace(MathWorks社製)は、安全性が重視されるアプリケーションにおける組み込みCおよびC++コードに対して抽象解釈を適用します。すべての操作を「緑」(エラーがないことが確認されている)、「赤」(エラーが確実に発生している)、または「オレンジ」(レビューが必要な潜在的なエラー)に分類します。「緑」の分類は形式的な証明であり、その操作において実行時にランタイムエラーが発生することはありません。

健全性・精度・性能の三角形

抽象解釈ツールは、相反する特性からなる基本的な三角形を巧みに操る。どのツールも、これら3つの特性すべてを同時に最大限に高めることはできない。

健全性とは、偽陰性がないことを意味します。つまり、分析対象クラスに存在するすべての実際のバグが検出されます。健全なツールは保証を提供しますが、健全でないツールはバグを見逃す可能性があります。

精度が高いということは、偽陽性が少ないことを意味します。つまり、発見された結果が、起こり得ない理論上の問題ではなく、現実の問題に対応しているということです。高い精度を実現するには、より洗練された抽象領域と、手順間の分析が必要となります。

パフォーマンスとは、分析が実用的な時間内に完了することを意味します。より精度の高い分析は、よりコストがかかります。100万行のコードベースで実行時エラーが一切存在しないことを証明するには数時間かかりますが、リンターによるスキャンは数秒で済みます。

用途によって、この三角形の異なる点が必要となる。

  • IDE lint と CI/CD性能第一、精度第二、健全性はオプション
  • セキュリティスキャン: 精度を最優先(開発者のアラート疲労を軽減)、重大度の高いクラスでは健全性が重要
  • 安全性が極めて重要な認証健全性が最優先(実際のバグを見逃してはならない)、パフォーマンスは二の次、手動レビュープロセスでは誤検出は許容される。

組み込みシステムおよび安全性が重要な開発における抽象解釈

「組み込み開発における静的解析の利点」という問いは、抽象解釈の最も重要な応用分野の一つを指摘しています。組み込みシステム、自動車制御ユニット、医療機器ファームウェア、航空宇宙飛行制御ソフトウェアなどは、抽象解釈が特に価値を持つような制約を抱えています。

すべての状態に対応するテストハーネスは存在しません。自動車用ECUは、数千ものセンサーの組み合わせにリアルタイムで応答します。すべての組み合わせに対応するテストを作成することは不可能です。抽象的解釈によって、すべての状態を同時に網羅できます。

認証要件。DO -178C(航空宇宙)、ISO 26262(自動車)、およびIEC 62443(産業制御)では、ソフトウェアがあらゆる条件下で正しく動作することを実証する必要があります。抽象解釈を用いた形式検証は、テストカバレッジレポートでは満たせないこの要件を満たすことができます。

リソースの制約。組み込みソフトウェアには、メモリ割り当て機能、例外処理機能、オペレーティングシステムのフォールバック機能が備わっていないことがよくあります。実行時エラー、ヌルポインタの逆参照、配列の範囲外アクセスなどは、システム全体の深刻な障害となります。これらのバグを見逃すことによる代償は、クラッシュレポートとホットフィックスの適用にとどまりません。それは、重大な安全上の問題です。

AstréeとPolyspaceのアナライザーは、まさにこの目的のために開発されたものです。これらの装置は、偽陽性率が高く、分析速度が遅いことを許容する代わりに、偽陰性を確実に検出できるという設計になっています。

偽陽性と拡大する問題

抽象解釈ツールに対する最も一般的な批判は、誤検出、つまり実際の実行では発生し得ない潜在的なエラーに関する警告です。誤検出が品質上の欠陥ではなく、本来的に発生するものである理由を理解することで、それらを管理しやすくなります。

偽陽性は、次の2つの原因から発生します。

抽象領域における過剰近似。 区間領域が追​​跡する場合 x ∈ [0, 100]、以下のケースを区別することはできません。 x 実際には常に 50 未満です。 x プログラムロジックが保証している場合でも、ゼロ除算の可能性があるとフラグが立てられることがあります。 x > 0より正確なドメイン(正確な値を追跡するか、制約をリンクする) x 別の変数に置き換えることで偽陽性を排除できるが、計算コストが高くなる。

拡大している。 ループ解析を扱いやすくする収束演算子は、必然的に情報を失う。 x from [0, 5] 〜へ [0, +∞)アナライザーはもはや x 境界内に留まります。コードがチェックした場合 assert(x < 1000) ループの後、この主張は、実際には x 常に1000を大きく下回る。

誤検出を管理するための実践的な戦略としては、重要なモジュールに対してより精度の高いドメインを使用するように分析を設定する(分析速度の低下は許容する)、対象を絞った注釈によって確認済みの誤検出を抑制する、ツールのオレンジ色/不明な検出結果を確定済みのバグではなく、優先度の高いレビューキューとして扱う、などが挙げられます。

認定条件 SMART TS XL エンタープライズ規模での静的解析の適用

SMART TS XL 抽象解釈理論と企業の実情が交錯する領域、すなわち複数の言語にまたがるコードベース、数十年にわたる開発、そしてプログラムごとの形式的な検証を非現実的なものにする組織的な境界といった領域で活動する。

すべてのプログラムに単一の抽象ドメインを適用するのではなく、 SMART TS XLさん 静的コード分析 COBOL、JCL、Java、Python、RPG、PL/I、SQL、および最新のスタックといった環境内の各言語に適した構造分析手法を組み合わせ、ポートフォリオ全体にわたって品質指標、依存関係データ、およびセキュリティに関する調査結果を同時に生成します。

アプリケーション依存関係マッピング機能は、言語をまたがる呼び出しグラフにグラフ理論的分析を適用し、プログラム、データセット、ジョブストリームが言語の境界を越えてどのように接続されているかを特定します。これは、単一言語ツールでは実行できないシステム全体の分析です。つまり、システムレベルでの構造的推論であり、個々のプログラムの特性を証明するのではなく、それらがどのように接続されているかの特性を証明するものです。

影響分析機能は、依存関係グラフに対して到達可能性分析を適用します。つまり、あるノードで提案された変更が与えられた場合、そこから到達可能なすべてのノードのセットを計算します。これは、静的解析における「何が影響を受けるのか?」という問いに対し、実行時の観察や人間の推測ではなく、コードの構造に基づいて答えるものです。

実施するチーム向け レガシーの近代化 プログラム、 SMART TS XLの構造分析は、形式的な抽象解釈ツール(言語固有であり、設定にはドメインの専門知識が必要)と、大規模で文書化されていない多言語レガシーシステムが実際に何をしているのかを理解するという実際的なニーズとの間のギャップを埋めます。これは、実行中に最も高額な予期せぬ問題を発見したくないあらゆる近代化プログラムの前提条件です。

よくある質問

抽象解釈とモデル検査の違いは何でしょうか?どちらもプログラム検証のための形式手法です。抽象解釈は可能な状態の集合を過剰近似します(健全ではあるものの、不正確になる可能性があります)。モデル検査は状態空間を網羅的に探索します(完全ではあるものの、有限で境界のあるシステムでのみ実行可能です)。抽象解釈は大規模なプログラムに対応できます。モデル検査は小規模なモデルの複雑な特性に対応できます。これらは競合するものではなく、互いに補完し合う関係にあります。

抽象解釈は安全性が極めて重要なソフトウェアのためだけのものでしょうか?いいえ、そうではありません。しかし、その真価が最も発揮されるのは安全性が極めて重要なソフトウェアです。Inferは大手テクノロジー企業の標準的なCI/CDパイプラインで実行され、日常的なJavaやCコードにおけるヌルポインタの逆参照やリソースリークを検出します。適用する厳密さの度合いは選択可能です。一方では形式的な保証による完全な健全性、他方では軽量なヒューリスティック分析があり、実用的なツールのほとんどはその中間に位置します。

抽象解釈はCOBOLを分析できますか?抽象解釈は理論的には言語に依存しません。COBOLに適用するには、COBOLの操作、PICフィールド演算、REDEFINES句、レベル88条件名などに対する抽象転送関数を実装する必要があります。汎用抽象解釈ツール(Infer、Astréeなど)はCOBOLをサポートしていません。COBOLをネイティブに理解するエンタープライズ構造分析プラットフォームは、関連する静的分析手法を適用して、COBOLコードベース内の品質問題、デッドコード、およびアーキテクチャ上の問題を検出します。