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

[インデックス 16386] ファイルの概要

このコミットは、Go言語の標準ライブラリ内のgo/astパッケージとgo/docパッケージに関連するビルドの問題を修正するものです。具体的には、抽象構文木(AST)における関数型(ast.FuncType)の表現と、それを利用するドキュメント生成ツールgo/docの挙動が改善されています。

変更されたファイルは以下の通りです。

  • src/pkg/go/ast/ast.go: ASTの定義、特にFuncTypePos()メソッドのロバスト性に関する修正。
  • src/pkg/go/doc/example.go: go/docパッケージ内の例(example)コードの処理に関する修正。ast.FuncTypeの生成方法が変更されています。
  • src/pkg/go/doc/example_test.go: go/docパッケージのテストケース。変数名がより一般的なものに変更され、可読性が向上しています。

コミット

e3a72b05f8d7ee2fa235e5592ca85b747f1bb310

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e3a72b05f8d7ee2fa235e5592ca85b747f1bb310

元コミット内容

go/doc: fix build

1) go/doc:
   - create correct ast.FuncType
   - use more commonly used variable names in a test case

2) make ast.FuncType.Pos robust in case of incorrect ASTs

R=golang-dev
CC=golang-dev
https://golang.org/cl/9651044

変更の背景

このコミットは、Go言語のツールチェインにおける2つの主要な問題に対処しています。

  1. go/docパッケージのビルド問題: go/docはGoのソースコードからドキュメントを生成するためのパッケージです。このパッケージが、特定の条件下でast.FuncTypeを正しく扱えず、ビルドエラーや予期せぬ挙動を引き起こしていた可能性があります。特に、ast.FuncTypeParamsフィールドがnilである場合に問題が発生していたと考えられます。
  2. 不正なASTに対するast.FuncType.Pos()のロバスト性向上: Goコンパイラやツールは、ソースコードを解析して抽象構文木(AST)を構築します。しかし、何らかの理由でASTが不完全または不正な状態になることがあります。ast.FuncTypePos()メソッドは、その型がソースコードのどこに位置するかを示す情報(token.Pos)を返しますが、不正なASTの場合にパニックを起こすなど、ロバスト性に欠けていたようです。コミットメッセージにある「issue 3870」がこの問題に関連している可能性が高いです。

これらの問題は、Goのドキュメント生成プロセスや、ASTを扱う他のツールに影響を与える可能性があったため、修正が必要とされました。

前提知識の解説

抽象構文木 (Abstract Syntax Tree, AST)

ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタプリタは、ソースコードを直接処理するのではなく、まずASTに変換してから、そのASTを基にコード生成や最適化、静的解析などを行います。Go言語では、go/astパッケージがASTの定義と操作を提供します。

go/astパッケージ

Go言語のgo/astパッケージは、Goプログラムのソースコードを表現するためのASTノードの型定義を提供します。例えば、関数宣言はast.FuncDecl、変数宣言はast.GenDecl、式はast.Exprなどで表現されます。

  • ast.FuncType: 関数の型を表すASTノードです。関数の引数(パラメータ)と戻り値の型情報を含みます。Paramsフィールドは引数のリスト(*ast.FieldList)を、Resultsフィールドは戻り値のリスト(*ast.FieldList)を保持します。
  • ast.FieldList: パラメータや戻り値のリストを表します。各要素はast.Fieldです。
  • token.Pos: ソースコード内の位置(行番号、列番号など)を表す型です。go/tokenパッケージで定義されます。
  • Pos()メソッド: 多くのASTノード型が持つメソッドで、そのノードがソースコード内で始まる位置(token.Pos)を返します。

go/docパッケージ

go/docパッケージは、Goのソースコードからパッケージ、関数、型、変数などのドキュメントを抽出・生成するためのツールです。go docコマンドやgodocツールがこのパッケージを利用しています。このパッケージは、ソースコードを解析してASTを構築し、そのASTからドキュメントコメントや宣言情報を読み取ります。

