連載|静的解析×AIエージェントを始めよう! 第4回 GitHub Copilot×SARIFの留意点(後編)

前々回から始めた検証 —SARIF Viewerで表示した静的解析の指摘に対して、VS Code上でGitHub Copilot Chatを使って説明・修正する際に、GitHub Copilotは果たして処理の流れを理解した上で動作しているのかを確かめる— も大詰めとなりました。

今回は、終盤の検証と、全体のまとめを行います。

ステップ6:relatedInformationは「落ちている」のか、「そもそも表現しきれない」のか

extensions/copilot/src/vscodeTypes.tsを見ると、Copilot Chatが使っているDiagnostic・DiagnosticRelatedInformationの型は、次のように定義されています。

// extensions/copilot/src/vscodeTypes.ts (microsoft/vscode, main)
export import Diagnostic = vscode.Diagnostic;
// ...
export import DiagnosticRelatedInformation = vscode.DiagnosticRelatedInformation;

つまり、Copilot Chat側が独自に定義や実装を持っているわけではなく、公開されているVS Code拡張機能API(vscodeモジュール)のDiagnostic・DiagnosticRelatedInformationをそのままre-exportしているだけです。フィールドの構成(range・message・severity・relatedInformationなど、そしてDiagnosticRelatedInformationが{ location, message }というフラットな組であること)は、VS Code拡張機能APIとして公開・安定している仕様です。詳しくはVS Code Extension APIの公式リファレンスを参照してください。

ステップ5で見た通り、Copilot Chat側にはこのrelatedInformationを読んで関連コードをプロンプトに追加する仕組み(DiagnosticRelatedInfo)がすでに存在します(ただし右クリックのFixでは、5-1・5-2で見た差し替えによりこの仕組みに到達しません)。ただし、ここで「じゃあSARIF ViewerがcodeFlowsをrelatedInformationに詰めるようにすれば解決だ」と単純化するのは早計です。実は、この一言の前には2つの壁があります。

壁1:非必須フィールドの使われ方はツールごとに違い、汎用の変換を決められない

codeFlowsは、SARIFの仕様では非必須のフィールドです。値を入れるかどうかも、どこまで詳しく入れるかも、各ツールベンダの判断に委ねられています。前述のCppcheckのように、locations[]の使い方そのものが仕様の想定から外れている例すらあります。SARIF Viewerは特定のツールに特化した実装ではありません。そのため、ヒューリスティックに一部を投影することはできても、どのツールの出力に対しても意味を保てる汎用的な変換を決めるのは困難です。

codeFlowsを例にとると、次のような判断がいずれも未確定のまま残ります。

  • codeFlowsは配列であり、1つの指摘に対して複数のCodeFlow(別の実行経路、代替シナリオなど)が入りうる。どれを代表として選ぶべきかに、汎用的な正解はない
  • 1つのthreadFlowが持つステップ数にも上限はない。特にtaint解析のように複数の関数・ファイルをまたぐ経路では、数十ステップに及ぶことも珍しくない。
  • ステップの重要度を示すimportance(essential / important / unimportant)は任意項目であり、ツールが必ず設定しているとは限らない。「essentialだけ抜き出す」というフィルタも、値が入っていなければ機能しない
  • ThreadFlowLocation.location(Location型)自体も任意項目であり、physicalLocationを持たずlogicalLocations(関数名などの論理的な位置)だけを持つステップもありうる。この場合、ファイルと行番号の組であるvscode.Locationにはそもそも変換できない

壁2:relatedInformationという入れ物自体が、SARIFのCodeFlowを表現しきれない

ここまでは「何を詰めるか」の話でした。仮にそれが決まったとしても、詰め先のrelatedInformation自体に限界があります。前述の通り、Diagnostic.relatedInformationはDiagnosticRelatedInformation[]、つまり{ location, message }の配列です。配列としての並び順そのものは保持できます。 一方で各要素には、SARIF CodeFlow専用の意味論を保持するフィールドはありません。

