[インデックス 10277] ファイルの概要
このコミットは、Goの抽象構文木(AST)処理において、SortImports
関数に存在していたバグを修正する重要な変更です。Russ Cox氏により2011年11月7日にコミットされ、AST操作における位置情報の適切な処理と、インポート文の書き換え機能の改善を含んでいます。
コミット
commit dfe03bb204f7e7b1417434e9c1c28f7a665e190e
Author: Russ Cox <rsc@golang.org>
Date: Mon Nov 7 14:44:06 2011 -0500
go/ast: fix bugs in SortImports
Tests are in gofix, since the bugs arise in rewritten ASTs.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5365043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dfe03bb204f7e7b1417434e9c1c28f7a665e190e
元コミット内容
このコミットは以下の2つのファイルに対して変更を加えています:
- src/cmd/gofix/import_test.go: テストケースの追加(90行追加)
- src/pkg/go/ast/import.go:
SortImports
関数のバグ修正(4行追加、1行削除)
変更統計:
- 2ファイル変更
- 101行追加
- 6行削除
変更の背景
2011年当時、Goはまだ初期段階にあり、開発チームは言語とツールチェーンの安定化に取り組んでいました。gofix
ツールは、Go言語のAPIの変更に伴って既存のコードを自動的に更新するために開発された重要なツールでした。このツールは、ASTを操作してコードの構造を変更し、古いAPIを新しいAPIに自動変換していました。
しかし、ASTの書き換え処理において、SortImports
関数が適切に位置情報を維持できていないという問題が発見されました。特に、インポート文の文字列リテラルが書き換えによって長さが変更された場合、元の位置情報が不正確になってしまうバグが存在していました。
前提知識の解説
抽象構文木(AST)とは
抽象構文木(Abstract Syntax Tree)は、プログラムソースコードの構文構造を木構造で表現したものです。Goのgo/ast
パッケージは、Goソースコードを解析してASTを生成し、それを操作するための機能を提供します。
go/astパッケージ
Goの標準ライブラリの一部であるgo/ast
パッケージは、以下の主要機能を提供します:
- Goソースコードの構文解析
- ASTノードの表現と操作
- AST内のインポート文のソートと重複排除
- 位置情報(ファイル内の行・列位置)の管理
gofixツール
gofix
は2011年に導入されたGo公式ツールで、APIの変更に対応するためのソースコード自動変換を行います。このツールの特徴:
- ASTレベルでのコード解析と変換
- パターンマッチングによる自動書き換え
- インポート文の自動追加・削除・変更
- コメントと位置情報の保持
SortImports関数
SortImports
関数は、go/ast
パッケージの重要な機能の一つで、以下の処理を行います:
- ファイル内の連続するインポート文の識別
- インポートパスによるアルファベット順ソート
- 重複インポートの除去(可能な場合)
- コメントの適切な再配置
技術的詳細
修正されたバグの詳細
このコミットで修正された主要なバグは、SortImports
関数における位置情報の不適切な計算でした。具体的には:
問題の原因:
// 修正前(バグのあるコード)
pos[i] = posSpan{s.Pos(), s.End()}
s.End()
メソッドはs.Path.Value
の文字列長を参照して終了位置を計算していました。しかし、gofix
によってインポートパスが書き換えられると、この文字列の長さが変わってしまい、位置情報が不正確になっていました。
修正後の解決策:
// 修正後(バグ修正版)
pos[i] = posSpan{s.Pos(), s.Pos() + 1}
この修正により、終了位置をs.Pos() + 1
に固定することで、文字列の長さ変更による影響を回避しました。
位置情報処理の改善
さらに、インポート名(エイリアス)の位置情報も適切に更新されるよう修正されました:
if s.Name != nil {
s.Name.NamePos = pos[i].Start
}
この追加により、インポートにエイリアスが設定されている場合の位置情報も正しく維持されるようになりました。
コアとなるコードの変更箇所
1. src/pkg/go/ast/import.go:67-72
修正前:
for i, s := range specs {
pos[i] = posSpan{s.Pos(), s.End()}
}
修正後:
for i, s := range specs {
// Cannot use s.End(), because it looks at len(s.Path.Value),
// and that string might have gotten longer or shorter.
// Instead, use s.Pos()+1, which is guaranteed to be > s.Pos()
// and still before the original end of the string, since any
// string literal must be at least 2 characters ("" or ``).
pos[i] = posSpan{s.Pos(), s.Pos() + 1}
}
2. src/pkg/go/ast/import.go:155-157
追加されたコード:
if s.Name != nil {
s.Name.NamePos = pos[i].Start
}
3. src/cmd/gofix/import_test.go:116-131
rewriteImportFn関数の拡張:
// 修正前:single pair対応
func rewriteImportFn(old, new string) func(*ast.File) bool
// 修正後:multiple pairs対応
func rewriteImportFn(oldnew ...string) func(*ast.File) bool
コアとなるコードの解説
posSpan構造体の使用法
posSpan
は位置情報の範囲を表現する構造体で、開始位置と終了位置を保持します。修正では、この終了位置の計算方法を変更することで、文字列長の変更に対する耐性を持たせました。
type posSpan struct {
Start token.Pos
End token.Pos
}
位置情報の保証
修正後のコードでは、s.Pos() + 1
を使用することで以下の保証を得ています:
- 安全性:
s.Pos() + 1
は常にs.Pos()
より大きい - 妥当性: 文字列リテラルは最低2文字(
""
または` `
)なので、元の終了位置より前に位置する - 一貫性: 文字列の長さ変更に影響されない
rewriteImportFn関数の改善
テストコードでは、複数のインポート書き換えを一度に処理できるよう関数シグネチャが変更されました:
func rewriteImportFn(oldnew ...string) func(*ast.File) bool {
return func(f *ast.File) bool {
fixed := false
for i := 0; i < len(oldnew); i += 2 {
if imports(f, oldnew[i]) {
rewriteImport(f, oldnew[i], oldnew[i+1])
fixed = true
}
}
return fixed
}
}
この変更により、一つのテストケースで複数のインポート書き換えパターンをテストできるようになりました。