token.Pos.IsValid()

token.Pos型にはIsValid()メソッドがあります。これは、その位置情報が有効であるかどうかをチェックします。通常、token.NoPos(無効な位置)でない場合にtrueを返します。

技術的詳細

このコミットは、主に以下の2つの技術的な変更を含んでいます。

  1. ast.FuncType.Pos()の修正 (src/pkg/go/ast/ast.go):

    • 変更前:
      func (x *FuncType) Pos() token.Pos {
      	if x.Func.IsValid() {
      		return x.Func
      	}
      	return x.Params.Pos() // interface method declarations have no "func" keyword
      }
      
    • 変更後:
      func (x *FuncType) Pos() token.Pos {
      	if x.Func.IsValid() || x.Params == nil { // see issue 3870
      		return x.Func
      	}
      	return x.Params.Pos() // interface method declarations have no "func" keyword
      }
      

    この変更は、FuncTypeの開始位置を返すPos()メソッドのロジックを改善しています。以前はx.Funcfuncキーワードの位置)が有効でない場合、無条件にx.Params.Pos()を返していました。しかし、x.Paramsnilである場合(例えば、不正なASTや、FuncTypeが不完全に構築された場合)、x.Params.Pos()を呼び出すとパニックを引き起こす可能性がありました。 新しいロジックでは、x.Func.IsValid()trueであるか、またはx.Paramsnilである場合にx.Funcの位置を返します。これにより、x.Paramsnilであっても安全にPos()を呼び出せるようになり、不正なASTに対するロバスト性が向上しました。コメントで「see issue 3870」とあるように、この修正は特定のバグ報告に対応するものです。

  2. ast.FuncTypeの生成修正 (src/pkg/go/doc/example.go):

    • 変更前:
      funcDecl := &ast.FuncDecl{
      	Name: ast.NewIdent("main"),
      	Type: &ast.FuncType{},
      	Body: body,
      }
      
    • 変更後:
      funcDecl := &ast.FuncDecl{
      	Name: ast.NewIdent("main"),
      	Type: &ast.FuncType{Params: &ast.FieldList{}}, // FuncType.Params must be non-nil
      	Body: body,
      }
      

    go/docパッケージ内で、main関数を合成する際にast.FuncTypeを生成しています。以前はType: &ast.FuncType{}としていましたが、これはParamsフィールドがnilのままになります。ast.FuncTypeParamsフィールドは、たとえ引数がなくてもnilであってはならず、空のast.FieldListであるべきです。この修正により、Params: &ast.FieldList{}と明示的に初期化することで、go/docが生成するASTがより正確になり、ast.FuncType.Pos()のようなメソッドが安全に動作するようになります。

  3. テストケースの変数名変更 (src/pkg/go/doc/example_test.go):

    • fsfsetに、bbufに変更しています。これらはそれぞれtoken.FileSetbytes.Bufferのインスタンスを表す変数です。Goコミュニティでは、FileSetのインスタンスにはfsetbytes.Bufferのインスタンスにはbufという変数名が慣習的に使われることが多いため、コードの可読性と一貫性を向上させるための変更です。機能的な変更はありません。

コアとなるコードの変更箇所