SARIFのThreadFlowLocation(CodeFlowの1ステップ)は、@types/sarifの型定義を見ると、次のようなフィールドを持っています。

// @types/sarif(SARIF 2.1.0の型定義パッケージ)
export interface ThreadFlowLocation {
    /** An integer representing the temporal order in which execution reached this location. */
    executionOrder?: number;
    /** Specifies the importance of this location ("essential" | "important" | "unimportant"). */
    importance?: ThreadFlowLocation.importance;
    /** A set of distinct strings that categorize the thread flow location
     *  (e.g. 'acquire', 'release', 'call', 'return', 'branch', 'taint', ...). */
    kinds?: string[];
    /** The code location. */
    location?: Location;
    /** An integer representing a containment hierarchy within the thread flow. */
    nestingLevel?: number;
    // ...
}

Diagnostic.relatedInformationは配列なので、各要素をCodeFlowの実行順に並べれば、要素同士の前後関係自体は保持できます。ただし、これはあくまで配列の並び順が保たれるという意味です。 CopilotのDiagnosticRelatedInfoが生成するプロンプトでは、「This diagnostic has some related code:」という見出しの下に、各要素に対応するコードが順番に並びます。SARIF Viewer自身がエディタ上に出す"Step 1: ..."という注釈のように、明示的に番号付けされるわけではありません。

一方、SARIFのCodeFlowには、executionOrder・nestingLevel・importance・kinds・stateなど、位置とメッセージだけでは表せない情報があります。さらに、1つの結果に複数のcodeFlowやthreadFlowを持つこともできます。

DiagnosticRelatedInformationが持つのは基本的にlocationとmessageです。そのため、CodeFlowの一部を関連情報として渡すことはできても、SARIFが持つ構造全体をそのまま移すことはできません。

まとめると、

  • codeFlowsをはじめとする非必須フィールドは、使われ方がツールごとに違う:一部をヒューリスティックに投影することはできても、汎用かつ無損失な変換を決めるのは困難
  • 仮に詰めても、relatedInformationで維持できるのは主に位置・メッセージと配列順であり、CodeFlowのネスト・重要度・kinds・複数threadFlow等の意味論までは表現できない:vscode.Diagnostic APIの構造的な限界(静的解析専用ではない汎用APIである以上、ある程度は避けられない)

このように、CodeFlowをそのまま表現することは難しい一方で、ruleIdをDiagnostic.codeへ入れる、sourceを設定する、relatedLocationsの一部をrelatedInformationへ投影するといった、標準フィールドへの追加投影の余地は残っています。

ステップ7:SARIF Viewer自身は、なぜCodeFlowを表示できるのか

ここまでを素直に読むと、「SARIFのCodeFlowはDiagnosticに変換された時点で消えてなくなる」ように見えます。しかし実際には、SARIF Viewer自身のUI—サイドパネルの「Analysis Steps」タブや、エディタ上に出るStep 1: ...という注釈—には、CodeFlowがきちんと表示されます。

これは、SARIF ViewerがDiagnosticとは別に2つのルートを持っているからです。

  • Detailsパネル(Webview):読み込んだSARIFログをそのまま保持している内部のストアからresultを受け取って描画しており、vscode.Diagnosticをまったく経由していません
  • エディタ上の注釈・ハイライト:ResultDiagnosticの.resultからSARIFのresultを取り出し、result.codeFlows[0].threadFlows[0].locationsを使って描画しています

後者のコードが次の箇所です。

// src/extension/index.activateDecorations.ts (microsoft/sarif-vscode-extension)
const diagnostic = context.diagnostics[0] as ResultDiagnostic | undefined;
if (!diagnostic) return undefined;

const result = diagnostic?.result;
if (!result) return undefined;

https://github.com/microsoft/sarif-vscode-extension/.../index.activateDecorations.ts

