
2026/07/26 21:21
Go アNALISIS フレームワーク:Go チームによるモジュール静的解析ツール
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
核心的な革新は、Go 用モジュラー静的解析フレームワークであり、「Analyzer」API を介して解析ロジックを実行ドライバーから分離するものである。各「Analyzer」構文は、「Name」、「Doc」、「Flags」、「Run」、「Requires」、および「FactTypes」を含むフィールドを通じて動作を定義する。ドライバーは、これらの解析器をリストにインポートし、その「Flags」をコマンドライン引数または GUI 設定にマッピングし、ヘルプインターフェースのためにドキュメントを活用することで、これらをオーケストレーションする。
モジュラリティのための主要な有効化手段として、「事実 (Facts)」の採用があり、これは
gob を使用してシリアライズ可能な中間結果であり、下位の解析が上位の解析にデータを供給することを可能にし、独立したコンパイルへの類推を促す。フレームワークは、分析器の「Run」関数に文法樹と型情報を提供する「Pass」型を通じてパッケージを処理する。診断機能は、「SuggestedFix」構文による非重複な「TextEdit」をサポートし、提案を行う。今後の開発には、有向非循環「Requires」グラフなどの妥当性情報チェックを確認するための「Validate」関数の実装が含まれる。また、このシステムは singlechecker および multichecker サブパッケージを通じてスタンドアロンツールをサポートしており、fmt.Printf 形式文字列を検証する printf チェッカーがその例である。本文
Go アナリシス API ガイド
概要
パッケージ分析は、モジュラー型の静的解析ツールとそれを駆動するプログラム(ドライバ)の間のインターフェースを定義します。
- 静的解析: Go のソースコードを対象に診断情報(誤り報告など)を生成する機能。
- チェッカー: 非公式な名称で、誤りを報告する解析器のこと(例:
)。printf チェッカー
- チェッカー: 非公式な名称で、誤りを報告する解析器のこと(例:
- モジュラー型: より上位のパッケージを検証する際に、より下位の依存パッケージから情報を保存・利用する仕組み。
- 部分的コンパイル(separate compilation)に類似した概念。
- 共通インターフェースのメリット:
- 多様な出所のチェッカーを選定・組み込み・再利用が容易になる。
- 以下のような様々なドライバプログラムで使用可能:
- コマンドラインツール(例:
)vet - テキストエディタや IDE
- ビルドおよびテストシステム(go build, Bazel, Buck など)
- テストフレームワーク、コードレビューツール、インデキサー、ドキュメントビューアなど。
- コマンドラインツール(例:
解析器 (Analyzer)
API の主要な型は
Analyzer です。ユーザーは論理的に定数である型の Analyzer を宣言することで解析を定義します。
アナライザの構造と設定
典型的な例(
go/analysis/passes/unusedresult):
var Analyzer = &analysis.Analyzer{ Name: "unusedresult", Doc: "一部の関数への呼び出しの結果が未使用であることをチェック", Run: run, ... } func run(pass *analysis.Pass) (interface{}, error) { ... }
ドライバと解析器の統合
ドライバプログラム(例:
vet)はセットの解析を実行し、診断情報を出力します。新しい解析器を追加するには、インポートリストに項目を加えるだけで済みます。
import ( "unusedresult"; "nilness"; "printf" ) var analyses = []*analysis.Analyzer{ unusedresult.Analyzer, nilness.Analyzer, printf.Analyzer, }
Analyzer 型のフィールド
Analyzer 型には以下のフィールドが含まれます:
| フィールド | 説明 |
|---|---|
| 解析器の名前。 |
| ドキュメント(1 行サマリーとパラグラフ)。 |
| 分析動作を制御する命名済みフラグ変数のセット。 |
| 解析を実行するための主要関数。ドライバはこれを呼び出します。 |
| 不適切な型付けに対処できるかどうかを示すフラグ。スキップするかどうかが決まります。 |
| 計算され、他の解析に利用可能となる結果値の型。 |
| この解析が依存する解析のリスト(実行順序を制約)。 |
| モジュラー解析で使用する事実の型。詳細は「モジュラー解析」節参照。 |
- Validate 関数:
の健全性チェックを行います(例:非循環な依存関係グラフ、一意な fact/結果型など)。Analyzer - Flags の扱い:
と異なり、解析用フラグはコマンドラインに直接宣言されず、ドライバが設定します。vet
パス (Pass)
Pass は単一の作業単位を表し、特定の Analyzer を特定の Go コードパッケージに適用する過程です。
Pass の構造
解析器の
Run 関数には以下の情報を提供します:
type Pass struct { Fset *token.FileSet Files []*ast.File // 構文木 OtherFiles []string // アセンブリなどの非 Go ファイル IgnoredFiles []string // 現在のパッケージに含まれないファイル Pkg *types.Package // パッケージ情報 TypesInfo *types.Info // 型情報 ResultOf map[*Analyzer]interface{} // 依存解析の結果 Report func(Diagnostic) // 診断情報の出力関数 ... }
ファイルの読み込みと処理
とOtherFiles
を含むファイル内容はIgnoredFiles
で読み込みます。Pass.ReadFile- 非 Go ファイルの診断: アセンブリファイルなどへの診断情報を報告する例は「asmdecl」または「buildtags」解析器参照。
content, err := pass.ReadFile(filename) if err != nil { ... } tf := fset.AddFile(filename, -1, len(content)) tf.SetLinesForContent(content) // 診断情報の出力 pass.Reportf(tf.LineStart(line), "oops")
結果と拡張機能 (ResultOf
)
ResultOf
: 依存解析器が計算した結果のマップ。ResultOf- 利点: コア API に依存を追加せずに後の解析器を拡張可能。必要な機能分のみを実装すればよい(支払うコスト削減)。
- 例:
- 「ctrlflow」:制御フローグラフ (
) を返す。*ctrlflow.CFGs - 「inspect」:構文木の効率的な走査に役立つ値を返す。
- 「buildssa」:SSA フォームの中間表現を構築する。
- 「ctrlflow」:制御フローグラフ (
診断情報の報告 (Report
)
Report
構造体はソース位置に関連するメッセージを含みます。Diagnostic- Category: 複数の種類の診断がある場合の分類識別子(オプション)。
- 重症度を示すフィールドはないため、フィルタリングと優先付けはドライバ側でカスタマイズする必要があります。
ヘルパーメソッド
Pass は文字列フォーマットによる新しい診断報告のためのメソッドを提供します:
pass.Reportf(pos token.Pos, format string, args ...any)
生のテキストファイルの診断
1行に対して診断情報を報告するシーケンス例:
content, err := pass.ReadFile(filename) if err != nil { ... } tf := fset.AddFile(filename, -1, len(content)) tf.SetLinesForContent(content) ... pass.Reportf(tf.LineStart(line), "oops")
事実(Fact)を用いたモジュラー解析
大規模プログラムにおける部分的コンパイルのメリット(効率性、拡張性)を静的解析にも適用するのがモジュラー解析です。
基本概念
- ファクト (Fact): 興味深い結果への踏台となる事実(例:「f は printf wrapper です」)。
- 役割: 新しい種類の事実型を定義し、オブジェクトやパッケージに関連付け、既存の事実を検索する機能を提供。
解析器での Fact の宣言
var Analyzer = &analysis.Analyzer{ Name: "printf", FactTypes: []analysis.Fact{new(isWrapper)}, ... } type isWrapper struct{} // => *types.Func f “is a printf wrapper”
シリアル化とプロパゲーション
- 責任: ドライバは事実を 1 つのパッケージから別のパッケージ(アドレス空間をまたいで)に伝播させる必要があります。
- シリアル化:
を使用して事実をシリアル化する必要がある(決定論的)。encoding/gob- デフォルトが不適切な場合、
/GobEncoder
インターフェースを実装できます。GobDecoder - 事実は状態を持たない(stateless)べきです。
- 理由: ビルダシステムでコンテンツアドレス可能なキャッシュを使用する際に見かけ上のキャッシュミスを回避するため。
Pass での Fact の管理
type Pass struct { ... ExportObjectFact func(types.Object, Fact) ImportObjectFact func(types.Object, Fact) bool ExportPackageFact func(fact Fact) ImportPackageFact func(*types.Package, Fact) bool }
- 制約: 解析器は現在のパッケージまたはそのオブジェクトに関連する事実のみをエクスポートできますが、インポート依存関係にある任意の事実をインポートできます。
- 実装注意: 一部のドライバ(Bazel など)は標準ライブラリパッケージに対して解析器を適用しないため、事実を標準パッケージで利用可能とは見做すべきではありません。
アナリシストのテストとコマンド
テスト (analysistest
)
analysistestanalysistest サブパッケージはユーティリティを提供し、以下のような簡易なテストが可能になります:
- testData ファイルのパッケージに対して解析器を実行。
- 予想されるすべての診断情報と事实在が報告されているかチェック。
- 期待値の記述: 入力コード内の
コメントを使用。// want ...
スタンドアロンコマンド
vet が複数の解析器をインポートしますが、独自の分析コマンドを作成したい場合:
- SingleChecker (
):singlechecker- 単一の解析器を実行するコマンドの
関数を生成。main
- 単一の解析器を実行するコマンドの
package main import ( "golang.org/x/tools/go/analysis/passes/findcall" "golang.org/x/tools/go/analysis/singlechecker" ) func main() { singlechecker.Main(findcall.Analyzer) }
- MultiChecker (
):multichecker- 複数の解析器を提供するツール向け。Analyzers のリストを与えます。
API リファレンスサマリー
関数と型一覧
-
func Validate(analyzers []*Analyzer) error- 誤設定があった場合のエラー報告機能。
- チェック項目:名前の有効性、Doc の空有無、Run の nil 判定、依存グラフの非循環性、Fact 型の一意性及びポインタ型であること。
-
type Analyzer- 解析関数とオプションを記述する主要型。
- メソッド:
String() string
-
type CycleInRequiresGraphError struct- 依存関係グラフに循環が存在する場合のエラー構造体。
-
type Diagnostic- ソース位置または範囲に関連するメッセージ。
- 特徴:
- オプションの
(定数)で分類。Category
値はPos
に相対的に解釈される。Pass.Fset
が提供された場合、診断はその範囲に適用される。End
- オプションの
-
type Fact interface- 解析中に生成される中間事実。
- 特徴:
- 命名された宣言(
)またはパッケージ全体に関連付けられる。types.Object - 「決して戻らない」ような述語を表す(主体ではない)。
- アドレス空間を超えて生成・消費可能。
- ビルド出力内の型エクスポートデータに類似。
- 各パスは、直接インポートしたパッケージから始まる事実セットで動作。
- 必須: 型はポインタである必要がある (
でチェック)。Validate - エンコード:
を使用(カスタマイズ時はencoding/gob
/GobEncoder
)。GobDecoder - エクスポート後は変更不可。
- 命名された宣言(
-
|type Module
: モジュールおよび読み込みエラーを表す。type ModuleError struct -
|type ObjectFact
: 対象と関連付けられた事実の具体化型。type PackageFact -
type Pass- 解析器とドライバの間のインターフェースを形成。入力・出力両方のコンポーネントを持つ。
- 注意: 1 つのパスは他のパスの結果に依存可能だが、
関数は並行して呼び出してはいけない。Run - メソッド:
ReportRangef(rng Range, format string, args ...any)Reportf(pos token.Pos, format string, args ...any)String() string
-
type Range- 範囲を提供する型(
と同等)。ast.Node
- 範囲を提供する型(
-
type RelatedInformation struct- 診断に関連する情報(例:変数の重複宣言時の既存宣言リスト)を含む。
-
type SuggestedFix struct- ユーザーがコードに適用できる変更(
,Message
)。TextEdits - 制約:
は重なり合ったり、他のパッケージを含んではいけない。挿入順序は重要でないが、適用順序を決定する必要がある。TextEdit
- ユーザーがコードに適用できる変更(
-
type TextEdit- Pos と End の間のコードを新しいテキストに置換する操作を表す。各エディットは単一ファイルに適用される。