diff --git a/src/pkg/go/ast/ast.go b/src/pkg/go/ast/ast.go
index e8599184a6..f26ff6b1af 100644
--- a/src/pkg/go/ast/ast.go
+++ b/src/pkg/go/ast/ast.go
@@ -439,7 +439,7 @@ func (x *KeyValueExpr) Pos() token.Pos   { return x.Key.Pos() }
 func (x *ArrayType) Pos() token.Pos      { return x.Lbrack }
 func (x *StructType) Pos() token.Pos     { return x.Struct }
 func (x *FuncType) Pos() token.Pos {\n-\tif x.Func.IsValid() {\n+\tif x.Func.IsValid() || x.Params == nil { // see issue 3870\n \t\treturn x.Func
 \t}\
 \treturn x.Params.Pos() // interface method declarations have no "func" keyword
diff --git a/src/pkg/go/doc/example.go b/src/pkg/go/doc/example.go
index 2761083c7e..2358ed3890 100644
--- a/src/pkg/go/doc/example.go
+++ b/src/pkg/go/doc/example.go
@@ -265,7 +265,7 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {\n \t// Synthesize main function.\n \tfuncDecl := &ast.FuncDecl{\n \t\tName: ast.NewIdent(\"main\"),\n-\t\tType: &ast.FuncType{},\n+\t\tType: &ast.FuncType{Params: &ast.FieldList{}}, // FuncType.Params must be non-nil\n \t\tBody: body,\n \t}\

コアとなるコードの解説

src/pkg/go/ast/ast.go の変更

FuncType構造体のPos()メソッドの変更は、ASTの健全性とロバスト性を高める上で非常に重要です。

  • 変更前: if x.Func.IsValid() { return x.Func } これは、funcキーワードが存在し、その位置が有効であれば、その位置を返すというロジックでした。しかし、funcキーワードがない場合(例えば、インターフェースのメソッド宣言など)や、何らかの理由でx.Funcが無効な場合、次の行のreturn x.Params.Pos()が実行されます。
  • 変更後: if x.Func.IsValid() || x.Params == nil { return x.Func } ここに|| x.Params == nilという条件が追加されました。これは、x.Funcが有効でない場合でも、もしx.Params(関数の引数リスト)がnilであれば、x.Funcの位置を返すようにします。 なぜこれが重要かというと、x.Paramsnilの場合にx.Params.Pos()を呼び出すと、nilポインタ参照によるパニックが発生する可能性があるためです。FuncTypeが不完全に構築されたり、不正なASTが渡されたりするシナリオでは、x.Paramsnilになることがあり得ます。この修正により、そのような不正な状態でも安全にPos()メソッドが呼び出せるようになり、Goツールチェイン全体の安定性が向上します。コメントにある「issue 3870」は、この特定のバグ報告を指しています。

src/pkg/go/doc/example.go の変更

playExample関数内でmain関数を合成する際のast.FuncTypeの初期化方法が変更されました。

  • 変更前: Type: &ast.FuncType{} この初期化では、ast.FuncTypeParamsフィールドはGoのゼロ値であるnilになります。
  • 変更後: Type: &ast.FuncType{Params: &ast.FieldList{}} この変更により、FuncTypeParamsフィールドが明示的に空のast.FieldListインスタンスで初期化されます。GoのASTの設計では、引数がない場合でもParamsフィールドはnilではなく、空のFieldListであるべきという慣習があります。これにより、go/docが生成するASTがより仕様に準拠したものとなり、ast.FuncType.Pos()のようなメソッドがParamsフィールドがnilであることによる問題を回避できるようになります。これは、go/ast/ast.goでのPos()メソッドの修正と連携して、より堅牢なシステムを構築します。

src/pkg/go/doc/example_test.go の変更

このファイルでは、テストコード内の変数名が変更されました。

  • fs -> fset: token.FileSetのインスタンスを表す変数名として、fsetがより一般的で分かりやすいです。
  • b -> buf: bytes.Bufferのインスタンスを表す変数名として、bufがより一般的で分かりやすいです。

これらの変更はコードの機能には影響しませんが、Goコミュニティのコーディング規約や慣習に合わせることで、コードの可読性と保守性を向上させます。

関連リンク

  • Go issue 3870: このコミットのast.FuncType.Pos()の修正に関連するバグ報告です。go/printerFuncTypeParamsフィールドがnilの場合にクラッシュするという問題が報告されています。
  • Go CL 9651044: このコミットに対応するGoのコードレビューシステム(Gerrit)のチェンジリストです。

参考にした情報源リンク

  • Go言語の公式ドキュメント: go/astパッケージ、go/docパッケージ
  • Go言語のソースコード
  • Go言語のIssueトラッカー (GitHub)
  • Go言語のコードレビューシステム (Gerrit)