ステップ3-2で見たものと同じcontext.diagnosticsですが、SARIF Viewer自身はこれをResultDiagnosticとして扱い、独自プロパティの.resultを参照しています。as ResultDiagnosticというキャストは、その型を自分で定義しているからこそ自然に書けるものです。技術的には、他の拡張機能でも.resultを読みに行くコードを書けなくはありません。ただしそれは、他の拡張機能が内部で持っている非公開のプロパティに依存することになります。

GitHub Copilot Chatは、当然このSARIF Viewer固有の型を知りませんし、依存もしていません。Copilotが汎用的に扱えるのは、公開インターフェースであるvscode.Diagnosticの標準フィールドだけです。つまり、SARIF Viewerが画面に出せていることと、Copilotがそれを使えることは、根本的に別の話なのです。

なお、この表示経路もcodeFlows[0]のthreadFlows[0]だけを見ているため、SARIFのCodeFlow構造を一般的に完全再現しているわけではありません(複数ファイルにまたがる場合に起きる不具合は、巻末の付録にまとめています)。

結論:SARIFの構造化情報は、Copilotに届く前に落ちている

ここまでの各ステップを踏まえると、SARIFの指摘がGitHub Copilotの推論材料になるまでの流れは、次のようになります。

項目SARIFが​持って​いる​情報Copilot​拡張機能​から​参照​可能な​標準​Diagnostic​情報モデル用診断コンテキストへの反映
指摘メッセージresults[]​​.message​​.text⭕ Diagnostic​.message
Severityresults[]​​.level⭕ Diagnostic​​.severity△ 主に対象診断の選別に使われ、診断テキストとして常に明示されるわけではない
ルール IDresults[]​​.ruleId❌ SARIF ViewerはDiagnostic​​.codeへ投影していない
ルール定義のメッセージ文runs[]​.tool​.driver​.rules[]​.messageStrings⭕ Diagnostic.message(result.message.idで参照する形式のSARIFでは、ルール定義側の文面が採用される)
ルール定義のその他(説明・ヘルプ等)rules[]​.fullDescription、​rules[]​.helpなど❌ 標準フィールドへ投影されていない
代表位置results[]​​​.locations[0]⭕ Diagnostic​​.range⭕ 周辺コードの取得に利用される
locations[]の2番目以降results[]​​.locations[1..]❌ relatedInformationへ投影されていない
CodeFlowresults[]​​​.codeFlows[]​​​.threadFlows[]​​​.locations[]❌ 標準フィールドへ投影されていない
relatedInformationSARIF Viewer側で未設定経路によって異なる:右クリックFix・通常のインラインチャットは読まない。パネル等で/fixが正しくFixIntentに分類された場合のみDiagnosticRelatedInfoが読むSARIF Viewer由来では空

Cppcheckのnull_ptr.cの例で言えば、モデルの診断コンテキストへ明示的に反映される中心情報は、代表位置付近のコードとNull pointer dereference: ptrという診断メッセージです。SARIFファイルに含まれていた2番目のlocations[]要素や、別のツールが出力するcodeFlowsの構造が、そのまま診断コンテキストへ追加されるわけではありません。

さらに、通常のインラインFix系の入口では、ステップ5-1・5-2で見た通り、/fix ${diagnostics}という文字列を組み立てていた場合でもVS Codeコアによって"Fix the attached problem(s)"という定型文に差し替えられ、診断はDiagnosticVariableが診断メッセージと位置を書き出すだけです。この経路では上の表のrelatedInformationの列はそもそも参照されず、Copilotが受け取るのは診断メッセージと位置情報だけです。

このコード例は単純なので、Copilotはメッセージと周辺コードだけからでも正しい修正案・説明を生成できるでしょう。しかし、実務のコードでは、原因箇所と発生箇所が別の関数・別のファイルにまたがっていたり、CodeFlowが3ステップ以上の長い経路をたどっていたりするケースも珍しくありません。そうした指摘に対して右クリックのFix/Explainをそのまま実行すると、Copilotは診断メッセージと代表位置を主な手掛かりとして推論することになり、原因箇所を見ないまま、発生箇所の症状だけを取り繕う修正案が出てくる可能性があります。

だから、気を付けて

