Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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つのファイルに対して変更を加えています:

  1. src/cmd/gofix/import_test.go: テストケースの追加(90行追加)
  2. 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パッケージの重要な機能の一つで、以下の処理を行います:

  1. ファイル内の連続するインポート文の識別
  2. インポートパスによるアルファベット順ソート
  3. 重複インポートの除去(可能な場合)
  4. コメントの適切な再配置

技術的詳細

修正されたバグの詳細

このコミットで修正された主要なバグは、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を使用することで以下の保証を得ています:

  1. 安全性: s.Pos() + 1は常にs.Pos()より大きい
  2. 妥当性: 文字列リテラルは最低2文字(""または` `)なので、元の終了位置より前に位置する
  3. 一貫性: 文字列の長さ変更に影響されない

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
    }
}

この変更により、一つのテストケースで複数のインポート書き換えパターンをテストできるようになりました。

関連リンク

参考にした情報源リンク