cmd/vet, x/tools/go/analysis/passes/errorsastype: detect misuse of \\`AsType\\` in \\`else if\\`
要約
概要
errors.AsType[T] を else if の初期化文で連続して使うと、意図せず前段の型付きゼロ値を渡してしまうバグを検出する解析器を、go vet の標準解析器スイートに追加しようとするproposalです。
ステータス変更
active → hold
2026-07-22の提案レビュー会議(@aclements, @adonovan, @bradfitz, @cherrymui, @ianlancetaylor, @neild, @rolandshoemaker)にて保留となりました。理由は、issue提案者本人が実装した、より汎用的なif/elseシャドーイング検出解析器(go/analysis/passes/ifshadow、CL 801300)を29,000モジュールに対して試したところ、38モジュールで70件のヒットがあり、そのうち抽出した20件中ほぼ全てが真陽性(true positive)だったものの、ヒット総数自体が少なく、if/else文自体は非常に多く出現するパターンであるためコスト対効果が判断しづらい、という点にあります。より広い検出パターンの方が有望である可能性があり、追加の分析・検証が必要と判断され、その結論が出るまでこのproposalは保留とされました。
技術的背景
現状の問題点
errors.AsType[T](Go 1.26で追加された、型パラメータを使うerrors.As相当のジェネリック関数)をelse ifの初期化文で連鎖させると、後続の呼び出しが前段の変数をシャドーイングしてしまい、意図しない型付きゼロ値を参照するバグが発生します。
var err error
if err, ok := errors.AsType[*FooErr](err); ok {
doSth(err)
} else if err, ok := errors.AsType[*BarErr](err); ok {
doSth(err)
}
2番目のerrors.AsTypeに渡されるerrは、外側のerror型変数ではなく、1つ目のifの初期化文で宣言された*FooErr型の変数(okがfalseのため常に型付きnil)です。これは「シャドーイング解析器」(x/toolsのshadow)でも検出が難しく、単に到達不能なコードになるだけでなく、FooErrがUnwrapのようなポインタレシーバのメソッドを持っていると、nilポインタ参照でランタイムパニックを引き起こす可能性があります。
提案された解決策
当初は既存のerrorsas解析器に新しい診断を追加する案でしたが、レビューア(@adonovan)の指摘でAsとAsTypeは性質が異なるため、errorsastypeという新規解析器としてgoplsの内部実装をx/tools/go/analysis/passes/errorsastypeとして切り出し、go vetの解析器スイートに組み込む提案に再構成されました。当初の実装アイデアは「else ifの初期化文にあるerrors.AsType呼び出しの第一引数が、直前のif/else ifアームで宣言された結果変数と同一のtypes.Objectを指しているかを検出する」という狭いパターンマッチングでした。
これによって何ができるようになるか
このproposalが実現すれば、go vet(およびgoplsのエディタ診断)が、複数のエラー型をerrors.AsTypeで判定する連鎖的なif/else ifパターンにおけるシャドーイングバグを、コンパイル前に自動的に警告してくれるようになります。特に以下のような場面で有用と考えられます。
- 複数のカスタムエラー型を型ごとに処理を分岐させるハンドラコードのレビュー・保守時
Unwrapなど、型付きnilに対する呼び出しでパニックしうる潜在的なバグの早期発見- コードレビュー時に人間が見逃しやすい微妙なシャドーイングを機械的に検出
コード例
// Before: 一見正しく見えるが、2番目のAsTypeが誤った値を参照するバグ
var err error
if err, ok := errors.AsType[*FooErr](err); ok {
handleFoo(err)
} else if err, ok := errors.AsType[*BarErr](err); ok { // err は *FooErr型のゼロ値(バグ)
handleBar(err)
}
// After: 解析器導入後、go vet が該当箇所を警告
// possible misuse of shadowed variable err
// 修正例:変数名を分けて衝突を回避
var err error
if ferr, ok := errors.AsType[*FooErr](err); ok {
handleFoo(ferr)
} else if berr, ok := errors.AsType[*BarErr](err); ok {
handleBar(berr)
}
議論のハイライト
- 提案者(@mateusz834)は実際のコードレビューでこの誤りを目撃し、既存の
shadow解析器(#75368)でも「グローバル変数のシャドーイング」としては報告されるが、「前段のif変数をさらにシャドーイングしている」ことまでは伝わらないと指摘しました。 - 当初は既存の
errorsas解析器を拡張する案でしたが、@adonovanの提案でAsとAsTypeに共通点がないことから、新規にerrorsastype解析器として分離し、gopls先行導入・go vet追加は別途proposalとする方針に転換しました。 - レビュー中に「
Tとerrの型がともに非interface型で等しければ冗長、異なれば必ず失敗する」というより単純な代替案が出ましたが、Unwrap/Asメソッドの存在や、Tがポインタ型に限らない(構造体・文字列型もありうる)ことから、単純な型比較だけでは不十分と判断されました。 - 2026-07-15前後の議論で「本質的な検出対象は
AsType固有ではなく、外側で宣言された変数がifの:=でシャドーイングされ、後続のelse if条件式で参照される」という、より一般的なシャドーイングパターンではないかという指摘が出ました。 - この一般化案を検証する実装(
ifshadow解析器)がCL 801300として作成され、大規模コーパス(29Kモジュール)に対する実験でほぼ真陽性のみを検出できたものの、ヒット数自体が少なく、if/elseという頻出構文全体を対象とすることのコスト対効果が不透明なため、さらなる分析が必要として保留(hold)となりました。
関連リンク
- Proposal Issue github.com/golang/go
- Review Comment proposal review meeting
- 関連Issue: errors: AsType (As with type parameters) #51945
- Proposal Issue
- Review Minutes
- 関連Issue: x/tools/go/analysis/passes/errorsas: support errors.As provided by other packages #44784
- 関連Issue: proposal: x/tools/.../shadow: specific cases that are unlikely to have false positives #58917