SARIF ViewerとGitHub Copilot Chatを組み合わせたFix/Explainでは、モデルに届く診断情報は診断メッセージと代表位置が中心で、CodeFlowのような構造化情報は加味されません。

とはいえ、最近のAIエージェントは賢いので、診断メッセージと周辺コードだけでも原因にたどり着いてしまうことは多いはずです。問題は、右クリックのFix/Explainという入口では渡せるコンテキストがそもそも少ないことにあります。せっかく静的解析ツールが原因から発生までの経路を出力しているのに、それを見せないまま推論させるのはもったいない、という話です。

もっとも、MISRAのようなパターンチェック系の指摘であれば、1行分の情報だけでも判断できることが多く、この制約はあまり問題になりません。効いてくるのは、原因箇所と発生箇所が離れる、データフロー解析系の指摘です。

そうした指摘に対してまずできるのは、SARIFファイルそのものをAIエージェントに渡してしまうことです。右クリックのFix/Explainという入口を経由せず、該当するresultを直接読んでもらえば、CodeFlowも含めて元の情報がそのまま推論の材料になります。

「だったらSARIF Viewerを使う価値は?」という話になりますよね。SARIF Viewerを入口にしたまま、もっと多くの情報をAIエージェントへ渡せないか。次回のコラムでは、そこを実際に試していきます。

まとめ

  • SARIF ViewerはSARIFのresultをResultDiagnostic(vscode.Diagnosticの派生クラス)へ投影して、VS Code標準の診断機構へ登録している
  • この時点で標準フィールドに入るのは主にrange・message・severityであり、locations[1..]やcodeFlowsはrelatedInformationへ投影されていない
  • .resultには元SARIFが残っており、Copilot側からも同じオブジェクトを参照できる。ただしCopilotはSARIF Viewer固有型へ依存せず、標準Diagnosticとして扱うため.resultを利用しない
  • PROBLEMSパネルは標準Marker情報を通して診断を扱うため、.resultのような独自プロパティは渡らない
  • 普段よく使う右クリックやコマンドなどの通常のインラインFix系入口では、VS CodeコアのInlineChatControllerが"Fix the attached problem(s)"という定型文に差し替えてしまうため、Copilotの意図分類(FixIntent)自体に通常はたどり着かない。この経路で実際にモデルへ渡るのは、ライブマーカーを手掛かりに選び直した診断をDiagnosticVariableが診断メッセージ・位置・code・severity(重大度)としてプロンプト化した内容だけ。ただしSARIF Viewer由来ではcodeが未設定で、relatedInformationも読まれない
  • relatedInformationやCookbookによる追加コンテキストを読むDiagnosticRelatedInfoが呼ばれるのは、/fix ...が正しくFixIntentに分類された場合(典型的にはCopilot Chatパネルへの直接入力)に限られる。その場合でも、SARIF ViewerがrelatedInformationを設定していないため、SARIF CodeFlowはそこからは届かない
  • SARIF Viewer自身は、読み込んだSARIFを内部に保持し、ResultDiagnostic.resultからも参照して自分のUI表示(Analysis Stepsタブやエディタ上の"Step 1: ..."という注釈)に使っている。ただし表示経路ではcodeFlows[0].threadFlows[0]を使っており、CodeFlow構造全体を一般的に完全再現しているわけではない
  • 複数ファイルにまたがるCodeFlowでは、Array.prototype.filterへasyncコールバックを渡しているためファイル絞り込みが機能せず、誤った位置への装飾につながる実装上の問題がある(巻末の付録を参照)
  • relatedInformationは配列順を保持できるため単一threadFlowの線形な順序はある程度表現できるが、nestingLevel・importance・kinds・複数threadFlowなどSARIF CodeFlow固有の意味論までは表現できない
  • 原因箇所と発生箇所が離れた指摘に対しては、右クリックのFix/Explainだけに頼らず、SARIFファイルそのものをAIエージェントに渡すなど、コンテキストの渡し方を工夫する余地がある

SARIFを「静的解析×AIエージェント」の入口として使うこと自体は、前回紹介した通りお手軽で有効なアプローチです。ただし、その入口を通った先でAIエージェントが実際に何を読み、何を読んでいないのかを把握しておくことは、誤った修正案を鵜呑みにしないためにも欠かせません。次回は、実際に手を動かしながら、CodeFlowをどこまでAIエージェントへ渡せるのか、そしてどこで頭打ちになるのかを見ていきます。

付録:複数ファイルにまたがるCodeFlowの表示に見つかった不具合

本稿の主題からは外れますが、コードを追う過程で見つけた点を記録しておきます。

SARIF Viewerは、ResultDiagnostic.resultに残しているSARIFのcodeFlowsを使って、エディタ上にStep 1: ...といった注釈やハイライトを独自に描画しています。その描画処理のうち、CodeFlowの各ステップを「現在開いているファイルに属するものだけ」に絞り込む箇所が、意図通りに動いていません。

// src/extension/index.activateDecorations.ts (microsoft/sarif-vscode-extension)
const locationsInDoc = locations.filter(async tfl => {
    const [artifactUriString] = parseArtifactLocation(result, tfl.location?.physicalLocation?.artifactLocation);
    return await baser.translateLocalToArtifact(currentDoc.uri) === artifactUriString;
});

https://github.com/microsoft/sarif-vscode-extension/.../index.activateDecorations.ts

Array.prototype.filterはコールバックの戻り値を同期的に真偽判定します。一方、async関数は呼び出すとPromiseを返します。Promiseオブジェクト自体はtruthyであるため、このコードではawait後の比較結果ではなく、返されたPromiseそのものが判定に使われます。

その結果、locationsInDocは実質的に「絞り込み前の、CodeFlow全ステップ」と同じものになり、後続の

const ranges = locationsInDoc.map(tfl => driftedRegionToSelection(diffBlocks, currentDoc, tfl.location?.physicalLocation?.region, originalDoc));
editor.setDecorations(decorationTypeHighlight, ranges);

で、本来は別ファイル用だった行・列番号まで、現在開いているドキュメント上のRangeとして処理され得ます。原因箇所と発生箇所が別ファイルにまたがるCodeFlowでは、位置情報の組み合わせによって、現在のファイル上の無関係な位置へ装飾が出ることがあります。

このコードは、2026年8月時点でもmicrosoft/sarif-vscode-extensionのmainブランチ最新コミット(bd3eeb1、GitHubのコミット履歴上では2025年6月11日付と表示)にそのまま残っています。


第2回でDiagnostic化による情報欠落を確認し、第3回でCopilotへの渡り先がmessage中心であることを検証、第4回でcodeFlowsとStep表示の乖離を整理しました。現状の制約は明確になりました。

次回以降のコラムでは、今回まとめた制約を踏まえた上で、CodeFlowも含めたより多くのコンテキストをAIエージェントに渡すための工夫についても取り上げていく予定です。ご期待ください。

このコラムの著者

株式会社ユビキタスAI

エンベデッド第3部 セールス&PM セクション

藤江 克彦​(ふじえ かつひこ)

より詳しく技術や​関連製品について​知りたい方へ

本コラムに関係する技術や関連する製品について知りたい方は、お気軽にご相談ください。


製品情報

高精度静的解析ツール

CodeSonar

世界トップレベルの解析能力で不具合や脆弱性の原因となるソースコードのバグを検出
製品ページを見る
連載

連載|静的解析×AIエージェントを始めよう! 第3回 GitHub Copilot×SARIFの留意点(中編)

コラムを読む
連載

連載|静的解析×AIエージェントを始めよう! 第2回 GitHub Copilot×SARIFの留意点(前編)

コラムを読む
連載

連載|静的解析×AIエージェントを始めよう! 第1回 SARIFを使う

コラムを読む

CWE-1000は脆弱性じゃない⁉ CWEが直感的に分かりにくい理由

コラムを読む
メニューを閉じる
一つ前に戻る